Laravel でイベントとハンドラを使って通知を送る
イベント・リスナ
イベント定義を使って、「なにか」が起きたとき「これをやる」を定義出来る。
ジョブキューで分割すると思いがちだけど、別にシングルスレッドで実行しても構わない。
シングルスレッドでイベントとリスナを使った場合でもソースコードがスッキリするので、積極的に使っていきたい。
業務系のプログラミングの登録・削除や、セキュリティ監査プログラミングなど、複数箇所で似たようなコード(動作ログの保存など)が一箇所にまとまるので便利かもしれない。業務系のワークフローなどはイベント発生連鎖で整理して、イベントハンドラを定義したほうがスッキリすると思う。
イベントとリスナを作って使う
イベントとハンドラ(リスナ)をまとめて定義する。
php artisan event:generate
php artisan make:event SampleEvent
php artisan make:listener SampleListener
イベントを起動するコマンドも定義しておく。
php artisan make:command DispatchEventSample
作成されるファイル
イベントとリスナをペアで作ったので、次のファイルとディレクトリ(フォルダ)が作成される。
app/Events/SampleEvent.php
app/Listeners/SampleListener.php
Eventの定義
いまは、特に何もしない。クラスを作ればイベントとして使える。
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SampleEvent {
use Dispatchable, SerializesModels;
}
?>
リスナの定義
リスナでは、イベント発生時の動作を定義する
<?php
namespace App\Listeners;
class SampleListener {
public function __construct () {
}
public function handle ( $event ) {
dump(get_class($event).'が発生し、リスナが起動しました');
}
}
イベントとリスナを紐づける。
イベントとハンドラの紐づけは、event:generate
でServiceProviderが作成され、その中で行われる。
EventServiceProvider
の $lisnen
変数に配列で登録してあげればいい。
リスナ紐づけは、キー・バリューで登録を書いていくわけだ。
app/Providers/EventServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use App\Events\SampleEvent;
use App\Listeners\SampleListener;
class EventServiceProvider extends ServiceProvider {
protected $listen = [
SampleEvent::class => [SampleListener::class],
];
public function boot () {}
public function shouldDiscoverEvents () {
return false;
}
}
イベントを発生させる
イベントを発生させてみる。
メイン関数の代わりに、コンソール・コマンドを次のように作成した。
app/Console/Commands/DispatchEventSample.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Events\SampleEvent;
class DispatchEventSample extends Command {
protected $signature = 'my_event:sample';
public function handle () {
SampleEvent::dispatch();
return 0;
}
}
イベントを発生させる。
takuya@host $ php artisan event:sample
^ "App\Events\SampleEventが発生し、リスナが起動しました"
これで、イベントが発生し、Laravelがソースコードを飛び回ってイベントに対応したハンドラを起動してくれることがわかる。
リスナに情報を渡す。
Eventが発生したときに、詳細情報を渡す必要があるが、この点においては、詳細情報をどこにいれたり、イベント・クラスの構造には特に定義がない。自分で定義する必要がある。コンストラクタで渡すことだけが決まっている。
イベントの発生箇所
<?php
class DispatchEventSample extends Command {
protected $signature = 'my_event:sample';
public function handle () {
SampleEvent::dispatch(['aaaaaa']);
return 0;
}
}
イベント・クラス定義
イベント発生時に保存しておく変数をイベント・クラスに定義して保存する。
<?php
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SampleEvent {
use Dispatchable, SerializesModels;
protected $args;
public function __construct (array $arr) {
$this->args = $arr;
}
public function getEventArg(){
return $this->args;
}
}
イベント・リスナ定義
イベントクラスから取り出す。
<?php
namespace App\Listeners;
class SampleListener {
public function handle ( $event ) {
$args = $event->getEventArg();
dump($args);
}
}
注意点
LaravelではEventの構造に特に指定がないので、ハンドラ側でイベントを受け取るときに、クラス指定ができない。
そのため、IDE補完ができない。
もちろん、クラス指定してもいいのだろうが、今後Laravel本体で指定されるかもしれないので、ソースコードに引数の型を直接に指定するのはちょっと怖いと思ったり。
リスナで次のように書きたくなるが、、、Laravelが公式にEventクラスやFacadeを導入してくると困るので
public function handle ( SampleEvent $event ) {
doc comment で@paramを使うほうが無難かもしれない。
<?php
@param
public function handle ( $event ) {
イベント発火・通知
イベント発生から通知の送信は切っても切れない関係にある。
そのために、通知を使うと思う。リスナのなかで通知してもいいし、リスナ自身を通知として登録してもいい。
リスナ・通知でクラスを分ける例
<?php
namespace App\Listeners;
use Illuminate\Support\Facades\Notification;
use App\Notifications\MyFirstNotification;
class SampleListener {
public function handle ( $event ) {
Notification::route( 'mail', 'takuya@example.com' )
->notify( new MyFirstNotification() );
}
}
または、通知を直接にイベントハンドラとして登録する。
<?php
class EventServiceProvider extends ServiceProvider {
protected $listen = [];
public function boot () {
\Event::listen( SampleEvent::class, function( $event ) {
Notification::route( 'mail', 'takuya@9b-p.com' )
->notify( new MyFirstNotification() );
},
);
}
}
まとめ
イベント・ハンドラに切り分けることで、フックを簡単に作れる。Wordpressでよく見るイベント・フック系の関数をクラス定義で作れるので、知っていればどこを見ればいいかわかりやすくなる。イベント連鎖や、変化があったときの記録、何がが起きたときに何かするという、人間の行動に近いプログラミングが可能になり、ソースコードの再利用性とメンテナンス性が高まる。オブジェクト指向プログラミングの基本だったりする。
イベント・ハンドラは、Slackやメール通知など、通知機能と組み合わせて使うから役に立つ。
さらなる発展
イベント・リスナが複数のイベントを受け取るときはObserverを使うといい。
イベントの処理をキューイングしてあとに回したり、キュー処理のワーカーと、キューイングを別のインスタンスに分けて強靭化できるかもしれない。