【！2022年最新！】Laravel9でLINE Messaging APIを使う

Qiita初投稿です！お手柔らかにお願いします。

業務でLINE Messaging APIを使う機会はあったのですが、当時はPHP7.4だったりLaravelも8系だったりと
古い環境だったので、現在の最新バージョンでサンプルを作ってみました。
これからLaravelを使って、LINEBotなどを作ろうとしている方は参考になるかと思います。

## 準備
環境を準備します。
今回は以下の環境でやりました。（OSはWindowsでしたがMacでも同様です）
- Windows10 Pro 22H2(WSL2インストール)
- Docker Desktop 4.9.1 (81317)
- 以下、docker内でインストール
    - PHP 8.1
    - Laravel v9.38.0
    - line-bot-sdk 7.6.1

docker環境の作り方やLaravelのインストール方法などは割愛します。
composerを使い、line-bot-sdkをインストールします。
```
composer require linecorp/line-bot-sdk
```

私はここでミスに気付かずに、古いバージョン(3.6)をインストールしてしまいました。
正常にインストール自体はできたので、問題ないと思ったんです。
ただ、ライブラリは記事執筆時点で7.6.1がリリースされているため、
私のように古いバージョンがインストールされてしまった方は注意して下さい。
packagistに情報がある通り
- phpのバージョンは7.2以上
- ext-curl、ext-json、ext-socketsが有効になっている

が必要です。

## LINE DevelopersでMessaging APIを有効にする
この辺りの記事を参考に↓

https://qiita.com/YSFT_KOBE/items/8dc62ac40c5112df2ed3

LINEでMessaging APIを使えるように設定します。

## いよいよ実装
それではいよいよ実装です。
似たような記事は他にもあると思うので、そちらも参考にしてみると良いでしょう。

### ルーティング
routes/api.phpを編集します。
```routes/api.php
Route::post('line-callback', [LineBotController::class, 'callback'])->name('line.callback');
```

ここで設定した内容によってPOSTするURLが決まります。
URLが決まったら、LINE DevelopersサイトでWebhook URLの入力・検証するのをお忘れなく。

### コントローラ
今回は「app\Http\Controllers\Api\Line\LineBotController.php」とします。
```app\Http\Controllers\Api\Line\LineBotController.php
class LineBotController extends Controller
{
    public function callback(LINEBot $bot, Request $request): JsonResponse
    {
        return response()->json('success');
    }
}
```
LINEBotクラスのインスタンスをメソッドインジェクションしています。
後述で解説します。

### LINEに接続するための環境変数設定
.envファイルを用意し、LINEへの接続情報（アクセストークン、アクセスシークレット）を設定します。
今回は下記のようにしました。
```config/const.php
<?php

return [
    'line_token' => env('LINE_ACCESS_TOKEN', ''),
    'line_secret' => env('LINE_CHANNEL_SECRET', ''),
];

```
```.env
APP_NAME=laravel-linebot-sample
APP_ENV=local
APP_KEY="base64:hogehogehoge..."
APP_DEBUG=true
APP_URL=http://localhost

　～～～　中略　～～～

LINE_ACCESS_TOKEN=aaaaaaa
LINE_CHANNEL_SECRET=bbbbbb
```

ここも、古いバージョンのline-bot-sdkをインストールしてしまったことで
config/const.phpを作り、環境変数を代入するという作り方をしたのですが
最新版はenvの設定のみで動くようになっています。
vendor\linecorp\line-bot-sdk\src\Laravel\config\line-bot.php
というファイルがありますので、そちらを覗くと
```php
return [
    'channel_access_token' => env('LINE_BOT_CHANNEL_ACCESS_TOKEN'),
    'channel_secret' => env('LINE_BOT_CHANNEL_SECRET'),
];
```
と定義されていますので、そちらに合わせてenvを作成してください（私も後で直したい・・・）

### サービスプロバイダを登録する
コントローラの解説部分で、LINEBotクラスのインスタンスをメソッドインジェクションしている
と書きましたが、サービスプロバイダを使ってDIするようにしています。
```app\Providers\LineBotProvider.php
class LineBotProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(LINEBot::class, function () {
            $httpClient = new CurlHTTPClient(config('const.line_token'));
            return new LINEBot($httpClient, ['channelSecret' => config('const.line_secret')]);
        });
    }

}
```
Controllerやビジネスロジック発動の度に毎度LINEBotインスタンスを作るのが面倒だったので
singletonで必ず１度初期化するようにしてます。
このあたりは個人の好みですので、初学者の方など、まだDIの仕組みやLaravelFrameworkに慣れていない方は
ロジック側でnewする形でも良いでしょう。

サービスプロバイダを作ったら、config\app.phpで読み込ませます。
```config\app.php
'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,

　　　　 // ～～～　中略　～～～

        /** add */
        App\Providers\LineBotProvider::class,

    ],
```

ここまでで、一先ず疎通はできる段階かなと思います。
この先で具体的な処理を書いていきます。

### コントローラの修正
最初に作ったコントローラを修正します。

```app\Http\Controllers\Api\Line\LineBotController.php
class LineBotController extends Controller
{
    public function callback(LINEBot $bot, Request $request): JsonResponse
    {
        // Line Signature Check
        $signature = $request->header(HTTPHeader::LINE_SIGNATURE);
        if (empty($signature)) {
            return abort(400, 'Bad Request');
        }

        try {
            $events = $bot->parseEventRequest($request->getContent(), $signature);

            foreach ($events as $event) {

                if ($event instanceof FollowEvent) {
                    // 友達登録時の処理
                } elseif ($event instanceof TextMessage) {
                    // テキストメッセージが送られてきた時の処理
                } elseif ($event instanceof ImageMessage) {
                    // 画像メッセージが送られてきた時の処理
                } elseif ($event instanceof PostbackEvent) {
                    // ポストバックが来た時の処理
                } elseif ($event instanceof UnfollowEvent) {
                    // 友達解除時の処理
                }
            }
        } catch (Throwable $th) {
            logger()->error($th->getMessage());
            logger()->error($th->getTraceAsString());
        }
        

        return response()->json('success');
    }
}
```

一応、try～catchブロックで主処理を囲んでます。
先ず、最初に「シグネチャチェック」というものを実行します。
LINEサーバからwebhookされてきたものが正しいかどうかチェックする処理です。
必須というわけでは無いのですが、LINE Messaging API開発ガイドラインに従い、実装します。
https://developers.line.biz/ja/docs/messaging-api/development-guidelines/

次にevent情報を受け取り、foreachループ内で処理します。
LINEは「Webhookイベントオブジェクト」という形でデータを送信してきます。
この「Webhookイベントオブジェクト」は配列、且つ順番を保持しません（元を辿るとjson形式になります）
詳しくはこちらの資料を参照してみてください。
https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects

配列で受け取った後は、単一のeventオブジェクトがどんな処理なのかを判定します。
line-bot-sdkでイベントをパースした場合、それぞれのクラスのオブジェクトに変化します。
- 友達登録：FollowEvent
- 友達解除：UnfollowEvent
- テキストメッセージ：MessageEvent\TextMessage
- 画像メッセージ：MessageEvent\ImageMessage

というような感じです。今回のケースでは、変数$eventの「型」がどのクラスのものかで種類を判定します。
後は、それぞれのイベントで自由にロジックを組むと良いでしょう。

以上です。GitHubに実装したものを置いていますので
最終形がどんなものになるのか、気になる方はそちらもご覧ください。
実際に友達登録してみて、動作を試すこともできます。
https://github.com/engineer-yone3/laravel-linebot-sample

ここまでご覧頂き、ありがとうございました。