メインコンテンツまでスキップ
バージョン: 13.x

Events

Introduction

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

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

Generating Events and Listeners

イベントとリスナを迅速に生成するには、make:event および make:listener Artisan コマンドを使用できます。

php artisan make:event PodcastProcessed

php artisan make:listener SendPodcastNotification --event=PodcastProcessed

便宜上、追加の引数なしで make:event および make:listener Artisan コマンドを呼び出すこともできます。これを行うと、Laravel はクラス名と、リスナの作成時にリッスンするイベントの入力を自動的に求めます。

php artisan make:event

php artisan make:listener

Registering Events and Listeners

Event Discovery

デフォルトでは、Laravel はアプリケーションの Listeners ディレクトリをスキャンすることにより、イベントリスナを自動的に検索して登録します。 Laravel が handle または __invoke で始まるリスナ クラス メソッドを見つけると、Laravel はそれらのメソッドを、メソッドのシグネチャでタイプヒントされているイベントのイベント リスナとして登録します。

use App\Events\PodcastProcessed;

class SendPodcastNotification
{
/**
* Handle the event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}
}

PHP の共用体タイプを使用して複数のイベントをリッスンすることができます。

/**
* Handle the event.
*/
public function handle(PodcastProcessed|PodcastPublished $event): void
{
// ...
}

リスナを別のディレクトリまたは複数のディレクトリ内に保存する予定がある場合は、アプリケーションの bootstrap/app.php ファイル内の withEvents メソッドを使用して、これらのディレクトリをスキャンするように Laravel に指示できます。

->withEvents(discover: [
__DIR__.'/../app/Domain/Orders/Listeners',
])

* 文字をワイルドカードとして使用して、複数の同様のディレクトリでリスナをスキャンできます。

->withEvents(discover: [
__DIR__.'/../app/Domain/*/Listeners',
])

event:list コマンドを使用すると、アプリケーション内に登録されているすべてのリスナを一覧表示できます。

php artisan event:list

Event Discovery in Production

アプリケーションの速度を向上させるには、optimize または event:cache Artisan コマンドを使用して、アプリケーションのすべてのリスナのマニフェストをキャッシュする必要があります。通常、このコマンドはアプリケーションの deployment process の一部として実行する必要があります。このマニフェストは、イベント登録プロセスを高速化するためにフレームワークによって使用されます。 event:clear コマンドを使用してイベント キャッシュを破棄することができます。

Dynamic Event Discovery

特定のリスナを検出するかどうかを動的に制御するには、リスナクラスに ShouldBeDiscovered インターフェースを実装し、真偽値を返す shouldBeDiscovered メソッドを定義します。このメソッドが false を返すと、そのリスナはイベント検出時に登録されません。

use Illuminate\Contracts\Events\ShouldBeDiscovered;

class SendPodcastNotification implements ShouldBeDiscovered
{
/**
* Handle the event.
*/
public function handle(PodcastProcessed $event): void
{
// ...
}

/**
* Determine if the listener should be discovered.
*/
public static function shouldBeDiscovered(): bool
{
return app()->environment('production');
}
}

Manually Registering Events

Event ファサードを使用すると、アプリケーションの AppServiceProviderboot メソッド内でイベントとそれに対応するリスナを手動で登録できます。

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

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(
PodcastProcessed::class,
SendPodcastNotification::class,
);
}

event:list コマンドを使用すると、アプリケーション内に登録されているすべてのリスナを一覧表示できます。

php artisan event:list

Closure Listeners

通常、リスナはクラスとして定義されます。ただし、アプリケーションの AppServiceProviderboot メソッドでクロージャ ベースのイベント リスナを手動で登録することもできます。

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

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(function (PodcastProcessed $event) {
// ...
});
}

Queueable Anonymous Event Listeners

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

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

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(queueable(function (PodcastProcessed $event) {
// ...
}));
}

キューに入れられたジョブと同様に、onConnectiononQueue、および delay メソッドを使用して、キューに入れられたリスナの実行をカスタマイズできます。

Event::listen(queueable(function (PodcastProcessed $event) {
// ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->plus(seconds: 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) {
// The queued listener failed...
}));

Wildcard Event Listeners

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

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

