それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Laravelのイベント・リスナをDB利用で非同期に分離動作する。

Laravelのイベント・リスナを分割する。

データベースを使ってイベント・リスナを分割する。

データベース(SQL)を使って、イベントをキューに登録し、queue:work でリスナがイベントを拾って動作させる。

データベースにイベント発生を登録してジョブキューとワーカーにする。イベントハンドラとイベントを動作タイミングを分離してレスポンスを高速化したり、後でいいものは後ろに回す。

プロジェクトを作る

まず、プロジェクトを作成して実験環境を整える。

composer create-project laravel/laravel database-listener-sample
cd database-listener-sample
sed -i config/app.php -e 's|UTC|Asia/Tokyo|'
sed -i .env -e 's/DB_/#DB_/' -e '/mysql/i DB_CONNECTION=sqlite'
php artisan migrate
## キューをDBに入れる。
sed -i .env -e 's/QUEUE_CONNECTION=sync/QUEUE_CONNECTION=database/'

イベントとリスナを作る

ここで作ったリスナが、php artisan queue:work でリスナを受け取って実行する。

また、イベントを発火させるコマンドを作る。

php artisan make:event MyEvent
php artisan make:listener MyListener
php artisan make:command AddJob

コマンドからイベントを発火させる部分

ここでは、手動でイベントを発火させるコードを記入しておく。

<?php

namespace App\Console\Commands;

use App\Events\MyEvent;
use App\Listeners\MyListener;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Event;

class AddJob extends Command {  
  protected $signature = 'app:add-job';
  public function handle() {
    Event::listen(MyEvent::class,MyListener::class);
    Event::dispatch(new MyEvent());
  }
}

イベント

ここでは、特に何も書かない。イベントとしてクラスを定義するだけ

<?php
namespace App\Events;

use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class MyEvent {  
  use Dispatchable, InteractsWithSockets, SerializesModels;
}

リスナ定義

リスナはイベント発火時に動作をする。イベントはデータベースから引き取るためにconnection を入れる。

<?php
namespace App\Listeners;

use App\Events\MyEvent;
use Illuminate\Contracts\Queue\ShouldQueue;

class MyListener implements ShouldQueue {
  
  public $connection = 'database';
  public function __construct() {}
  
  public function handle( MyEvent $event ):void {
    dump([$this, $event]);
  }
}

DBの作成

キューをためておくテーブルを作る。

php artisan queue:table
php artisan migrate
## または php artisan migrate:fresh

準備完了

ここまでで、キューテーブルをつくり、テーブルでイベントの受け渡しをするListernを作り、イベントを発火させるコマンドを作った。

実験

コマンドを実行して、イベント発火する

php artisan app:add-job

ここでは、イベントを発火させるだけ。

ジョブの登録を確認。

イベントはジョブとしてキューに貯まるはずので、それを確認する。

sqlite3 database/database.sqlite 'select * from jobs;'
-- Loading resources from /Users/takuya/.sqliterc
id  queue    payload                               attempts  reserved_at  available_at  created_at
--  -------  ------------------------------------  ----  -----------  ------------  ----------
1   default  {..."App\\Listeners\\MyListener",...  0                      1707798204    1707798204

リスナを動作させる。

キューに溜まったイベントをリスナで動かす。

php artisan queue:work

コマンド実行すると、ワーカーが、キューを読み込んで、リスナに処理をやらせてくれる。

INFO  Processing jobs from the [default] queue.

  2024-02-13 03:46:10 App\Listeners\MyListener ......... RUNNING
  2024-02-13 03:46:10 App\Listeners\MyListener ......... 8.69ms DONE

あとは、画面を見ながら、イベントを発火せてリスナが処理をするのを眺めていれば、処理機構の枠組みがわかってくる。

覚えるポイント

キューを使ってイベント・リスナの処理を分割するときのポイント。

リスナをデータベースに入れる。

$connection = 'database' を使ってイベント発火し、リスナに割り当てる(dispatch)するとき、実行せずにデータベースに放り込む。

<?php
class MyListener {
    public $connection = 'database';
}

リスナを「動作可能にする」

laravel ではClass::handle()Class::__invoke()を用意すれば、クラスを「実行可能」と定義できる。

<?php
class MyListener implements ShouldQueue {
    public function handle( ...);
}

イベント発火の方法

次はほぼ同じ。

Event::dispatch(new MyEvent());
event(new MyEvent());

イベントとリスナを紐付ける方法

サービスプロバイダで定義してもいいし、Event::listen を使ってもいい。

サービスプロバイダを使うときはEventServiceProviderにかくか、AppServiceProvider#boot()にかく。

Evnet:listen はFacedeに定義されている。\Illuminate\Support\Facades\Event がクラス名である。

キューをデータベースに入れる。

sed -i .env \
  -e 's/QUEUE_/#QUEUE_/' \
  -e '/CONNECTION=sync/i QUEUE_CONNECTION=database'

envに書かずに、動かすには、自分で書くしかない。

データベースに入れ設定を先に書いておかないと、queue:work によるキュー動かないので注意。

2024-05-30

下書きから更新。