Laravelのイベントを使ってみた


◆メリット

イベントとリスナーを利用(オブザーバーパターン)すれば、コントローラーの一つのメソッドに書くコード量を減らすことができ、可読性・管理のしやすさが上がる。

◆オブザーバーパターンとは

監視する側(リスナー)と監視される側(イベント)に分けて、イベントの変化をリスナーが受け取って処理するという感じ

◆イベント・リスナのクラス作成(実装)

今回の機能:アクセス時に、アクセスIPをLogに記述する

コマンド
php artisan event:generate

このコマンドで、イベントクラスとリスナークラスを作ってくれるが、そのために、
EventServiceProviderにイベントとリスナーを登録する必要がある。
(php artisan make:eventとphp artisan make:listenerで個別にも作れる)
(登録方法はほかにも、ファサードもしくはサービスコンテナのeventに登録する方法もある)
https://readouble.com/laravel/5.7/ja/events.html

EventServiceProvider
protected $listen = [
    //デフォルトで登録されているもの
    'App\Events\Event' => [
        'App\Listeners\EventListener',
    ],
    //アクセスを検出するイベント
    'App\Events\AccessDetectionEvent' => [
        // アクセスIP記録リスナー
        'App\Listeners\AccessIpRecordListener',
    ],
];

イベントの中にリスナーを記述するようにする(複数のイベント・リスナーに紐づけることも可能)。完了したら、

php artisan event:generate

で作成!

◆イベントクラスの実装

class AccessDetectionEvent
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $ip;

    public function __construct($ip)
    {
        $this->ip = $ip;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('channel-name');
    }
}

今回は、イベントクラスはipを設置するだけ
二つめのメソッドのbroadcastOnはブロードキャストイベントを使いたい時用
[ブロードキャストイベント]https://blog.ytake.jp.net/entry/2015/12/15/023631

トレイト
・Dispatchable:イベントを発火させる(ディスパッチ)ときに利用
・InteractsWithSockets:soket.ioを使用したイベント通知時利用
・SerializesModels:キューと組み合わせて非同期イベントを発火させるためのもの

◆リスナークラスの実装

class AccessIpRecordListener
{
    public function __construct()
    {
        //
    }

    public function handle(AccessDetectionEvent $event)
    {
        Log::info($event->ip);
    }
}

イベントのipプロパティにアクセスして、その情報をLogに記述している。

◆コントローラーの実装

class SampleController extends Controller
{
    public function sample(Request $request)
    {
        event(new AccessDetectionEvent($request->ip()));

        dd('Logファイルみよう。');
    }
}

eventメソッドでイベントを発行(ディスパッチ)している。

◆結果

Log
[2019-04-06 00:00:00] local.INFO: 192.168.99.1

◆まとめ

コントローラー内のメソッドの処理の分割に成功した。
このイベント機能は、他にもキューを使った遅延処理や非同期で行う方法などがあるらしいから、やりたいと思う。

[参考にさせていただいた記事]
https://www.ritolab.com/entry/35

おまけ

◆非同期リスナーを実装

コマンド
php artisan make:listener AccessIpRecordQueueListener
class AccessIpRecordQueueListener implements ShouldQueue
{
    use InteractsWithQueue;

    public function __construct()
    {
        //
    }

    public function handle($event)
    {
        Log::info($event->ip);
    }
}

ShouldQueueを実装して、use InteractsWithQueue;を記述するだけでOK
あとは、同じようにLogに書き込む処理を追加する。

EventServiceProvider
protected $listen = [
    Registered::class => [
        SendEmailVerificationNotification::class,
    ],
    //アクセスを検出するイベント
    AccessDetectionEvent::class => [
        // アクセスIP記録リスナー
        AccessIpRecordListener::class,
        //アクセスIP記録リスナー(非同期)
        AccessIpRecordQueueListener::class
    ],
];

非同期用のリスナーを登録する。

env
QUEUE_CONNECTION=sync

envファイルをみると、同期になっているので、非同期設定にする。
(データベース・redis・SQSなどを使える)
今回は、redisを使ってみる。

env
QUEUE_CONNECTION=redis
REDIS_HOST=redis
php artisan config:cache

設定を変えたので、設定のキャッシュをクリア

composer require predis/predis

Redisを使うために導入する。

php artisan queue:work

キューを監視させる(非同期リスナーの起動)
アクセスする

redis connection refused
のエラーが出た。dockerにredisを加えるのを忘れてた。

docker-compose.yml
redis: 
    image: redis
    container_name: redis
    ports: 
      - "6379:6379"
コマンド
docker-compose up -d

再びアクセス

root@○○:/var/www# php artisan queue:work
[2019-04-06 00:00:00][○○] Processing: App\Listeners\AccessIpRecordQueueListener
[2019-04-06 00:00:00][○○] Processed:  App\Listeners\AccessIpRecordQueueListener
Log
[2019-04-06 00:00:00] local.INFO: 192.168.99.1  
[2019-04-06 00:00:00] local.INFO: 192.168.99.1 

ちゃんと同期と非同期で2個Logが記述されていた。クリア

[参考にさせていただいた記事]
https://qiita.com/yk-m/items/d65dedfa291d9cbfed38