Defining Events

イベント クラスは本質的に、イベントに関連する情報を保持するデータ コンテナーです。たとえば、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;

/**
* Create a new event instance.
*/
public function __construct(
public Order $order,
) {}
}

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

Defining Listeners

次に、サンプル イベントのリスナを見てみましょう。イベント リスナは、handle メソッドでイベント インスタンスを受け取ります。 make:listener Artisan コマンドは、--event オプションを指定して呼び出すと、適切なイベント クラスを自動的にインポートし、handle メソッドでイベントをタイプヒントします。 handle メソッド内で、イベントに応答するために必要なアクションを実行できます。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendShipmentNotification
{
/**
* Create the event listener.
*/
public function __construct() {}

/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Access the order using $event->order...
}
}

イベント リスナは、コンストラクターに必要な依存関係をタイプヒントで示すこともできます。すべてのイベント リスナは Laravel service container 経由で解決されるため、依存関係は自動的に挿入されます。

Stopping The Propagation Of An Event

場合によっては、他のリスナへのイベントの伝播を停止したい場合があります。これを行うには、リスナの handle メソッドから false を返します。

Queued Event Listeners

リスナのキューイングは、リスナが電子メールの送信や HTTP リクエストの作成などの遅いタスクを実行する場合に有益です。キュー リスナを使用する前に、必ず configure your queue を実行し、サーバーまたはローカル開発環境でキューワーカーを起動してください。

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

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
// ...
}

それでおしまい!このリスナによって処理されるイベントがディスパッチされると、リスナは Laravel の queue system を使用するイベント ディスパッチャーによって自動的にキューに入れられます。リスナがキューによって実行されたときに例外がスローされなかった場合、キューに入れられたジョブは処理終了後に自動的に削除されます。

Customizing The Queue Connection, Name, & Delay

イベント リスナのキュー接続、キュー名、またはキュー遅延時間をカスタマイズしたい場合は、リスナ クラスで ConnectionQueue、および Delay 属性を使用できます。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;

#[Connection('sqs')]
#[Queue('listeners')]
#[Delay(60)]
class SendShipmentNotification implements ShouldQueue
{
// ...
}

リスナのキュー接続、キュー名、または実行時の遅延を定義したい場合は、リスナで viaConnectionviaQueue、または withDelay メソッドを定義できます。

/**
* Get the name of the listener's queue connection.
*/
public function viaConnection(): string
{
return 'sqs';
}

/**
* Get the name of the listener's queue.
*/
public function viaQueue(): string
{
return 'listeners';
}

/**
* Get the number of seconds before the job should be processed.
*/
public function withDelay(OrderShipped $event): int
{
return $event->highPriority ? 0 : 60;
}

Conditionally Queueing Listeners

場合によっては、実行時にのみ使用できるデータに基づいて、リスナをキューに入れる必要があるかどうかを決定する必要がある場合があります。これを実現するには、shouldQueue メソッドをリスナに追加して、リスナをキューに入れる必要があるかどうかを決定できます。 shouldQueue メソッドが false を返す場合、リスナはキューに入れられません。

<?php

namespace App\Listeners;

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

class RewardGiftCard implements ShouldQueue
{
/**
* Reward a gift card to the customer.
*/
public function handle(OrderCreated $event): void
{
// ...
}

/**
* Determine whether the listener should be queued.
*/
public function shouldQueue(OrderCreated $event): bool
{
return $event->order->subtotal >= 5000;
}
}

Manually Interacting With the Queue

リスナの基になるキュー ジョブの 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;

/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
if ($condition) {
$this->release(30);
}
}
}

Queued Event Listeners and Database Transactions

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

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

<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueueAfterCommit
{
use InteractsWithQueue;
}

これらの問題の回避方法の詳細については、queued jobs and database transactions に関するドキュメントを参照してください。

Queued Listener Middleware

キューに登録されたリスナは、job middleware を利用することもできます。ジョブ ミドルウェアを使用すると、キューに入れられたリスナの実行にカスタム ロジックをラップして、リスナ自体の定型文を減らすことができます。ジョブ ミドルウェアを作成した後、リスナの middleware メソッドからジョブ ミドルウェアを返すことによって、ジョブ ミドルウェアをリスナにアタッチできます。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use App\Jobs\Middleware\RateLimited;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Process the event...
}

