Lang x Lang

Events

Table of Contents

Introduction

Laravel の events は、シンプルなオブザーバーパターンの実装を提供し、あなたの application 内で発生するさまざまな events を subscribe してリッスンすることができます。 Event クラスは通常、app/Events ディレクトリに保存され、その listeners は app/Listenersに保存されます。これらのディレクトリがあなたの application に見えなくても心配しないでください、なぜならあなたが events と listeners を Artisan console コマンドを使用して生成するときにそれらは作成されます。

Events は、application の様々な要素を分離する素晴らしい方法として役立ちます。なぜなら、シングル Events は、互いに依存しない複数の listeners を持つことができるからです。例えば、注文が発送されるたびに user に Slack 通知を送りたい場合があります。注文の処理コードを Slack 通知コードに結びつける代わりに、listeners が受け取って Slack 通知を dispatch するために使用できるApp\Events\OrderShipped Events を発生させることができます。

Generating Events and Listeners

すばやく events と listeners を生成するには、make:eventおよびmake:listenerという Artisan コマンドを使用できます:

php artisan make:event PodcastProcessed

php artisan make:listener SendPodcastNotification --event=PodcastProcessed

便宜上、追加の引数なしで make:eventmake:listener の Artisan コマンドを呼び出すこともできます。そうすると、 Laravel は自動的にあなたに class 名を prompt し、リスナーを作成するときには、それがリッスンすべき event を指示します:

php artisan make:event

php artisan make:listener

Registering Events and Listeners

Event 発見

default として、Laravel は、あなたの application のListenersディレクトリをスキャンして、自動的にあなたの event listeners を登録します。Laravel が、handleまたは__invokeで始まる listener class methods を見つけたとき、その methods が signature で types 指定されている event の event listeners として、それらの methods を登録します。

use App\Events\PodcastProcessed;

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

あなたが listeners を異なるディレクトリや複数のディレクトリに保存する予定の場合、withEvents method を使用してそれらのディレクトリを Laravel にスキャンさせるよう指示できます。この操作は、アプリケーションの bootstrap/app.php ファイル内で行います:

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

event:listの command は、あなたの application 内で登録されているすべての listeners をリストするために使用できます:

php artisan event:list

Production における Event の発見

あなたの application の速度を向上させるために、optimizeまたはevent:cacheの Artisan コマンドを使用して、アプリケーションの listeners のマニフェスト全体を cache する必要があります。通常、この command は、あなたのアプリケーションのdeployment processの一部として実行するべきです。このマニフェストはフレームワークによって event の登録 process を高速化するために使用されます。event:clear command は、 event cache を削除するために使用することができます。

手動で Events を登録する

Event facade を使用すると、アプリケーションのAppServiceProviderboot method 内で手動で register events とそれに対応する listeners を設定することができます:

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の command は、あなたの application 内に登録されているすべての listeners を一覧表示するために使用することができます:

php artisan event:list

クロージャー Listeners

通常、 listeners はクラスとして定義されますが、アプリケーションのAppServiceProviderboot method で手動で閉じ込めベースの event listeners を register することも可能です。

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

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

キュー可能な匿名の Event Listeners

クロージャーベースの event listeners を登録する際には、リスナークロージャをIlluminate\Events\queueable関数でラップして、 Laravel にリスナーをqueueを使用して実行するよう指示することができます:

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

キューに入れられた jobs のように、キューに入れられたリスナーの実行をカスタマイズするためにonConnectiononQueue、およびdelayメソッドを使用することができます:

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

匿名のキューリスナーの失敗を handle したい場合は、queueableリスナーを定義する際に、catch method にクロージャを提供できます。このクロージャは、 event インスタンスと、リスナーの失敗を引き起こした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...
}));

ワイルドカード Event Listeners

また、ワイルドカードパラメータとして*文字を使用して、 register listeners することも可能で、これにより同じリスナー上で複数の events を catch することができます。ワイルドカードの listeners は、最初の引数として event 名を、2 番目の引数として全体の event data array を受け取ります:

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

Defining Events

event class とは基本的に、 event に関連する情報を保持する data コンテナです。例えば、App\Events\OrderShipped event がEloquent ORM object を受け取るとしましょう:

<?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,
    ) {}
}

ご覧の通り、この event class はロジックを含んでいません。これは購入されたApp\Models\Orderインスタンスのコンテナです。 event で使われているSerializesModelsトレイトは、PHP のserialize関数を使用して event object がシリアライズされた場合、たとえば queued listenersを利用する際などに Eloquent models を優雅にシリアライズします。

Defining Listeners

