経緯
LINEのプッシュ通知をLaravelで実装しようと思って調べてみたら、コミュニティが提供している通知チャンネルの中にLINE通知がなかったため、自分で作ってみました。
(LINEの国外ユーザ数を考えれば、提供されていないのは当然かもしれませんが、、)
環境
- PHP 8.0.9
- Laravel Framework 8.51.0
前提
-
LINE Messaging APIでチャネルを作成していて、
Channel access token
とChannel secret
を持っている - LINEの
userId
(友達検索などで使うLINE IDではなく、LINEが管理しているユーザ固有のID)がDBに登録されている
Laravel で LINE Messaging API を簡単に使う
curl
でそれぞれのエンドポイントを叩くコードを自分でイチから実装しても良かったのですが、めんどくさかったのでLINE Messaging API SDK for PHPを使うことにしました。
下記コマンドでSDKをインストールします。
$ composer require linecorp/line-bot-sdk
.env
ファイルに以下の環境変数を追加します。
LINE_BOT_CHANNEL_ACCESS_TOKEN=<前提のChannel Access Token>
LINE_BOT_CHANNEL_SECRET=<前提のChannel Secret>
この環境変数は、vendor\linecorp\line-bot-sdk\src\Laravel\config\line-bot.php
で読み込まれて、LINEBotServiceProvider
によってLINEBot
ファサードが登録されます。
これで、Laravel App のどこからでもLINEBot
ファサードを呼べるようになり、LINE Messaging APIを利用することができます。
$profile = \LINEBot::getProfile($user_id);
LINE通知チャンネルの作成
下記を参考に実装しました。
app/
├── Channels/
│ └── LINEBot/
│ ├── LineChannel.php
│ └── Exceptions/
│ └── CouldNotSendNotification.php
<?php
namespace App\Channels\LINEBot;
use App\Channels\LINEBot\Exceptions\CouldNotSendNotification;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Notifications\Events\NotificationFailed;
use Illuminate\Notifications\Notification;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;
class LineChannel
{
/**
* @var Dispatcher
*/
protected $events;
public function __construct(Dispatcher $events)
{
$this->events = $events;
}
/**
* Send the given notification.
*
* @return \LINE\LINEBot\Response
*/
public function send($notifiable, Notification $notification)
{
try {
$destination = $this->getDestination($notifiable, $notification);
$message = $this->getMessage($notifiable, $notification);
$response = \LINEBot::pushMessage($destination, $message);
if (!$response->isSucceeded()) {
throw CouldNotSendNotification::failedToPushMessage($response);
}
return $response;
} catch (\Exception $e) {
\Log::error($e);
$event = new NotificationFailed(
$notifiable,
$notification,
'line',
['message' => $e->getMessage(), 'exception' => $e]
);
$this->events->dispatch($event);
}
}
/**
* Get the LINE userId to send a notification to.
*
* @throws CouldNotSendNotification
*/
protected function getDestination($notifiable, Notification $notification)
{
if ($to = $notifiable->routeNotificationFor('line', $notification)) {
return $to;
}
return $this->guessDestination($notifiable);
}
/**
* Try to get the LINE userId from some commonly used attributes for that.
*
* @throws CouldNotSendNotification
*/
protected function guessDestination($notifiable)
{
$commonAttributes = ['user_id', 'line_id', 'line_user_id'];
foreach ($commonAttributes as $attribute) {
if (isset($notifiable->{$attribute})) {
return $notifiable->{$attribute};
}
}
throw CouldNotSendNotification::invalidReceiver();
}
/**
* Get the LINEBot TextMessageBuilder object.
*
* @throws CouldNotSendNotification
*/
protected function getMessage($notifiable, Notification $notification): TextMessageBuilder
{
$message = $notification->toLine($notifiable);
if (is_string($message)) {
return new TextMessageBuilder($message);
}
if ($message instanceof TextMessageBuilder) {
return $message;
}
throw CouldNotSendNotification::invalidMessageObject($message);
}
}
<?php
namespace App\Channels\LINEBot\Exceptions;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;
use LINE\LINEBot\Response;
class CouldNotSendNotification extends \Exception
{
public static function invalidReceiver()
{
return new static(
'The notifiable did not have a receiving LINE userId. Add a routeNotificationForLine
method or one of the conventional attributes to your notifiable.'
);
}
public static function invalidMessageObject($message)
{
$type = is_object($message) ? get_class($message) : gettype($message);
return new static(
'Notification was not sent. The message should be a instance of `'.TextMessageBuilder::class."` and a `{$type}` was given."
);
}
public static function failedToPushMessage(Response $response)
{
return new static(
"Failed to send LINE messages\n".
"httpStatus: {$response->getHTTPStatus()}\n".
"message: {$response->getJSONDecodedBody()['message']}"
);
}
}
LINE通知チャンネルの使い方
他の通知チャンネルと同じように使うことができます。
通知クラスのvia()
メソッドでLineChannel::class
を指定して、toLine()
メソッドを実装します。
<?php
use App\Channels\LINEBot\LineChannel;
use Illuminate\Notifications\Notification;
class SomeNotification extends Notification
{
public function via($notifiable)
{
return [LineChannel::class];
}
public function toLine($notifiable)
{
// 通知内容が書かれたViewファイルの文字列を取得(もちろん、文字列を直接記述してもよい)
$message = view('notifications.some_notification')->render();
// 文字列をそのまま返す方法:
return $message;
// LINEBotのTextMessageBuilderを返す方法:
return new \LINE\LINEBot\MessageBuilder\TextMessageBuilder($message);
}
}
次に、LINE通知先のuserIdが格納されたテーブルのモデルに、Notifiable
トレイトを追加します。
デフォルトで、テーブルカラム名がuser_id
, line_id
, line_user_id
のいずれかであるデータを通知先として採用します。
通知先をオーバライドするためには、routeNotificationForLine()
メソッドを実装します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
class SomeModel extends Model
{
use Notifiable;
public function routeNotificationForLine($notification)
{
return 'xxxxxxxxxxxx';
}
}
これで、モデルのnotify()
メソッドを呼ぶと、実際にLINE通知を送信することができるようになります。
SomeModel::find($input_id)->notify(new SomeNotification());