/**
* Get the middleware the listener should pass through.
*
* @return array<int, object>
*/
public function middleware(OrderShipped $event): array
{
return [new RateLimited];
}
}

Encrypted Queued Listeners

Laravel を使用すると、encryption 経由でキューに入れられたリスナのデータのプライバシーと整合性を確保できます。まず、ShouldBeEncrypted インターフェイスをリスナ クラスに追加するだけです。このインターフェースがクラスに追加されると、Laravel はリスナをキューにプッシュする前に自動的に暗号化します。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue, ShouldBeEncrypted
{
// ...
}

Unique Event Listeners

固有のリスナには、locks をサポートするキャッシュ ドライバが必要です。現在、memcachedredisdynamodbdatabasefile、および array キャッシュ ドライバはアトミック ロックをサポートしています。

場合によっては、特定のリスナのインスタンスが常に 1 つだけキューに存在するようにしたい場合があります。これを行うには、リスナ クラスに ShouldBeUnique インターフェイスを実装します。

<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
public function __invoke(LicenseSaved $event): void
{
// ...
}
}

上の例では、AcquireProductKey リスナは一意です。したがって、リスナの別のインスタンスがすでにキュー上にあり、処理が完了していない場合、リスナはキューに入れられません。これにより、ライセンスが立て続けに複数回保存された場合でも、ライセンスごとにプロダクト キーが 1 つだけ取得されるようになります。

場合によっては、リスナを一意にする特定の「キー」を定義したり、リスナが一意でなくなるタイムアウトを指定したりすることができます。これを実現するには、リスナ クラスで uniqueId および uniqueFor プロパティまたはメソッドを定義できます。メソッドはイベント インスタンスを受け取り、イベント データを使用して戻り値を構築できるようになります。

<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
/**
* The number of seconds after which the listener's unique lock will be released.
*
* @var int
*/
public $uniqueFor = 3600;

public function __invoke(LicenseSaved $event): void
{
// ...
}

/**
* Get the unique ID for the listener.
*/
public function uniqueId(LicenseSaved $event): string
{
return 'listener:'.$event->license->id;
}
}

上の例では、AcquireProductKey リスナはライセンス ID によって一意です。したがって、同じライセンスに対するリスナの新しいディスパッチは、既存のリスナが処理を完了するまで無視されます。これにより、同じライセンスに対して重複したプロダクト キーが取得されるのを防ぎます。さらに、既存のリスナが 1 時間以内に処理されない場合、一意のロックが解放され、同じ一意のキーを持つ別のリスナがキューに追加される可能性があります。

アプリケーションが複数のWebサーバーまたはコンテナからイベントをディスパッチする場合は、Laravelがリスナが一意であるかどうかを正確に判断できるように、すべてのサーバーが同じ中央キャッシュサーバーと通信していることを確認する必要があります。

Keeping Listeners Unique Until Processing Begins

デフォルトでは、リスナが処理を完了するか、すべての再試行に失敗すると、一意のリスナは「ロック解除」されます。ただし、処理される直前にリスナのロックを解除したい場合もあります。これを実現するには、リスナは ShouldBeUnique コントラクトの代わりに ShouldBeUniqueUntilProcessing コントラクトを実装する必要があります。

<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;

class AcquireProductKey implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
// ...
}

Unique Listener Locks

バックグラウンドでは、ShouldBeUnique リスナがディスパッチされると、Laravel は uniqueId キーを使用して lock を取得しようとします。ロックがすでに保持されている場合、リスナはディスパッチされません。このロックは、リスナが処理を完了するか、すべての再試行に失敗すると解放されます。デフォルトでは、Laravel はデフォルトのキャッシュドライバを使用してこのロックを取得します。ただし、ロックの取得に別のドライバを使用したい場合は、使用するキャッシュ ドライバを返す uniqueVia メソッドを定義できます。

<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
// ...

/**
* Get the cache driver for the unique listener lock.
*/
public function uniqueVia(LicenseSaved $event): Repository
{
return Cache::driver('redis');
}
}