次に、私たちの例の event に対するリスナーを見てみましょう。 Event listeners は、そのhandle method 内で event インスタンスを受け取ります。make:listener Artisan command は、--eventオプションで呼び出されると、適切な event class を自動的にインポートし、 event をhandle method 内で型ヒントします。handle method 内で、 event に対応するために必要なアクションを実行することができます:

<?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...
    }
}

NOTE

あなたの event listeners は、コンストラクタで必要とする依存関係を型ヒントとしても指定できます。すべての event listeners は、 Laravel のservice containerを介して解決されるため、依存関係は自動的に注入されます。

Event の伝播を停止する

時には、 event の伝播を他の listeners に止めたいと思うこともあるかもしれません。その場合、リスナーの handle method から false を返すことでそれを実現できます。

Queued Event Listeners

listeners を queue イングすることは、mail の送信や HTTP request の作成など、時間のかかるタスクを実行する場合に有用です。queue イングされた listeners を使用する前に、queue を設定し、server またはローカル development 環境で queue ワーカーを起動してください。

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

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    // ...
}

それで完了です!これで、このリスナーが処理する event がディスパッチされると、リスナーは Laravel のqueue systemを使用して event ディスパッチャーによって自動的にキューに追加されます。リスナーが queue によって実行される時に exceptions がスローされなければ、キューに追加された job は自動的に削除されます。 processing が完了した後にです。

Queue Connection のカスタマイズ、名前、& Delay

もし event リスナーの queue connection 、 queue 名、または queue delay 時間をカスタマイズしたい場合、リスナーの class に $connection$queue、または $delay のプロパティを定義することができます:

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    /**
     * The name of the connection the job should be sent to.
     *
     * @var string|null
     */
    public $connection = 'sqs';

    /**
     * The name of the queue the job should be sent to.
     *
     * @var string|null
     */
    public $queue = 'listeners';

    /**
     * The time (seconds) before the job should be processed.
     *
     * @var int
     */
    public $delay = 60;
}

リスナーの queue connection 、 queue 名、または delay を runtime で定義したい場合は、リスナーで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;
}

条件付きキューイング Listeners

時々、リスナーがキューに入るべきかどうかを決定する必要があり、その判断材料は data としてしか利用できない runtime の情報に基づいているかもしれません。このような場合に対処するために、リスナーに shouldQueue method を追加してリスナーがキューに入るべきかどうかを判断できます。shouldQueue method が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;
    }
}

Queue と手動でやり取りする

リスナーの基礎となる queue ジョブの deleterelease メソッドへ手動でアクセスする必要がある場合、Illuminate\Queue\InteractsWithQueue trait を使用して行うことができます。この trait は、生成された listeners に対して default でインポートされ、これらのメソッドへのアクセスを提供します。

<?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 (true) {
            $this->release(30);
        }
    }
}

キューイングされた Event Listeners と Database Transactions

キューに入れられた listeners が database transactions 内でディスパッチされると、それらは database transaction がコミットされる前に queue によって処理される可能性があります。これが起こると、 database transaction の間に models や database のレコードに行った更新は、まだ database に反映されていない可能性があります。また、 transaction の中で作成された models や database のレコードは、 database に存在しない可能性もあります。リスナーがこれらの models に依存している場合、キューに入れられたリスナーをディスパッチする job が処理されるときに、予期しない errors が発生する可能性があります。

あなたの queue 接続のafter_commit設定オプションがfalseに設定されている場合でも、特定のキューに入れられたリスナーがすべてのオープンな database transactions がコミットされた後にディスパッチされるべきであることを示すことはできます。それは、リスナーの class 上でShouldHandleEventsAfterCommitインターフェースを実装することによって行われます。

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue, ShouldHandleEventsAfterCommit
{
    use InteractsWithQueue;
}

NOTE

これらの問題を回避する方法について詳しく知るために、キュー内の jobs と database transactionsに関するドキュメンテーションをご覧ください。

失敗した Jobs の処理

場合によっては、キューに追加された event listeners が失敗することがあります。キューに追加されたリスナーが queue ワーカーによって定義された最大試行回数を超えた場合、failed method がリスナーで呼び出されます。failed method は event インスタンスと、その失敗を引き起こした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
    {
        // ...
    }
}

キューリスナーの最大試行回数の指定

あなたのキューに入れられた listeners の 1 つが error に遭遇している場合、おそらくそれが無限に再試行し続けることは望ましくないでしょう。そのため、 Laravel は、リスナーが何回またはどれくらいの期間試行されるかを指定するための様々な方法を提供しています。

