イントロダクション
Laravelのイベント機能はオブザーバーのシンプルな実装を提供します。アプリケーションの中でイベントを発行し、購読するために使用します。イベントクラスは通常app/Events
ディレクトリーへ保存され、リスナーはapp/Listener
に設置します。
イベント/リスナー登録
Laravelアプリケーションに含まれているEventServiceProvider
は、イベントハンドラーを全て登録するために便利な手段を提供しています。listen
プロパティは全イベント(キー)とリスナー(値)で構成されている配列です。もちろん、アプリケーションで必要とされているイベントをこの配列に好きなだけ追加できます。たとえばPodcastWasPurchased
イベントを追加してみましょう。
/**
* アプリケーションのイベントリスナーをマップ
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
イベントとリスナークラス生成
もちろん毎回ハンドラーやリスナーを作成するのは手間がかかります。代わりにハンドラーとリスナーをEventServiceProvider
に追加し、event:generate
コマンドを使いましょう。このコマンドはEventServiceProvider
にリストしてあるイベントやリスナーを生成してくれます。既存のイベントとハンドラーには当然変更を加えません。
php artisan event:generate
イベントを自分で登録する
通常、イベントはEventServiceProvider
の$listen
配列を使用し登録すべきです。しかし、Event
ファサードやIlluminate\Contracts\Events\Dispatcher
契約の実装を使用し、イベントディスパッチャーでイベントを自分で登録することもできます。
/**
* アプリケーションのその他のイベントを登録
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('event.name', function ($foo, $bar) {
//
});
}
ワイルドカードイベントリスナー
*
をワイルドカードとしてリスナーを登録することができ、同じリスナーで複数のイベントを補足することが可能です。ワイルドカードリスナーは一つの引数により、イベントデータ全体の配列を受け取ります。
$events->listen('event.*', function (array $data) {
//
});
イベント定義
イベントクラスはシンプルなデータコンテナで、イベントに関する情報を保持します。たとえば生成したPodcastWasPurchased
イベントがEloquent
ORMオブジェクトを受け取るとしましょう。
<?php
namespace App\Events;
use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class PodcastWasPurchased extends Event
{
use SerializesModels;
public $podcast;
/**
* 新しいイベントインスタンスの生成
*
* @param Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
}
ご覧の通り、このクラスは特別なロジックを含みません。購入されたPodcast
オブジェクトのコンテナに過ぎません。イベントオブジェクトがPHPのserialize
関数でシリアライズされる場合でも、EloquentモデルはSerializesModels
トレイトが優雅にシリアライズします。
リスナーの定義
次にサンプルイベントのリスナーを取り上げましょう。イベントリスナーはイベントインスタンスをhandle
メソッドで受け取ります。event:generate
コマンドは自動的に対応するイベントクラスをインポートし、handle
メソッドのイベントのタイプヒントを行います。そのイベントに対応するために必要なロジックを実行してください。
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation
{
/**
* イベントリスナー生成
*
* @return void
*/
public function __construct()
{
//
}
/**
* イベントの処理
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// $event->podcastでポッドキャストへアクセス…
}
}
イベントリスナーは必要な依存をコンストラクターのタイプヒントで指定できます。イベントリスナーは全てLaravelのサービスコンテナで依存解決されるので、依存は自動的に注入されます。
use Illuminate\Contracts\Mail\Mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
イベントの伝播の停止
場合によりイベントが他のリスナーへ伝播されるのを止めたいこともあります。その場合はhandle
メソッドからfalse
を返してください。
イベントリスナーのキュー投入
イベントハンドラーをキューに投入する必要があるのですか? これ以上ないくらい簡単です。リスナークラスにShouldQueue
インターフェイスを追加するだけです。リスナーがevent:generate
Artisanコマンドにより生成されている場合は現在の名前空間にこのインターフェイスがインポートされていますので、すぐに使えます。
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue
{
//
}
これだけです!これでこのハンドラーがイベントのために呼び出されると、Laravelのキューシステムを使い、イベントデスパッチャーにより自動的にキューへ投入されます。キューにより実行されるリスナーから例外が投げられなければ、そのキュージョブは処理が済んだら自動的に削除されます。
キューへのアクセス
裏で動作しているキュージョブのdelete
やrelease
メソッドを直接呼び出したければ可能です。生成されたリスナーではデフォルトでインポートされているIlluminate\Queue\InteractsWithQueue
トレイトを呼び出してください。両メソッドへのアクセスを提供します。
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue
{
use InteractsWithQueue;
public function handle(PodcastWasPurchased $event)
{
if (true) {
$this->release(30);
}
}
}
イベント発行
イベントを発行するにはEvent
ファサードを使用し、fire
メソッドにイベントのインスタンスを渡してください。fire
メソッドはそのイベントを登録されているリスナー全部へディスパッチします。
<?php
namespace App\Http\Controllers;
use Event;
use App\Podcast;
use App\Events\PodcastWasPurchased;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 指定されたユーザーのプロフィール表示
*
* @param int $userId
* @param int $podcastId
* @return Response
*/
public function purchasePodcast($userId, $podcastId)
{
$podcast = Podcast::findOrFail($podcastId);
// ポッドキャスト購入ロジック…
Event::fire(new PodcastWasPurchased($podcast));
}
}
もしくは、グローバルなevent
ヘルパ関数でイベントを発行します。
event(new PodcastWasPurchased($podcast));
ブロードキャストイベント
多くの近代的なアプリケーションでは、リアルタイムでライブ更新されるユーザーインターフェイスを実装するために、Webソケットを利用しています。サーバーであるデータが更新されたら、処理するクライアントへWebソケット接続を通じて送られます。
こうしたタイプのアプリケーション構築を援助するため、LaravelはイベントをWebソケット接続を通じて簡単に「ブロードキャスト」できます。Laravelのイベントをブロードキャストすることで、サーバーサイドのコードとクライアントサイドのJavaScriptで同じ名前のイベントを共有できるようになります。
設定
イベントブロードキャストの設定オプションはconfig/broadcasting.php
設定ファイルの中にあります。Laravelはいくつかのドライバーを用意しており、PusherやRedis、それにローカルの開発とデバッグのためのlog
ドライバーがあります。
ブロードキャスト事前要件
以下の依存パッケージがイベントのブロードキャストに必要です。
- Pusher:
pusher/pusher-php-server ~2.0
- Redis:
predis/predis ~1.0
動作要件:キュー
イベントをブロードキャストする前に、キューリスナーを設定し動かしておく必要があります。全てのイベントブロードキャストはキュー投入されるジョブとして動きますので、アプリケーションの反応時間にシリアスな影響を与えません。
ブロードキャストイベント作成
Laravelにイベントがブロードキャストされることを知らせるためにIlluminate\Contracts\Broadcasting\ShouldBroadcast
インターフェイスを実装してください。ShouldBroadcast
インターフェイスはbroadcastOn
メソッドの実装ひとつだけを求めます。broadcastOn
メソッドはブロードキャストされる「チャンネル」名の配列を返す必要があります。
<?php
namespace App\Events;
use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ServerCreated extends Event implements ShouldBroadcast
{
use SerializesModels;
public $user;
/**
* イベントインスタンスの生成
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* イベントをブロードキャストするチャンネル
*
* @return array
*/
public function broadcastOn()
{
return ['user.'.$this->user->id];
}
}
それから、通常通りにイベントを発行するだけです。イベントが発行されるとキュージョブで指定されたブロードキャストドライバーにより自動的にブロードキャストされます。
ブロードキャストイベント名のオーバーライド
ブロードキャストイベント名は、デフォルトでそのイベントの完全な名前空間名になります。上の例のクラスでは、ブロードキャストイベントは、App\Events\ServerCreated
になります。broadcastAs
メソッドにより、このブロードキャストイベント名を好きなようにカスタマイズできます。
/**
* ブロードキャストイベント名の取得
*
* @return string
*/
public function broadcastAs()
{
return 'app.server-created';
}
ブロードキャストデータ
イベントをブロードキャストする時、全public
プロパティーは自動的にシリアライズされ、イベントの本体(ペイロード)としてブロードキャストされます。それによりJavaScriptアプリケーションでパブリックデータへアクセスできるようになります。たとえばイベントにEloquentモデルのpublicの$user
プロパティがあったとすると、ブロードキャストの本体は次のようになります。
{
"user": {
"id": 1,
"name": "Jonathan Banks"
...
}
}
しかしブロードキャストされる本体をより良く調整、コントロールしたければ、broadcastWith
メソッドをイベントに追加してください。このメソッドは、そのイベントでブロードキャストしたいデータの配列を返す必要があります。
/**
* ブロードキャストするデータ取得
*
* @return array
*/
public function broadcastWith()
{
return ['user' => $this->user->id];
}
イベントブロードキャストの利用
Pusher
PusherのJacaScript
SDKを使い、Pusherドライバーでイベントブロードキャストを便利に使いたいと思うでしょう。たとえば前例のApp\Events\ServerCreated
イベントを使ってみましょう。
this.pusher = new Pusher('pusher-key');
this.pusherChannel = this.pusher.subscribe('user.' + USER_ID);
this.pusherChannel.bind('App\Events\ServerCreated', function(message) {
console.log(message.user);
});
Redis
Redisブロードキャストを使用する場合、メッセージを受け取るために自分でRedisのpub/subコンシューマーを書く必要があり、自分で選んだWebソケットのテクノロジーを使いブロードキャストしなくてはなりません。たとえばNodeで書かれ人気のあるSocket.ioライブラリーを使うことを選択できます。
socket.io
とioredis
Nodeライブラリーを使い、Laravelアプリケーションからブロードキャストされた全イベントを発行するイベントブロードキャスターを簡単に書けます。
var app = require('http').createServer(handler);
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
app.listen(6001, function() {
console.log('Server is running!');
});
function handler(req, res) {
res.writeHead(200);
res.end('');
}
io.on('connection', function(socket) {
//
});
redis.psubscribe('*', function(err, count) {
//
});
redis.on('pmessage', function(subscribed, channel, message) {
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
イベント購読
イベント購読クラスは一つのクラスで多くのイベントを購読するためのものです。購読クラスはイベントデスパッチャーインスタンスが渡されるsubscribe
メソッドを実装しなくてはなりません
<?php
namespace App\Listeners;
class UserEventListener
{
/**
* ユーザーログインイベントの処理
*/
public function onUserLogin($event) {}
/**
* ユーザーログアウトイベントの処理
*/
public function onUserLogout($event) {}
/**
* イベント購入リスナーの登録
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'App\Events\UserLoggedIn',
'App\Listeners\UserEventListener@onUserLogin'
);
$events->listen(
'App\Events\UserLoggedOut',
'App\Listeners\UserEventListener@onUserLogout'
);
}
}
イベント購読クラスの登録
購読クラスを定義したらイベントディスパッチャーに登録します。EventServiceProvider
の$subscribe
プロパティを使い、後続クラスを登録します。たとえばUserEventListener
を追加してみましょう。
<?php
namespace App\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* アプリケーションのイベントリスナーをマップ
*
* @var array
*/
protected $listen = [
//
];
/**
* 登録する購読クラス
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventListener',
];
}
フレームワークのイベント
Laravelはフレームワークが実行するアクションに対して発行する、様々な「コア」イベントを提供しています。皆さんが独自に用意するカスタムイベントと同じ方法で、これらのコアイベントも購入できます。
イベント | パラメータ |
---|---|
artisan.start | $application |
auth.attempt | $credentials, $remember, $login |
auth.login | $user, $remember |
auth.logout | $user |
cache.missed | $key |
cache.hit | $key, $value |
cache.write | $key, $value, $minutes |
cache.delete | $key |
connection.{name}.beganTransaction | $connection |
connection.{name}.committed | $connection |
connection.{name}.rollingBack | $connection |
illuminate.query | $query, $bindings, $time, $connectionName |
illuminate.queue.after | $connection, $job, $data |
illuminate.queue.failed | $connection, $job, $data |
illuminate.queue.stopping | null |
mailer.sending | $message |
router.matched | $route, $request |
composing:{view name} | $view |
creating:{view name} | $view |