リスナの同時処理を制限する必要があるだけの場合は、代わりに WithoutOverlapping ジョブ ミドルウェアを使用してください。

Handling Failed Jobs

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

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;

/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// ...
}

/**
* Handle a job failure.
*/
public function failed(OrderShipped $event, Throwable $exception): void
{
// ...
}
}

Specifying Queued Listener Maximum Attempts

キューに入れられたリスナの 1 つでエラーが発生した場合、そのリスナが無制限に再試行を続けることは望ましくありません。したがって、Laravel では、リスナの試行回数または試行時間を指定するさまざまな方法が提供されています。

リスナ クラスの Tries 属性を使用して、リスナが失敗したとみなされるまでの試行回数を指定できます。

<?php

namespace App\Listeners;

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

#[Tries(5)]
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;

// ...
}

失敗するまでにリスナを何回試行できるかを定義する代わりに、リスナを試行しなくなる時間を定義することもできます。これにより、リスナは指定された時間枠内で何度でも試行できます。リスナを試行しなくなる時間を定義するには、retryUntil メソッドをリスナ クラスに追加します。このメソッドは DateTimeInterface インスタンスを返す必要があります。

use DateTimeInterface;

/**
* Determine the time at which the listener should timeout.
*/
public function retryUntil(): DateTimeInterface
{
return now()->plus(minutes: 5);
}

retryUntiltries の両方が定義されている場合、Laravel は retryUntil メソッドを優先します。

Specifying Queued Listener Backoff

例外が発生したリスナを再試行する前に Laravel が待機する秒数を設定したい場合は、リスナ クラスで Backoff 属性を使用できます。

<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Backoff;

#[Backoff(3)]
class SendShipmentNotification implements ShouldQueue
{
// ...
}

リスナのバックオフ時間を決定するためにより複雑なロジックが必要な場合は、リスナ クラスで backoff メソッドを定義できます。

/**
* Calculate the number of seconds to wait before retrying the queued listener.
*/
public function backoff(OrderShipped $event): int
{
return 3;
}

backoff メソッドからバックオフ値の配列を返すことで、「指数関数的」バックオフを簡単に構成できます。この例では、再試行の遅​​延は、最初の再試行では 1 秒、2 回目の再試行では 5 秒、3 回目の再試行では 10 秒、さらに試行が残っている場合はその後の再試行ごとに 10 秒になります。

/**
* Calculate the number of seconds to wait before retrying the queued listener.
*
* @return list<int>
*/
public function backoff(OrderShipped $event): array
{
return [1, 5, 10];
}

Specifying Queued Listener Max Exceptions

場合によっては、キューに入れられたリスナを何度も試行できるが、(release メソッドによって直接解放されるのではなく) 指定された数の未処理の例外によって再試行がトリガーされた場合は失敗するように指定したい場合があります。これを実現するには、リスナ クラスで Tries 属性と MaxExceptions 属性を使用します。

<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\InteractsWithQueue;

#[Tries(25)]
#[MaxExceptions(3)]
class SendShipmentNotification implements ShouldQueue
{
use InteractsWithQueue;

/**
* Handle the event.
*/
public function handle(OrderShipped $event): void
{
// Process the event...
}
}

この例では、リスナは最大 25 回再試行されます。ただし、リスナによって 3 つの未処理の例外がスローされた場合、リスナは失敗します。

Specifying Queued Listener Timeout

多くの場合、キューに入れられたリスナにかかる時間がおおよそわかっています。このため、Laravel では「タイムアウト」値を指定できます。リスナがタイムアウト値で指定された秒数を超えて処理している場合、リスナを処理しているワーカーはエラーで終了します。リスナ クラスの Timeout 属性を使用して、リスナの実行を許可する最大秒数を定義できます。

<?php

namespace App\Listeners;

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

#[Timeout(120)]
class SendShipmentNotification implements ShouldQueue
{
// ...
}

リスナをタイムアウト時に失敗としてマークする必要があることを示したい場合は、リスナ クラスで FailOnTimeout 属性を使用できます。

<?php

namespace App\Listeners;

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

#[FailOnTimeout]
class SendShipmentNotification implements ShouldQueue
{
// ...
}