リスナーの class に $tries プロパティを定義することで、リスナーが失敗とみなされる前に何回試行することができるかを指定することができます:

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    /**
     * The number of times the queued listener may be attempted.
     *
     * @var int
     */
    public $tries = 5;
}

listener が失敗するまで何回試行するかを定義する代わりに、listener を試行しない時刻を定義することもできます。これにより、listener は指定された時間枠内で何回でも試行することが可能になります。listener の試行を停止する時刻を定義するには、retryUntil method を listener の class に追加してください。この method はDateTimeインスタンスを返すべきです:

use DateTime;

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

Dispatching Events

event を dispatch するためには、event 上で静的なdispatch method を呼び出すことができます。この method はIlluminate\Foundation\Events\Dispatchableトレイトによって event に用意されています。dispatch method に渡された引数はすべて、イベントのコンストラクターに渡されます:

<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
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');
    }
}

条件付きで dispatch あるいは event を行いたい場合は、dispatchIfメソッドとdispatchUnlessメソッドを使用することができます:

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

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

NOTE

testing helpers する際には、特定の events が dispatch されたことを assert することが役立つことがありますが、それらの listeners を実際にトリガーすることなく行います。Laravel の組み込み testing helperのおかげでとても簡単になります。

Database Transactions の後に Events を dispatch する

時々、アクティブな database transaction がコミットした後にのみ、 Laravel に event を dispatch するよう指示したいことがあるかもしれません。そのためには、ShouldDispatchAfterCommit インターフェースを event class に実装することができます。

このインターフェースは、現在の database transaction がコミットされるまで、 Laravel に event を dispatch しないよう指示します。もし transaction が失敗した場合、 event は破棄されます。もし event が発行された時に進行中の database transaction がない場合、 event はすぐに発行されます。

<?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,
    ) {}
}

Event Subscribers

Event サブスクライバーの記述

event の購読者は、購読者の class 内から複数の event を購読することができるクラスであり、一つの class 内にいくつもの event ハンドラーを定義することが可能です。購読者は、 subscribe method を定義すべきで、このメソッドは event dispatcher インスタンスが渡されます。その dispatch ャーに対してlisten method を呼び出すことで、event listener の登録が可能です。

<?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']
        );
    }
}

もし、あなたの events listenermethod がサブスクライバ自体内で定義されているなら、サブスクライバの subscribe method から events と method の名前の array を返す方が便利かもしれません。Laravel は event listeners を登録する時に自動的にサブスクライバの class 名を決定します。

<?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',
        ];
    }
}

Event サブスクライバーの登録

サブスクライバを書いた後、それを register して event ディスパッチャーに登録する準備が整います。サブスクライバは、Event facade のsubscribe method を使用して register することができます。通常、これはアプリケーションのAppServiceProviderboot method 内で行うべきです。

<?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

events を発行する testing コードを使用する時、events の listeners を実際に実行しないように Laravel に指示することができます。その理由は、listeners のコードは発行するコードと対応する event とは別に直接そして独立して testing することができるからです。もちろん、listeners 自体を testing するには、listeners インスタンスを生成し、testing 中にhandlemethod`を直接呼び出すことができます。

Eventfacade のfake method を使用すると、 listeners の実行を防ぎ、test 対象の code を実行し、その後、assertDispatchedassertNotDispatched、およびassertNothingDispatchedメソッドを使用して、あなたの application によって発行された events を assert することができます。

<?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 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 not dispatched...
        Event::assertNotDispatched(OrderFailedToShip::class);

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

閉包をassertDispatchedまたはassertNotDispatchedmethods に渡すことで、特定の"真実 test"を通過する event が dispatch されたことを assert することができます。少なくとも一つの event が指定された真実 test を通過していれば、アサーションは成功します:

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

もし単純に event リスナーが特定の event を聴いていることを assert したい場合は、assertListening method を使用することができます。

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

WARNING

Event::fake()を呼び出した後、 event listeners は実行されません。そのため、テストが events に依存する model のファクトリーを使用している場合(たとえば、モデルのcreating event の間に UUID を生成するような場合)、ファクトリーを使用したEvent::fake()を呼び出すべきです。

Events の一部を偽造する

特定の events のみに対して fake event listeners を設定したい場合、それらをfakeまたはfakeFor method に渡すことができます:

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 method を使用して、指定された一連の events を除いてすべての events を fake することができます。

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

スコープした Event フェイク

テストの一部でのみ fake event listeners を使用したい場合は、fakeFor method を使用することができます。

<?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([...]);
    }
}

当社サイトでは、Cookie を使用しています。各規約をご確認の上ご利用ください:
Cookie Policy, Privacy Policy および Terms of Use