Events - Laravel - The PHP Framework For Web Artisans
Laravelの公式ドキュメントのイベントの項目を参考に、動かしてみました。
目次
概要
Laravelのイベントシステムを使うと、様々なイベントを簡単に処理できます。アプリで発生するイベントに対する処理をリスナーとして登録しておくと、イベントを発生させることで、システムが登録されたリスナーを呼び出してくれます。一般的には、Eventクラスは app/Events ディレクトリー内に置き、リスナーは app/Listeners ディレクトリー内に置きます。これらのディレクトリーは最初は存在しません。 Artisan コマンドでイベントやリスナーを生成した時に自動的に作成されます。
イベントにより、複数の処理を切り離して実装することができます。一つのイベントは複数のリスナーを持つことができ、それらはお互いに依存しません。例えば、注文されたものを発送するたびに、ユーザーにSlackの通知を送信する場合を考えます。注文処理にSlackの通知のコードを組み込むこともできますが、OrderShippedイベントを発生させて、それを受け取ったリスナーがSlackの通知処理を行った方がシンプルです。
プロジェクトを準備
ログやエラー表示を実際に動かして、色々と試してみます。以下の手順で、プロジェクトを作成します。
- ターミナルを起動して、プロジェクトを作成したいフォルダーに移動する
- 環境を更新
composer global update npm update -g npm npm update -g
- 以下を実行して、Laravelプロジェクトを作成し、プロジェクトフォルダーに移動して、サーバーを起動する
laravel new laravel-event-test cd laravel-event-test php artisan serve
以上で準備完了です。Webブラウザを起動して、 http://localhost:8000 にアクセスすると、インストールしたLaravelプロジェクトが表示されれば成功です。
イベントの登録とリスナー
app/Providers/EventServiceProvider.php の $listen プロパティーに、アプリのすべてのイベントとリスナーを登録します。キーにはイベント、値にリスナーを配列で指定します。初期状態では、 SomeEvent というイベントに対して、 EventListener が登録されています。app/EventsフォルダーもApp/Listenersフォルダーも初期状態では存在しないので動作はしません。
protected $listen = [
'App\Events\SomeEvent' => [
'App\Listeners\EventListener',
],
];
イベントとリスナーを登録する
ここに、自前のイベントやリスナーを登録することができます。公式ドキュメントに従って OrderShipped イベントを作成してみましょう。
- app/Providers/EventServiceProvider.php を開く
- $listen プロパティーを以下のように修正
protected $listen = [
'App\Events\OrderShipped' => [
'App\Listeners\SendShipmentNotification',
],
];
イベントの定義
イベントクラスは、イベントに関する情報を保持するシンプルなデータコンテナです。先ほど作成した OrderShipped イベントに Eloquent ORM オブジェクトを持たせる例を以下に示します(公式ドキュメントのものは古いようで、useや継承などがうまく動作しませんでした)。
<?php namespace App\Events; use App\User; use Illuminate\Broadcasting\Channel; use Illuminate\Queue\SerializesModels; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class OrderShipped { use InteractsWithSockets, SerializesModels; public $user; /** * Create a new event instance. * * @return void */ public function __construct(User $user) { $this->user = $user; } /** * Get the channels the event should broadcast on. * * @return Channel|array */ public function broadcastOn() { return new PrivateChannel('channel-name'); } }
イベントクラスには処理はなく、データを保持しているだけです。 SerializesModels トレイトを利用することで、イベントオブジェクトが Eloquent モデルを保持することができます。
引き続き、リスナーや User モデルの生成を行います。
リスナーの定義
イベントリスナーは、 handle メソッドでイベントのインスタンスを受け取ります。 event:generate コマンドでイベントを生成すれば、適切なイベントクラスを組み込んで、 handle メソッドにコメントと引数が定義されます。 handle メソッド内で、イベントに対応するために必要な様々な処理を行います。
ここでは、イベントを受け取った時にユーザー名をログに出力するようにします。
<?php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Support\Facades\Log; class SendShipmentNotification { /** * Create the event listener. * * @return void */ public function __construct() { // } /** * Handle the event. * * @param OrderShipped $event * @return void */ public function handle(OrderShipped $event) { // Access the order using $event->user... Log::info('Event Fired : ' . $event->user->name); } }
リスナーの依存関係の解決と、イベントの伝播の停止
- リスナーの依存関係は、Laravelの service container により、自動的に解決されます
- 処理中のリスナーを終えたら、それ以降のイベント処理を停止したい場合があります(エラーが発生したなど)。そのような時は、 handle メソッドの戻り値を false にしてください。
データの準備
今回のサンプルを動かすには、User テーブルが必要なので生成します。
- .env を開く
- DB_ から始まるデータベースの設定を、各々の環境通りに設定する
- データベースに接続して、必要なデータベースを作成。以下、MySQLの手順
sudo mysql.server start mysql -u ユーザー名 -p
- パスワードを入力して、ログインする
- 以下でデータベースを作成
create database laravel_event_test;
exit
- MySQLから出たら、ターミナルで以下を実行する
php artisan migrate
エラーが表示されたら、データベースの作成ミスか、 .env の設定のミスなので、設定を見直してください。
イベントの発動(Firing Events)
イベントを発動させるには、 event ヘルパー関数にイベントのインスタンスを渡します。event 関数はグローバルに定義されているのでどこでも使えます。イベントを発動させると、登録されたリスナーが次々に呼び出されます。
ルートを呼び出したところで、イベントを発動させてみましょう。
- routes/web.php を開く
- getメソッドを以下のように修正する
Route::get('/', function () {
// テスト用のユーザーデータをファクトリーで作成
$user = factory(App\User::class)->create();
// イベントを生成
event(new OrderShipped($user));
// トップ画面を描画
return view('welcome');
});
上書き保存をしたら、 http://localhost:8000/ をWebブラウザーで開いてください。Laravelと表示されれば成功です。 storage/logs/laravel.log を開いて一番下を見ると、リスナーの handle メソッドが書き出した以下のようなログが追加されています。
[20xx-xx-xx xx:xx:xx] local.INFO: Event Fired : Dr. Mervin Schaefer IV
日付や名前は、実行するたびに異なります。
以上で、最低限のイベントやリスナーの登録と発動までできました。これ以降は、イベントをより便利に使うための情報です。
一つのクラスに複数のイベントを定義する(Event Subscribers)
Event Subscribers クラスを使うと、一つのクラスに複数のイベントを定義して、クラス内にイベントハンドラー(リスナー)を持たせることができます。 Subscriberクラスは、 subscribe メソッドを持ち、イベント dispatcher インスタンスが渡されます。渡された dispatcher インスタンスを使って、イベントのリスナーメソッドを呼び出すことができます。
公式ドキュメントでは、Subscriberは app/Listeners ディレクトリーに作成しているので、それに従って、2つのイベントハンドラーを持った UserEventSubscriber を作成してみます。
Subscriberを作成する
<?php namespace App\Listeners; use Illuminate\Support\Facades\Log; class UserEventSubscriber { /** * ユーザーのログインイベントを処理するハンドラー */ public function onUserLogin($event) { Log::info('ログイン : ' . $event->user->name); } /** * ユーザーのログアウトイベントを処理するハンドラー */ public function onUserLogout($event) { Log::info('ログアウト : ' . $event->user->name); } /** * Subscriberに、リスナーを登録する * * @param Illuminate\Events\Dispatcher $events */ public function subscribe($events) { $events->listen( 'App\Events\UserLogin', 'App\Listeners\UserEventSubscriber@onUserLogin' ); $events->listen( 'App\Events\UserLogout', 'App\Listeners\UserEventSubscriber@onUserLogout' ); } }
対応するイベントを作成する
イベントの内容は先に作成した OrderShipped.php と同じなので、複製して、クラス名だけ変更して利用します。
Subscriber を登録する
Subscriber を登録します。
- app/Providers/EventServiceProvider.php を開く
- 以下のプロパティーをクラス内に追加する
protected $subscribe = [ 'App\Listeners\UserEventSubscriber', ];
イベントを発動させるルートを作成する
- routes/web.php を開く
- 先頭の方に、以下の use を追加する
use App\Events\UserLogin; use App\Events\UserLogout;
- 以下の2つのルートを追加する
Route::get('/login', function () { // テスト用のユーザーデータをファクトリーで作成 $user = factory(App\User::class)->create(); // イベントを生成 event(new UserLogin($user)); // トップ画面を描画 return 'ログインしました。'; }); Route::get('/logout', function () { // テスト用のユーザーデータをファクトリーで作成 $user = factory(App\User::class)->create(); // イベントを生成 event(new UserLogout($user)); // トップ画面を描画 return 'ログアウトしました。'; });
以上ができたら、 http://localhost:8000/login をWebブラウザーで開いてください。「ログインしました。」と表示されたら、 storage/logs/laravel.log を開いて、以下のようなログが残っていることを確認してください(日付や名前は、毎回異なります)。
[2016-10-09 08:41:59] local.INFO: ログイン : Mrs. Marjorie Klein Jr.
次に、 http://localhost:8000/logout をWebブラウザーで開いてください。「ログアウトしました。」と表示されたら、先ほどと同じログファイルに、ログアウトのログが書き込まれます。
リスナーがそれほど規模が大きくなく、機能をまとめておきたい場合は、Subscriber を利用すると良いでしょう。
キューされたイベントリスナー(Queued Event Listeners)
e-mailを送ったり、HTTPリクエストを作成するなど、時間のかかるようなリスナーを実装する場合は、 キュー を使うリスナーが便利です。これを使う場合は、事前にキューの設定をして、 sync ドライバー以外の場合は、キューのdaemon を開始する必要があります。キューの記事はこちら(Laravel(5.3) のキューを試す)。
ローカルのテスト用キューである sync ドライバーでは同期処理になるので、本来のキューの目的である非同期処理は行われません。
リスナーをキューに登録させる
リスナーをキューに登録するには、 ShouldQueue インターフェースをリスナークラスに実装( implements )します。 event:generate を使って作成したリスナーには、あらかじめ ShouldQueue のネームスペースが定義されているので、すぐに利用することができます。
キューへのアクセス
リスナーをキューに登録した後、それが実行される前に削除や解放をしたい場合は、 Illuminate\Queue\InteractsWithQueue トレイトを利用します。このトレイトは、Artisanでqueue:generateした場合、自動的に設定されています。
hander メソッド内で、 $this->release(30); などのように、キューへの指示が可能になります。
詳細は、キューのドキュメントをご確認ください。 →
Queues - Laravel - The PHP Framework For Web Artisans