Dispatching Events

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

<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class OrderShipmentController extends Controller
{
/**
* Ship the given order.
*/
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);

// Order shipment logic...

OrderShipped::dispatch($order);

return redirect('/orders');
}
}

条件付きでイベントをディスパッチしたい場合は、dispatchIf メソッドと dispatchUnless メソッドを使用できます。

OrderShipped::dispatchIf($condition, $order);

OrderShipped::dispatchUnless($condition, $order);

テストする場合、特定のイベントが実際にリスナをトリガーせずにディスパッチされたことをアサートすると役立つ場合があります。 Laravel の built-in testing helpers を使えば簡単です。

Dispatching Events After Database Transactions

場合によっては、アクティブなデータベーストランザクションがコミットされた後にのみイベントをディスパッチするようにLaravelに指示したい場合があります。これを行うには、イベント クラスに ShouldDispatchAfterCommit インターフェイスを実装します。

このインターフェイスは、現在のデータベーストランザクションがコミットされるまでイベントをディスパッチしないようにLaravelに指示します。トランザクションが失敗した場合、イベントは破棄されます。イベントが送出されるときにデータベース トランザクションが進行中でない場合、イベントはすぐに送出されます。

<?php

namespace App\Events;

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

class OrderShipped implements ShouldDispatchAfterCommit
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
* Create a new event instance.
*/
public function __construct(
public Order $order,
) {}
}

Deferring Events

遅延イベントを使用すると、コードの特定のブロックが完了するまで、モデル イベントのディスパッチとイベント リスナの実行を遅らせることができます。これは、イベント リスナがトリガーされる前に、関連するすべてのレコードが確実に作成されるようにする必要がある場合に特に便利です。

イベントを延期するには、Event::defer() メソッドにクロージャーを提供します。

use App\Models\User;
use Illuminate\Support\Facades\Event;

Event::defer(function () {
$user = User::create(['name' => 'Victoria Otwell']);

$user->posts()->create(['title' => 'My first post!']);
});

クロージャ内でトリガーされたすべてのイベントは、クロージャの実行後にディスパッチされます。これにより、イベント リスナは遅延実行中に作成されたすべての関連レコードにアクセスできるようになります。クロージャー内で例外が発生した場合、遅延イベントはディスパッチされません。

特定のイベントのみを延期するには、イベントの配列を 2 番目の引数として defer メソッドに渡します。

use App\Models\User;
use Illuminate\Support\Facades\Event;

Event::defer(function () {
$user = User::create(['name' => 'Victoria Otwell']);

$user->posts()->create(['title' => 'My first post!']);
}, ['eloquent.created: '.User::class]);

Event Subscribers

Writing Event Subscribers

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

<?php

namespace App\Listeners;

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

class UserEventSubscriber
{
/**
* Handle user login events.
*/
public function handleUserLogin(Login $event): void {}

/**
* Handle user logout events.
*/
public function handleUserLogout(Logout $event): void {}

/**
* Register the listeners for the subscriber.
*/
public function subscribe(Dispatcher $events): void
{
$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;
use Illuminate\Events\Dispatcher;

class UserEventSubscriber
{
/**
* Handle user login events.
*/
public function handleUserLogin(Login $event): void {}

/**
* Handle user logout events.
*/
public function handleUserLogout(Logout $event): void {}

/**
* Register the listeners for the subscriber.
*
* @return array<string, string>
*/
public function subscribe(Dispatcher $events): array
{
return [
Login::class => 'handleUserLogin',
Logout::class => 'handleUserLogout',
];
}
}

Registering Event Subscribers

サブスクライバを作成した後、Laravel の event discovery conventions に従っている場合、Laravel はサブスクライバ内にハンドラー メソッドを自動的に登録します。それ以外の場合は、Event ファサードの subscribe メソッドを使用してサブスクライバを手動で登録できます。通常、これはアプリケーションの AppServiceProviderboot メソッド内で行う必要があります。

<?php

namespace App\Providers;

use App\Listeners\UserEventSubscriber;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::subscribe(UserEventSubscriber::class);
}
}

Testing

