Laravel 8.x イベント

イントロダクション

Laravelのイベントは、単純なオブザーバーパターンの実装を提供し、アプリケーション内で発生するさまざまなイベントをサブスクライブしてリッスンできるようにします。イベントクラスは通常、app/Eventsディレクトリに保存し、リスナはapp/Listenersに保存します。Artisanコンソールコマンドを使用してイベントとリスナを生成すると、これらのディレクトリが作成されるため、アプリケーションにこれらのディレクトリが表示されていなくても心配ありません。

1つのイベントに、相互に依存しない複数のリスナを含めることができるため、イベントは、アプリケーションのさまざまな側面を分離するための優れた方法として機能します。たとえば、注文が発送されるたびにユーザーにSlack通知を送信したい場合があります。注文処理コードをSlack通知コードに結合する代わりに、リスナが受信してSlack通知をディスパッチするために使用できるApp\Events\OrderShippedイベントを発生させることができます。

イベントとリスナの登録

Laravelアプリケーションに含まれているApp\Providers\EventServiceProviderは、アプリケーションのすべてのイベントリスナを登録するための便利な場所を提供しています。listenプロパティには、すべてのイベント(キー)とそのリスナ(値)の配列が含まれています。アプリケーションが必要とするイベントをこの配列へ全部追加できます。例として、OrderShippedイベントを追加してみましょう。

use App\Events\OrderShipped;
use App\Listeners\SendShipmentNotification;

/**
 * アプリケーションのイベントリスナマッピング
 *
 * @var array
 */
protected $listen = [
    OrderShipped::class => [
        SendShipmentNotification::class,
    ],
];

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

イベントとリスナの生成

もちろん、各イベントとリスナのファイルを一つずつ手で生成するのは面倒です。代わりに、リスナとイベントをEventServiceProviderへ追加し、event:generate Artisanコマンドを使用してください。このコマンドは、EventServiceProviderにリストされているまだ存在しないイベントとリスナを生成します。

php artisan event:generate

もしくは、make:eventコマンドとmake:listener Artisanコマンドを使用して、個々のイベントとリスナを生成することもできます。

php artisan make:event PodcastProcessed

php artisan make:listener SendPodcastNotification --event=PodcastProcessed

イベントの手動登録

通常、イベントはEventServiceProvider$listen配列を介して登録する必要があります。ただし、EventServiceProviderbootメソッドでクラスまたはクロージャベースのイベントリスナを手動で登録することもできます。

use App\Events\PodcastProcessed;
use App\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;

/**
 * アプリケーションの他の全イベントの登録
 *
 * @return void
 */
public function boot()
{
    Event::listen(
        PodcastProcessed::class,
        [SendPodcastNotification::class, 'handle']
    );

    Event::listen(function (PodcastProcessed $event) {
        //
    });
}

Queueable匿名イベントリスナ

クロージャベースのイベントリスナを手動で登録する場合、リスナクロージャをIlluminate\Events\queueable関数内にラップして、キューを使用してリスナを実行するようにLaravelへ指示できます。

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

/**
 * アプリケーションの他の全イベントの登録
 *
 * @return void
 */
public function boot()
{
    Event::listen(queueable(function (PodcastProcessed $event) {
        //
    }));
}

キュー投入ジョブと同様に、onConnectiononQueuedelayメソッドを使用して、キュー投入するリスナの実行をカスタマイズできます。

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));

キューに投入した匿名リスナの失敗を処理したい場合は、queueableリスナを定義するときにcatchメソッドにクロージャを指定できます。このクロージャは、リスナの失敗の原因となったイベントインスタンスとThrowableインスタンスを受け取ります。

use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;

Event::listen(queueable(function (PodcastProcessed $event) {
    //
})->catch(function (PodcastProcessed $event, Throwable $e) {
    // キュー投入したリスナは失敗した…
}));

ワイルドカードイベントリスナ

ワイルドカードパラメータとして*を使用してリスナを登録することもでき、同じリスナで複数のイベントをキャッチできます。ワイルドカードリスナは、最初の引数としてイベント名を受け取り、2番目の引数としてイベントデータ配列全体を受け取ります。

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

イベントディスカバリー

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

