イントロダクション

Laravelのイベントはシンプルなオブザーバの実装で、アプリケーションで発生する様々なイベントを購読し、リッスンするために使用します。イベントクラスは通常、app/Eventsディレクトリに保存されます。一方、リスナはapp/Listenersディレクトリへ保存されます。アプリケーションに両ディレクトリが存在しなくても、心配ありません。Artisanコンソールコマンドを使い、イベントとリスナを生成するとき、ディレクトリも生成されます。

一つのイベントは、互いに依存していない複数のリスナに紐付けられますので、アプリケーションの様々な側面を独立させるための良い手段として、イベントを利用できます。たとえば、注文を配送するごとにSlack通知をユーザへ届けたいとします。注文の処理コードとSlackの通知コードを結合する代わりに、シンプルにOrderShippedイベントを発行し、リスナがそれを受け取り、Slack通知へ変換するように実装できます。

イベント/リスナ登録

Laravelアプリケーションに含まれているEventServiceProviderは、イベントリスナを全て登録するために便利な場所を提供しています。listenプロパティは全イベント(キー)とリスナ(値)で構成されている配列です。もちろん、アプリケーションで必要とされているイベントをこの配列に好きなだけ追加できます。たとえばOrderShippedイベントを追加してみましょう。

/**
 * アプリケーションのイベントリスナをマップ
 *
 * @var array
 */
protected $listen = [
    'App\Events\OrderShipped' => [
        'App\Listeners\SendShipmentNotification',
    ],
];

イベント/リスナ生成

毎回ハンドラやリスナを作成するのは、当然のことながら手間がかかります。代わりにハンドラとリスナをEventServiceProviderに追加し、event:generateコマンドを使いましょう。このコマンドはEventServiceProviderにリストしてあるイベントやリスナを生成してくれます。既存のイベントとハンドラには、もちろん変更を加えません。

php artisan event:generate

イベントの手動登録

通常イベントは、EventServiceProvider$listen配列により登録するべきです。しかし、EventServiceProviderbootメソッドの中で、クロージャベースリスナを登録することができます。

/**
 * アプリケーションの他のイベントを登録する
 *
 * @return void
 */
public function boot()
{
    parent::boot();

    Event::listen('event.name', function ($foo, $bar) {
        //
    });
}

ワイルドカードリスナ

登録したリスナが、*をワイルドカードパラメータとして使用している場合、同じリスナで複数のイベントを捕捉できます。ワイルドカードリスナは、イベント全体のデータ配列を最初の引数で受け取ります。

Event::listen('event.*', function (array $data) {
    //
});

イベント定義

イベントクラスはシンプルなデータコンテナで、イベントに関する情報を保持します。たとえば生成したOrderShippedイベントがEloquent ORMオブジェクトを受け取るとしましょう。

<?php

namespace App\Events;

use App\Order;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use SerializesModels;

    public $order;

    /**
     * 新しいイベントインスタンスの生成
     *
     * @param  Order  $order
     * @return void
     */
    public function __construct(Order $order)
    {
        $this->order = $order;
    }
}

ご覧の通り、このクラスはロジックを含みません。購入されたOrderオブジェクトのための、単なるコンテナに過ぎません。イベントオブジェクトがPHPのserialize関数でシリアライズされる場合でも、EloquentモデルをイベントがuseしているSerializesModelsトレイトが優雅にシリアライズします。

リスナの定義

