LoginSignup
10
7

More than 1 year has passed since last update.

LINE notifications channel for Laravel を作ってみた

Posted at

経緯

LINEのプッシュ通知をLaravelで実装しようと思って調べてみたら、コミュニティが提供している通知チャンネルの中にLINE通知がなかったため、自分で作ってみました。
(LINEの国外ユーザ数を考えれば、提供されていないのは当然かもしれませんが、、)

環境

  • PHP 8.0.9
  • Laravel Framework 8.51.0

前提

  • LINE Messaging APIでチャネルを作成していて、Channel access tokenChannel 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を利用することができます。

例:LINEのプロフィール情報を取得する

$profile = \LINEBot::getProfile($user_id);

LINE通知チャンネルの作成

下記を参考に実装しました。

ディレクトリ構成
app/
├── Channels/
│   └── LINEBot/
│       ├── LineChannel.php
│       └── Exceptions/
│           └── CouldNotSendNotification.php
app\Channels\LINEBot\LineChannel.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);
    }
}
app\Channels\LINEBot\Exceptions\CouldNotSendNotification.php
<?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());
10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7