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',
],
];
イベントとリスナーの作成
自分でフォルダーやファイルを作成して、イベントやリスナーを作成することもできますが、 artisan の event:generate コマンドを呼び出せば、先ほど $listen に登録したイベントとリスナーのファイルを自動的に生成してくれます。ターミナルで、以下を実行してください。
php artisan event:generate
app/Events/OrderShipped.php と app/Listeners/SendShipmentNotification.php が生成されます。すでにファイルが作成済みの場合は何もしません。
イベントの定義
イベントクラスは、イベントに関する情報を保持するシンプルなデータコンテナです。先ほど作成した 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;
@return
public function __construct(User $user)
{
$this->user = $user;
}
@return
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
{
@return
public function __construct()
{
}
@param
@return
public function handle(OrderShipped $event)
{
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
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を作成する
- app/Listeners ディレクトリー内に UserEventSubscriber.php を作成する
- 作成したファイルを開いて、以下のコードを入力する
<?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);
}
@param
public function subscribe($events)
{
$events->listen(
'App\Events\UserLogin',
'App\Listeners\UserEventSubscriber@onUserLogin'
);
$events->listen(
'App\Events\UserLogout',
'App\Listeners\UserEventSubscriber@onUserLogout'
);
}
}
対応するイベントを作成する
イベントの内容は先に作成した OrderShipped.php と同じなので、複製して、クラス名だけ変更して利用します。
- app/Events/OrderShipped.php を同じフォルダーに複製する
- ファイル名を UserLogin.php に変更する
- UserLogin.php を開いて、クラス名を UserLogin に変更する
- 同様に、 app/Events/OrderShipped.php を同じフォルダーに複製する
- ファイル名を UserLogout.php に変更する
- UserLogout.php を開いて、クラス名を UserLogout に変更する
Subscriber を登録する
Subscriber を登録します。
- app/Providers/EventServiceProvider.php を開く
- 以下のプロパティーをクラス内に追加する
protected $subscribe = [
'App\Listeners\UserEventSubscriber',
];
イベントを発動させるルートを作成する
- routes/web.php を開く
- 先頭の方に、以下の use を追加する
use App\Events\UserLogin;
use App\Events\UserLogout;
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