Laravelは、PHPのリフレクションサービスを使用してリスナクラスをスキャンすることにより、イベントリスナを見つけます。Laravelがhandleで始まるリスナクラスメソッドを見つけると、Laravelはそれらのメソッドをメソッドの引数でタイプヒントされているイベントのイベントリスナとして登録します。

use App\Events\PodcastProcessed;

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

イベント検出はデフォルトで無効になっていますが、アプリケーションのEventServiceProvidershouldDiscoverEventsメソッドをオーバーライドすることで有効にできます。

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

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

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

実働環境でのイベント検出

本番環境では、フレームワークがすべてのリクエストですべてのリスナをスキャンするのは効率的ではありません。したがって、デプロイメントプロセス中に、event:cache Artisanコマンドを実行して、アプリケーションのすべてのイベントとリスナのマニフェストをキャッシュする必要があります。このマニフェストは、イベント登録プロセスを高速化するためにフレームワークか使用します。event:clearコマンドを使用してキャッシュを破棄できます。

イベント定義

イベントクラスは、基本的に、イベントに関連する情報を保持するデータコンテナです。たとえば、App\Events\OrderShippedイベントがEloquent ORMオブジェクトを受け取るとします。

<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * 注文インスタンス
     *
     * @var \App\Models\Order
     */
    public $order;

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

ご覧のとおり、このイベントクラスにはロジックが含まれていません。購読したApp\Models\Orderインスタンスのコンテナです。イベントで使用されるSerializesModelsトレイトは、キュー投入するリスナを利用する場合など、イベントオブジェクトがPHPのserialize関数を使用してシリアル化される場合、Eloquentモデルを適切にシリアル化します。

リスナ定義

次に、サンプルイベントのリスナを見てみましょう。イベントリスナは、handleメソッドでイベントインスタンスを受け取ります。event:generatemake:listener Artisanコマンドは、適切なイベントクラスを自動的にインポートし、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:generatemake:listener 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;
}

実行時にリスナのキュー接続またはキュー名を定義したい場合は、リスナにviaConnectionまたはviaQueueメソッドを定義します。

/**
 * リスナのキュー接続の名前の取得
 *
 * @return string
 */
public function viaConnection()
{
    return 'sqs';
}

/**
 * リスナのキュー名を取得
 *
 * @return string
 */
public function viaQueue()
{
    return 'listeners';
}

条件付き投入リスナ

場合によっては、実行時にのみ使用可能なデータに基づいて、リスナをキュー投入する必要があるかどうかを判断する必要が起きるでしょう。このために、shouldQueueメソッドをリスナに追加して、リスナをキュー投入する必要があるかどうかを判断できます。shouldQueueメソッドがfalseを返す場合、リスナは実行されません。

<?php

namespace App\Listeners;

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

class RewardGiftCard implements ShouldQueue
{
    /**
     * ギフトカードを顧客へ提供
     *
     * @param  \App\Events\OrderCreated  $event
     * @return void
     */
    public function handle(OrderCreated $event)
    {
        //
    }

    /**
     * リスナをキューへ投入するかを決定
     *
     * @param  \App\Events\OrderCreated  $event
     * @return bool
     */
    public function shouldQueue(OrderCreated $event)
    {
        return $event->order->subtotal >= 5000;
    }
}

キューの手動操作

リスナの基になるキュージョブのdeleteメソッドとreleaseメソッドへ手動でアクセスする必要がある場合は、Illuminate\Queue\InteractsWithQueueトレイトを使用してアクセスできます。このトレイトは、生成したリスナにはデフォルトでインポートされ、以下のメソッドへのアクセスを提供します。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

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

キュー投入するイベントリスナとデータベーストランザクション

キュー投入したリスナがデータベーストランザクション内でディスパッチされると、データベーストランザクションがコミットされる前にキューによって処理される場合があります。これが発生した場合、データベーストランザクション中にモデルまたはデータベースレコードに加えた更新は、データベースにまだ反映されていない可能性があります。さらに、トランザクション内で作成されたモデルまたはデータベースレコードは、データベースに存在しない可能性があります。リスナがこれらのモデルに依存している場合、キューに入れられたリスナをディスパッチするジョブの処理時に予期しないエラーが発生する可能性があります。