イベントを送出するコードをテストする場合、リスナのコードは、対応するイベントを送出するコードとは別に直接テストできるため、イベントのリスナを実際に実行しないように Laravel に指示することもできます。もちろん、リスナ自体をテストするには、リスナ インスタンスをインスタンス化し、テスト内で handle メソッドを直接呼び出します。

Event ファサードの fake メソッドを使用すると、リスナの実行を防止し、テスト対象のコードを実行してから、assertDispatchedassertNotDispatched、および assertNothingDispatched メソッドを使用してアプリケーションによってどのイベントがディスパッチされたかをアサートできます。

<?php

use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;

test('orders can be shipped', function () {
Event::fake();

// Perform order shipping...

// Assert that an event was dispatched...
Event::assertDispatched(OrderShipped::class);

// Assert an event was dispatched twice...
Event::assertDispatched(OrderShipped::class, 2);

// Assert an event was dispatched once...
Event::assertDispatchedOnce(OrderShipped::class);

// Assert an event was not dispatched...
Event::assertNotDispatched(OrderFailedToShip::class);

// Assert that no events were dispatched...
Event::assertNothingDispatched();
});
<?php

namespace Tests\Feature;

use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class ExampleTest extends TestCase
{
/**
* Test order shipping.
*/
public function test_orders_can_be_shipped(): void
{
Event::fake();

// Perform order shipping...

// Assert that an event was dispatched...
Event::assertDispatched(OrderShipped::class);

// Assert an event was dispatched twice...
Event::assertDispatched(OrderShipped::class, 2);

// Assert an event was dispatched once...
Event::assertDispatchedOnce(OrderShipped::class);

// Assert an event was not dispatched...
Event::assertNotDispatched(OrderFailedToShip::class);

// Assert that no events were dispatched...
Event::assertNothingDispatched();
}
}

特定の「真実テスト」に合格するイベントがディスパッチされたことをアサートするために、assertDispatched メソッドまたは assertNotDispatched メソッドにクロージャーを渡すことができます。指定された真実テストに合格する少なくとも 1 つのイベントがディスパッチされた場合、アサーションは成功します。

Event::assertDispatched(function (OrderShipped $event) use ($order) {
return $event->order->id === $order->id;
});

イベント リスナが特定のイベントをリッスンしていることを単にアサートしたい場合は、assertListening メソッドを使用できます。

Event::assertListening(
OrderShipped::class,
SendShipmentNotification::class
);

Event::fake() を呼び出した後は、イベント リスナは実行されません。したがって、モデルの creating イベント中に UUID を作成するなど、イベントに依存するモデル ファクトリをテストで使用する場合は、ファクトリを使用した Event::fake() を呼び出す必要があります。

Faking a Subset of Events

特定のイベント セットに対してのみイベント リスナを偽装したい場合は、それらを fake メソッドまたは fakeFor メソッドに渡すことができます。

test('orders can be processed', function () {
Event::fake([
OrderCreated::class,
]);

$order = Order::factory()->create();

Event::assertDispatched(OrderCreated::class);

// Other events are dispatched as normal...
$order->update([
// ...
]);
});
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
Event::fake([
OrderCreated::class,
]);

$order = Order::factory()->create();

Event::assertDispatched(OrderCreated::class);

// Other events are dispatched as normal...
$order->update([
// ...
]);
}

except メソッドを使用すると、指定されたイベントのセットを除くすべてのイベントを偽装できます。

Event::fake()->except([
OrderCreated::class,
]);

Scoped Event Fakes

テストの一部に対してのみイベント リスナを偽装したい場合は、fakeFor メソッドを使用できます。

<?php

use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;

test('orders can be processed', function () {
$order = Event::fakeFor(function () {
$order = Order::factory()->create();

Event::assertDispatched(OrderCreated::class);

return $order;
});

// Events are dispatched as normal and observers will run...
$order->update([
// ...
]);
});
<?php

namespace Tests\Feature;

use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class ExampleTest extends TestCase
{
/**
* Test order process.
*/
public function test_orders_can_be_processed(): void
{
$order = Event::fakeFor(function () {
$order = Order::factory()->create();

Event::assertDispatched(OrderCreated::class);

return $order;
});

// Events are dispatched as normal and observers will run...
$order->update([
// ...
]);
}
}