Lzh on GitHub

简介

Laravel 的事件提供了一个简单的观察者模式实现,允许您订阅和监听在应用程序中发生的各种事件。事件类通常存储在 app/Events 目录中,而它们的监听器则存储在 app/Listeners 中。如果您在应用程序中没有看到这些目录,也不用担心,因为当您使用 Artisan 控制台命令生成事件和监听器时,它们将自动为您创建。

事件是解耦应用程序各个部分的好方法,因为一个事件可以有多个相互独立的监听器。例如,您可能希望在每次订单发货时向用户发送 Slack 通知。与其将您的订单处理代码与 Slack 通知代码耦合在一起,您可以触发一个 App\Events\OrderShipped 事件,监听器可以接收该事件并用它来分派 Slack 通知。

生成事件和监听器

要快速生成事件和监听器,您可以使用 make:eventmake:listener Artisan 命令:

php artisan make:event PodcastProcessed

php artisan make:listener SendPodcastNotification --event=PodcastProcessed

为了方便起见,您也可以不带额外参数地调用 make:eventmake:listener Artisan 命令。当您这样做时,Laravel 会自动提示您输入类名,以及在创建监听器时,它应该监听的事件:

php artisan make:event

php artisan make:listener

注册事件和监听器

事件发现

默认情况下,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

生产环境中的事件发现

为了提高应用程序的速度,您应该使用 optimizeevent:cache Artisan 命令缓存应用程序中所有监听器的清单。通常,此命令应该作为应用程序 部署过程 的一部分运行。框架将使用此清单来加快事件注册过程。event:clear 命令可用于销毁事件缓存。

手动注册事件

使用 Event Facade,您可以在应用程序的 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

闭包监听器

通常,监听器被定义为类;但是,您也可以在应用程序的 AppServiceProviderboot 方法中手动注册基于闭包的事件监听器:

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

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

可排队的匿名事件监听器

注册基于闭包的事件监听器时,您可以将监听器闭包包装在 Illuminate\Events\queueable 函数中,以指示 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) {
        // ...
    }));
}

与排队任务一样,您可以使用 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) {
    // The queued listener failed...
}));

通配符事件监听器

您还可以使用 * 字符作为通配符参数来注册监听器,从而允许您在同一个监听器上捕获多个事件。通配符监听器将其接收到的事件名称作为第一个参数,并将整个事件数据数组作为第二个参数:

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

定义事件

事件类本质上是一个数据容器,它包含与事件相关的信息。例如,假设 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 实例的容器。如果事件对象使用 PHP 的 serialize 函数进行序列化,例如在使用 排队的监听器时,事件使用的 SerializesModels trait 将优雅地序列化任何 Eloquent 模型。

定义监听器

接下来,让我们看看我们示例事件的监听器。事件监听器在其 handle 方法中接收事件实例。当使用 --event 选项调用 make:listener Artisan 命令时,它将自动导入正确的事件类并在 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 服务容器 解析,因此依赖项将自动注入。

停止事件的传播

有时,您可能希望停止事件向其他监听器的传播。您可以通过从监听器的 handle 方法返回 false 来实现。

排队的事件监听器

如果您的监听器将要执行诸如发送电子邮件或发出 HTTP 请求之类的慢速任务,则对监听器进行排队可能会很有益。在使用排队的监听器之前,请确保 配置您的队列 并在您的服务器或本地开发环境中启动一个队列工作进程。

要指定一个监听器应该被排队,请在监听器类上添加 ShouldQueue 接口。由 make: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
{
    /**
     * 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;
}

如果您想在运行时定义监听器的队列连接、队列名称或延迟,您可以在监听器上定义 viaConnectionviaQueuewithDelay 方法:

/**
 * 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;
}

有条件地排队监听器

有时,您可能需要根据运行时可用的某些数据来决定是否应该对监听器进行排队。要实现这一点,可以在监听器上添加一个 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;
    }
}

手动与队列交互

如果您需要手动访问监听器的底层队列任务的 deleterelease 方法,您可以使用 Illuminate\Queue\InteractsWithQueue trait。该 trait 在生成的监听器上默认导入,并提供对这些方法的访问:

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

排队的事件监听器和数据库事务

当在数据库事务中分派排队的监听器时,它们可能会在数据库事务提交之前被队列处理。发生这种情况时,您在数据库事务期间对模型或数据库记录所做的任何更新可能尚未反映在数据库中。此外,在事务中创建的任何模型或数据库记录可能在数据库中尚不存在。如果您的监听器依赖于这些模型,则在处理分派排队监听器的任务时可能会发生意外错误。

如果您的队列连接的 after_commit 配置选项设置为 false,您仍然可以通过在监听器类上实现 ShouldQueueAfterCommit 接口来指示某个特定的排队监听器应该在所有打开的数据库事务提交后才分派:

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueueAfterCommit
{
    use InteractsWithQueue;
}
要了解有关解决这些问题的更多信息,请查阅有关 排队任务和数据库事务 的文档。

排队的监听器中间件

排队的监听器也可以使用 任务中间件。任务中间件允许您在排队的监听器执行周围包装自定义逻辑,从而减少监听器本身的样板代码。创建任务中间件后,可以通过从监听器的 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];
    }
}

加密的排队监听器

Laravel 允许您通过 加密 来确保排队监听器数据的隐私和完整性。首先,只需将 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
{
    // ...
}

处理失败的任务

有时您的排队事件监听器可能会失败。如果排队监听器超过您的队列工作进程定义的最大尝试次数,则将在您的监听器上调用 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
    {
        // ...
    }
}

指定排队监听器最大尝试次数

如果您的某个排队监听器遇到错误,您可能不希望它无限期地重试。因此,Laravel 提供了多种方法来指定监听器可以尝试多少次或多长时间。

您可以在监听器类上定义一个 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;
}

作为定义监听器失败前可以尝试多少次的替代方案,您可以定义监听器不应再尝试的时间。这允许在给定时间范围内尝试监听器任意次数。要定义监听器不应再尝试的时间,请在监听器类上添加 retryUntil 方法。此方法应返回一个 DateTime 实例:

use DateTime;

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

如果同时定义了 retryUntiltries,Laravel 会优先使用 retryUntil 方法。

指定排队的监听器回退

如果您想配置 Laravel 在重试遇到异常的监听器之前应该等待多少秒,您可以通过在监听器类上定义 backoff 属性来实现:

/**
 * The number of seconds to wait before retrying the queued listener.
 *
 * @var int
 */
public $backoff = 3;

如果您需要更复杂的逻辑来确定监听器的回退时间,您可以在监听器类上定义 backoff 方法:

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

您可以通过从 backoff 方法返回一个回退值数组来轻松配置“指数”回退。在此示例中,第一次重试的重试延迟将为 1 秒,第二次重试为 5 秒,第三次重试为 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];
}

