Laravel 5.8 イベント

イントロダクション

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) {
        //
    });
}

ワイルドカードリスナ

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

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

イベントディスカバリ

Note: イベントディスカバリは、Laravel5.8.9以降で利用できます。

EventServiceProvider$listen配列へ、自分でイベントとリスナを登録する代わりに、自動的にイベントを検出させることができます。イベントディスカバリを有効にすると、LaravelはアプリケーションのListenersディレクトリをスキャンし、自動的にイベントとリスナを見つけ出して登録します。さらに、EventServiceProviderで明示的に定義されたイベントリストも今まで通りに登録します。

Laravelはリフレクションを使いリスナクラスをスキャンし、イベントリスナを見つけます。Laravelはhandleで始まるイベントリスナクラスメソッドを見つけると、そのメソッド引数のタイプヒントで示すイベントに対する、イベントリスナとしてメソッドを登録します。

use App\Events\PodcastProcessed;

class SendPodcastProcessedNotification
{
    /**
     * 指定イベントの処理
     *
     * @param  \App\Events\PodcastProcessed
     * @return void
     */
    public function handle(PodcastProcessed $event)
    {
        //
    }
}

イベントディスカバリはデフォルトで無効になっています。アプリケーションのEventServiceProviderにあるshouldDiscoverEventsをオーバーライドすることで、有効にできます。

/**
 * イベントとリスナを自動的に検出するか指定
 *
 * @return bool
 */
public function shouldDiscoverEvents()
{
    return true;
}

アプリケーションのListenersディレクトリ中の全リスナが、デフォルトでスキャンされます。スキャンする追加のディレクトリを定義したい場合は、EventServiceProviderdiscoverEventsWithinをオーバーライドしてください。

/**
 * イベントを見つけるために使用するリスナディレクトリの取得
 *
 * @return array
 */
protected function discoverEventsWithin()
{
    return [
        $this->app->path('Listeners'),
    ];
}

実働時はリクエストのたびに、すべてのリスナをフレームワークにスキャンさせるのは好ましくないでしょう。アプリケーションのイベントとリスナの全目録をキャッシュする、event:cache Artisanコマンドを実行すべきです。この目録はフレームワークによるイベント登録処理をスピードアップするために使用されます。event:clearコマンドにより、このキャッシュは破棄されます。

Tip!! event:listコマンドで、アプリケーションに登録されたすべてのイベントとリスナを一覧表示できます。

イベント定義

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

<?php

namespace App\Events;

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

class OrderShipped
{
    use SerializesModels;

    public $order;

    /**
     * 新しいイベントインスタンスの生成
     *
     * @param  \App\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  \App\Events\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のキューシステムを使い、イベントデスパッチャーにより自動的にキューへ投入されます。キューにより実行されるリスナから例外が投げられなければ、そのキュージョブは処理が済み次第、自動的に削除されます。

キュー接続とキュー名のカスタマイズ

イベントリスナのキュー接続とキュー名、イベントリスナのキュー遅延時間をカスタマイズしたい場合は、$connection$queue$delayプロパティをリスナクラスで定義します。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    /**
     * ジョブを投入する接続名
     *
     * @var string|null
     */
    public $connection = 'sqs';

    /**
     * ジョブを投入するキュー名
     *
     * @var string|null
     */
    public $queue = 'listeners';

    /**
     * ジョブが処理開始されるまでの時間(秒)
     *
     * @var int
     */
    public $delay = 60;
}

条件付きリスナのキュー投入

あるデータが存在する場合のみ、実行時にリスナをキューすると判断する必要が起きる場合もあります。そのためにはshouldQueueメソッドをリスナへ追加し、そのリスナがキューされ同期的に実行されるかどうかを決めます。

<?php

namespace App\Listeners;

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

class RewardGiftCard implements ShouldQueue
{
    /**
     * 顧客にギフトカードを贈る
     *
     * @param  \App\Events\OrderPlaced  $event
     * @return void
     */
    public function handle(OrderPlaced $event)
    {
        //
    }

    /**
     * リスナがキューされるかどうかを決める
     *
     * @param  \App\Events\OrderPlaced  $event
     * @return bool
     */
    public function shouldQueue(OrderPlaced $event)
    {
        return $event->order->subtotal >= 5000;
    }
}

キューへの任意アクセス

リスナの裏で動作しているキュージョブの、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;

    /**
     * イベントの処理
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        if (true) {
            $this->release(30);
        }
    }
}

失敗したジョブの取り扱い

時々、キュー投入したイベントリスナが落ちることがあります。キューワーカにより定義された最大試行回数を超え、キュー済みのリスナが実行されると、リスナのfailedメソッドが実行されます。failedメソッドはイベントインスタンスと落ちた原因の例外を引数に受け取ります。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    /**
     * イベントの処理
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */
    public function handle(OrderShipped $event)
    {
        //
    }

    /**
     * 失敗したジョブの処理
     *
     * @param  \App\Events\OrderShipped  $event
     * @param  \Exception  $exception
     * @return void
     */
    public function failed(OrderShipped $event, $exception)
    {
        //
    }
}

イベントの発行

イベントを発行するには、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 handleUserLogin($event) {}

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

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

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

イベント購読登録

購読クラスを書いたら、イベントディスパッチャへ登録できる準備が整いました。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',
    ];
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる