こんにちはみなさん
Laravelって「こうやって書けたらいいなぁ」みたいなものがなんでか書けちゃうみたいな感じになっていて、よく驚かされるわけですが、一方で、「かったるいなぁ」って思うような書き方も残っているわけです。
イベントの仕組みはその中の一つだったのですが、それがLaravel5.8.9になってから解消されたようなので、紹介していきましょう。
https://laravel.com/docs/5.8/events
Laravelのイベント
Laravelにはイベントの仕組みがあって、特定のイベントが発火すると、それに対応したコールバックを実行する事ができます。さらに、このコールバックはQueueの仕組みと組み合わせることで、非同期で実行することができるため、重たい処理を後回しにしてレスポンスを返すこともできます。
イベントの作成
まず、普通にイベント・リスナーのセットを作ってみましょう。公式の作り方に倣ってみましょう。
まず、EventServiceProvider
には以下のように書きます。
protected $listen = [
'App\Events\SomethingEvent' => [
'App\Listeners\SomethingEventListener'
]
];
この設定は、SomethingEvent
というイベントが発火すると、コールバックとしてSomethingEventListener
のハンドラが実行されることを意味しています。
とはいえ、これを書いただけでは発火するイベントもそれを受け取るイベントもありません。この設定をもとに、実際のイベントとリスナーのクラス定義を生成する必要があります。
$ php artisan event:generate
Events and listeners generated successfully!
すると、app/Events/SomethingEvent.php
とapp/Listeners/SomethingEventListener.php
が生成されます。
あとは、適当にリスナーを書いていくわけです。
微妙なところ
こんな感じでイベントを書いていくわけですが、そうするとEventServiceProvider
がどんどん膨らんでいきますし、いちいちApp\Events\...
みたいなフルパスのクラスをいちいち書かないといけません。
イベントディスカバリー
イベントディスカバリーはEventServiceProvider
を使わないで済む仕組みです。なぜか、Laravel5.8.9という、パッチバージョンで入れ込んできましたが、これを使うとリスナー側のhandler
に指定されているイベントの型宣言を読んで、その型のイベントのリスナーに自動で登録されるという仕組みです。
イベントディスカバリーを試す
ではイベントディスカバリーを試してみましょう。まず、イベントディスカバリーを有効にする必要があります。
//...中略
public function shouldDiscoverEvents()
{
return true;
}
これをEventServiceProvider
にて設定することで、イベントディスカバリーが有効になります。
イベントとリスナーを作る
イベントを作りましょう。これは、コマンドを作って作れます。
php artisan make:event DiscoveryEvent
次に、リスナーを作ります。これもコマンドを使うのですが、監視するイベントを設定する必要があります。
php artisan make:listener DiscoveryEventListener --event DiscoveryEvent
あとで動作確認したいので、リスナーにコードを足しましょう。
//...中略
/**
* Handle the event.
*
* @param DiscoveryEvent $event
* @return void
*/
public function handle(DiscoveryEvent $event)
{
\Log::debug('聞き届けました!')
}
ここで、handleの引数に型宣言がしてありますが、これがあることで、イベントディスカバリーにより、自動でイベントのリスナーに登録されます。
動作を確認する
簡単に動作確認しましょう。
まず、適当なアクションにおいてイベントを発火させてみましょう。
<?php
use App\Events\DiscoveryEvent;
Route::get('/', function () {
DiscoveryEvent::dispatch();
return view('welcome');
});
トップページアクセスでイベントが発火するようになりました。
こいつを動かすのですが、ブラウザ使わなくとも、テストで簡単に確認できます。
幸い、Laravelのプロジェクトを作ると、tests
というディレクトリができていて、そこにはすでにサンプルのテストがあります。
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
}
なので、その場でテスト流すだけでイベントの発火を確認できます。
./vendor/bin/phpunit
テストを実行すると、ログに先程のリスナーで設定したものが記載されています。
[2019-07-17 13:02:32] local.DEBUG: 聞き届けました!
無事、動いたようです。
リストの表示
EventServiceProvider
による集中管理をやめたため、どのイベントがどのリスナーに監視されているのかわからなくなりそうですが、ちゃんとこれをリスト化して提供する仕組みがあるので問題ありません。
$ php artisan event:list
+-----------------------------------+-------------------------------------------------------------+
| Event | Listeners |
+-----------------------------------+-------------------------------------------------------------+
| App\Events\DiscoveryEvent | App\Listeners\DiscoveryEventListener@handle |
| App\Events\SomethingEvent | App\Listeners\SomethingEventListener@handle |
| | App\Listeners\SomethingEventListener |
| Illuminate\Auth\Events\Registered | Illuminate\Auth\Listeners\SendEmailVerificationNotification |
+-----------------------------------+-------------------------------------------------------------+
ここでSomethingEvent
にリスナーが二重に登録されてしまっていますが、これはEventServiceProvider
の$listen
にリスナーが登録されたままになっており、EventServiceProvider
の設定とイベントディスカバリーの動作がかぶったためでしょう。
protected $listen = [];
こんな感じでリスナーから取り除いてしまえば
php artisan event:list
+---------------------------+---------------------------------------------+
| Event | Listeners |
+---------------------------+---------------------------------------------+
| App\Events\DiscoveryEvent | App\Listeners\DiscoveryEventListener@handle |
| App\Events\SomethingEvent | App\Listeners\SomethingEventListener@handle |
+---------------------------+---------------------------------------------+
こんな感じになります。
キャッシュする
アクセス頻度が高いサイトで、いちいちイベントディスカバリーの操作が走るのは精神衛生的によろしくないので、本番環境などではキャッシュさせちゃいましょう。
$ php artisan event:cache
Cached events cleared!
Events cached successfully!
このコマンドでキャッシュを作ると、イベントディスカバリーの処理は走らなくなりますので、新しくイベントを追加しても、イベントの追加はなされません。
本番以外ではこのコマンドは使わないようにするか、php artisan event:clear
コマンドでキャッシュを消すのが良いでしょう。
小ネタ
上のリストを見るとわかりますが、イベントに対するリスナーについては、ハンドラーメソッドまで指定されています。ハンドラーメソッドはその名称がhandle
で始まっていれば良いらしく、
public function handle2(DiscoveryEvent $event)
{
\Log::debug('ここにも届きました!');
}
こんなものを書いておくと、リストにも追加され、実行時のログも出ます。面白いっちゃ面白いんですが、使いすぎるとリスナーが変に太りまくる可能性があったり、リスナーの役割が不明瞭になるので、あまり乱用しないほうがいいでしょう。
イベントディスカバリーの欠点
欠点と言っていいのかどうかわかりませんが、複数のイベントに共通のリスナーを登録するということはできません。例えば、こんなやつです。
protected $listen = [
SomethingEvent::class => [
AllListener::class
],
DiscoveryEvent::class => [
AllListener::class
]
];
SomethingEvent
およびDiscoveryEvent
のどちらについても発火するとAllListener
のhandler
が実行されます。
一応、先の小ネタで書いたように、ハンドラーメソッドを変えれば、リスナークラスを複数のイベントに関連付けることはできますが、ハンドラーメソッド自体を共通化することは、イベントディスカバリーでは無理っぽいです。
まとめ
そういうわけで、マニュアルを読んでいたら目に入ったので、イベントディスカバリーとやらを調べてみました。
リスナーが監視するイベントが一つだけであるのならば、かなり有用ではないかと思います。
ただ、パッチバージョンで導入するのってどうなのかしらんって思った感じです。
今回はこんなところです。