指定排队监听器最大异常数

有时您可能希望指定排队的监听器可以尝试多次,但如果重试是由给定数量的未处理异常触发的(而不是直接由 release 方法释放),则应该失败。为了实现这一点,您可以在监听器类上定义一个 maxExceptions 属性:

<?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 = 25;

    /**
     * The maximum number of unhandled exceptions to allow before failing.
     *
     * @var int
     */
    public $maxExceptions = 3;

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

在此示例中,监听器将重试最多 25 次。但是,如果监听器抛出三个未处理的异常,则监听器将失败。

指定排队监听器超时

通常,您大致知道您期望排队的监听器需要多长时间。因此,Laravel 允许您指定一个“超时”值。如果监听器处理时间超过超时值指定的秒数,则处理该监听器的工作进程将以错误退出。您可以通过在监听器类上定义 timeout 属性来定义允许监听器运行的最大秒数:

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    /**
     * The number of seconds the listener can run before timing out.
     *
     * @var int
     */
    public $timeout = 120;
}

如果您想表明监听器应该在超时时被标记为失败,您可以在监听器类上定义 failOnTimeout 属性:

<?php

namespace App\Listeners;

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

class SendShipmentNotification implements ShouldQueue
{
    /**
     * Indicate if the listener should be marked as failed on timeout.
     *
     * @var bool
     */
    public $failOnTimeout = true;
}

分派事件

要分派事件,您可以调用事件上的静态 dispatch 方法。此方法由 Illuminate\Foundation\Events\Dispatchable trait 在事件上提供。传递给 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');
    }
}

如果您想有条件地分派事件,可以使用 dispatchIfdispatchUnless 方法:

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

OrderShipped::dispatchUnless($condition, $order);
在测试时,断言某些事件已被分派但实际上不触发它们的监听器会很有帮助。Laravel 的 内置测试助手 使这变得轻而易举。

数据库事务后分派事件

有时,您可能希望指示 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,
    ) {}
}

延迟事件

延迟事件允许您延迟模型事件的分派和事件监听器的执行,直到特定代码块完成。当您需要确保在触发事件监听器之前创建所有相关记录时,这特别有用。

要延迟事件,请向 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!']);
});

闭包中触发的所有事件都将在闭包执行后分派。这确保了事件监听器可以访问在延迟执行期间创建的所有相关记录。如果闭包中发生异常,则延迟的事件将不会被分派。

要仅延迟特定事件,请将事件数组作为第二个参数传递给 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]);

事件订阅者

编写事件订阅者

事件订阅者是可以在订阅者类本身内部订阅多个事件的类,允许您在单个类中定义多个事件处理程序。订阅者应定义一个 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',
        ];
    }
}

注册事件订阅者

编写订阅者后,如果它们的处理程序方法遵循 Laravel 的 事件发现约定,Laravel 将自动注册它们。否则,您可以使用 Event Facade 的 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);
    }
}

测试

在测试分派事件的代码时,您可能希望指示 Laravel 不要实际执行事件的监听器,因为监听器的代码可以与分派相应事件的代码分开直接进行测试。当然,要测试监听器本身,您可以实例化一个监听器实例并在测试中直接调用 handle 方法。

使用 Event Facade 的 fake 方法,您可以阻止监听器执行,执行待测试的代码,然后使用 assertDispatchedassertNotDispatchedassertNothingDispatched 方法断言您的应用程序分派了哪些事件:

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

您可以向 assertDispatchedassertNotDispatched 方法传递一个闭包,以断言分派了通过给定“真实性测试”的事件。如果至少有一个通过给定真实性测试的事件被分派,则断言将成功:

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()

模拟部分事件

如果您只想模拟特定事件集的事件监听器,您可以将它们传递给 fakefakeFor 方法:

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

您可以使用 except 方法模拟除指定事件集之外的所有事件:

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

作用域事件模拟

如果您只想在测试的一部分中模拟事件监听器,您可以使用 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([
        // ...
    ]);
});