キュー接続のafter_commit設定オプションがfalseに設定されている場合でも、リスナクラスで$afterCommitプロパティを定義することにより、開いているすべてのデータベーストランザクションがコミットされた後に、特定のキューに入れられたリスナをディスパッチする必要があることを示すことができます。

<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    public $afterCommit = true;
}

Tip!! こうした問題の回避方法の詳細は、キュー投入されるジョブとデータベーストランザクションに関するドキュメントを確認してください。

失敗したジョブの処理

キュー投入したイベントリスナが失敗する場合があります。キュー投入したリスナがキューワーカーによって定義された最大試行回数を超えると、リスナ上のfailedメソッドが呼び出されます。failedメソッドは、失敗の原因となったイベントインスタンスとThrowableを受け取ります。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

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

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

キュー投入したリスナの最大試行回数の指定

キュー投入したリスナの1つでエラーが発生した場合、リスナが無期限に再試行し続けることを皆さんも望まないでしょう。そのため、Laravelはリスナを試行できる回数または期間を指定するさまざまな方法を提供しています。

リスナクラスで$trysプロパティを定義して、リスナが失敗したと見なされるまでに試行できる回数を指定できます。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    /**
     * キュー投入したリスナが試行される回数
     *
     * @var int
     */
    public $tries = 5;
}

リスナが失敗するまでに試行できる回数を定義する代わりに、リスナをそれ以上試行しない時間を定義することもできます。これにより、リスナは特定の時間枠内で何度でも試行します。リスナの試行最長時間を定義するには、リスナクラスにretryUntilメソッドを追加します。このメソッドはDateTimeインスタンスを返す必要があります:

/**
 * リスナタイムアウト時間を決定
 *
 * @return \DateTime
 */
public function retryUntil()
{
    return now()->addMinutes(5);
}

イベント発行

イベントをディスパッチするには、イベントで静的なdispatchメソッドを呼び出します。このメソッドはIlluminate\Foundation\Events\Dispatchableトレイトにより、イベントで使用可能になります。dispatchメソッドに渡された引数はすべて、イベントのコンストラクタへ渡されます。

<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Illuminate\Http\Request;

class OrderShipmentController extends Controller
{
    /**
     * 指定注文を発送
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $order = Order::findOrFail($request->order_id);

        // 注文出荷ロジック…

        OrderShipped::dispatch($order);
    }
}

Tip!! テスト時は、特定のイベントが実際にリスナを起動せずにディスパッチされたことを宣言できると役立つでしょう。Laravelの組み込みのテストヘルパで簡単にできます。

イベントサブスクライバ

イベントサブスクライバの記述

イベントサブスクライバは、サブスクライバクラス自体から複数のイベントを購読できるクラスであり、単一のクラス内で複数のイベントハンドラを定義できます。サブスクライバは、イベントディスパッチャーインスタンスを渡すsubscribeメソッドを定義する必要があります。特定のディスパッチャ上のlistenメソッドを呼び出して、イベントリスナを登録します。

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;

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

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

    /**
     * サブスクライバのリスナを登録
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     * @return void
     */
    public function subscribe($events)
    {
        $events->listen(
            Login::class,
            [UserEventSubscriber::class, 'handleUserLogin']
        );

        $events->listen(
            Logout::class,
            [UserEventSubscriber::class, 'handleUserLogout']
        );
    }
}

イベントリスナのメソッドがサブスクライバ自身の中で定義されている場合は、サブスクライバのsubscribeメソッドからメソッド名とイベントの配列を返す方が便利でしょう。Laravelはイベントリスナを登録する際に、サブスクライバのクラス名を自動的に決定します。

<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;

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

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

    /**
     * サブスクライバのリスナを登録
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     * @return array
     */
    public function subscribe($events)
    {
        return [
            Login::class => 'handleUserLogin',
            Logout::class => 'handleUserLogout',
        ];
    }
}

イベントサブスクライバの登録

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

<?php

namespace App\Providers;

use App\Listeners\UserEventSubscriber;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

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

    /**
     * 登録するサブスクライバクラス
     *
     * @var array
     */
    protected $subscribe = [
        UserEventSubscriber::class,
    ];
}

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュを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)へ移動

その他

?

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