次にサンプルイベントのリスナを取り上げましょう。イベントリスナはイベントインスタンスをhandleメソッドで受け取ります。event:generateコマンドは自動的に適切なイベントクラスをインポートし、handleメソッドのイベントのタイプヒントを指定します。そのイベントに対応するために必要なロジックをhandleメソッドで実行してください。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendShipmentNotification
{
    /**
     * イベントリスナ生成
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * イベントの処理
     *
     * @param  OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        // $event->orderにより、注文へアクセス…
    }
}

Tip!! イベントリスナでも、必要な依存をコンストラクターのタイプヒントで指定できます。イベントリスナは全てLaravelのサービスコンテナで依存解決されるので、依存は自動的に注入されます。

イベントの伝播の停止

場合によりイベントが他のリスナへ伝播されるのを止めたいこともあります。その場合はhandleメソッドからfalseを返してください。

イベントリスナのキュー投入

メール送信やHTTPリクエストを作成するなど、遅い仕事を担当する場合、そのリスナをキューイングできると便利です。キューリスナに取り掛かる前に、キューの設定を確実に行い、サーバかローカル開発環境でキューリスナを起動しておいてください。

リスナをキュー投入するように指定するには、ShouldQueueインターフェイスをリスなクラスに追加します。event:generate Artisanコマンドにより生成したリスナには、既にこのインターフェイスが現在の名前空間下にインポートされていますので、すぐに使用できます。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    //
}

これだけです!これでこのリスナがイベントのために呼び出されると、Laravelのキューシステムを使い、イベントデスパッチャーにより自動的にキューへ投入されます。キューにより実行されるリスナから例外が投げられなければ、そのキュージョブは処理が済み次第、自動的に削除されます。

キューへの任意アクセス

リスナの裏で動作しているキュージョブの、deletereleaseメソッドを直接呼び出したければ、Illuminate\Queue\InteractsWithQueueトレイトを使えます。このトレイトは生成されたり砂にはデフォルトとしてインポートされており、これらのメソッドへアクセスできるようになっています。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    public function handle(OrderShipped $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

イベント発行

イベントを発行するには、eventヘルパにそのイベントインスタンスを渡してください。このヘルパはイベントを登録している全リスナに対してディスパッチします。eventヘルパはグローバルに使用できますので、アプリケーションのどこからでも呼び出せます。

<?php

namespace App\Http\Controllers;

use App\Order;
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;

class OrderController extends Controller
{
    /**
     * 指定した注文を発送
     *
     * @param  int  $orderId
     * @return Response
     */
    public function ship($orderId)
    {
        $order = Order::findOrFail($orderId);

        // 注文発送ロジック…

        event(new OrderShipped($order));
    }
}

Tip!! テスト時には、実際にリスナを起動せずに、イベントが発行されたかをアサートできるようになっています。Laravelの組み込みテストヘルパにより、簡単にテストできます。

イベント

イベント購読プログラミング

イベント購読クラスは、その内部で複数のイベントを購読でき、一つのクラスで複数のイベントハンドラを定義できます。購読クラスは、イベントディスパッチャインスタンスを受け取る、subscribeメソッドを定義する必要があります。イベントリスナを登録するには、渡されたディスパッチャのlistenメソッドを呼び出します。

<?php

namespace App\Listeners;

class UserEventSubscriber
{
    /**
     * ユーザログインイベント処理
     */
    public function onUserLogin($event) {}

    /**
     * ユーザログアウトイベント処理
     */
    public function onUserLogout($event) {}

    /**
     * 購読するリスナの登録
     *
     * @param  Illuminate\Events\Dispatcher  $events
     */
    public function subscribe($events)
    {
        $events->listen(
            'Illuminate\Auth\Events\Login',
            'App\Listeners\UserEventSubscriber@onUserLogin'
        );

        $events->listen(
            'Illuminate\Auth\Events\Logout',
            'App\Listeners\UserEventSubscriber@onUserLogout'
        );
    }

}

イベント購読登録

購読クラスを書いたら、イベントディスパッチャへ登録できる準備が整いました。EventServiceProvider$subscribeプロパティを使用し、購読クラスを登録します。例として、UserEventSubscriberをリストに追加してみましょう。

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * アプリケーションのイベントリスナをマップ
     *
     * @var array
     */
    protected $listen = [
        //
    ];

    /**
     * 登録する購読クラス
     *
     * @var array
     */
    protected $subscribe = [
        'App\Listeners\UserEventSubscriber',
    ];
}