跳到内容

事件

简介

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

事件是解耦应用程序各个方面的好方法,因为单个事件可以有多个互不依赖的监听器。例如,你可能希望在每次订单发货时向用户发送 Slack 通知。与其将订单处理代码与 Slack 通知代码耦合,不如引发一个 App\Events\OrderShipped 事件,监听器可以接收该事件并用于调度 Slack 通知。

生成事件和监听器

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

1php artisan make:event PodcastProcessed
2 
3php artisan make:listener SendPodcastNotification --event=PodcastProcessed

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

1php artisan make:event
2 
3php artisan make:listener

注册事件和监听器

事件发现

默认情况下,Laravel 将通过扫描应用程序的 Listeners 目录自动查找和注册你的事件监听器。当 Laravel 找到任何以 handle__invoke 开头的监听器类方法时,Laravel 会将这些方法注册为该方法签名中类型提示的事件的事件监听器

1use App\Events\PodcastProcessed;
2 
3class SendPodcastNotification
4{
5 /**
6 * Handle the given event.
7 */
8 public function handle(PodcastProcessed $event): void
9 {
10 // ...
11 }
12}

你可以使用 PHP 的联合类型监听多个事件

1/**
2 * Handle the given event.
3 */
4public function handle(PodcastProcessed|PodcastPublished $event): void
5{
6 // ...
7}

如果你计划将监听器存储在不同的目录或多个目录中,你可以指示 Laravel 使用应用程序 bootstrap/app.php 文件中的 withEvents 方法扫描这些目录

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

你可以使用 * 字符作为通配符扫描多个类似的目录中的监听器

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

event:list 命令可以用来列出应用程序中注册的所有监听器

1php artisan event:list

生产环境中的事件发现

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

手动注册事件

使用 Event 外观模式,你可以在应用程序 AppServiceProviderboot 方法中手动注册事件及其对应的监听器

1use App\Domain\Orders\Events\PodcastProcessed;
2use App\Domain\Orders\Listeners\SendPodcastNotification;
3use Illuminate\Support\Facades\Event;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Event::listen(
11 PodcastProcessed::class,
12 SendPodcastNotification::class,
13 );
14}

event:list 命令可以用来列出应用程序中注册的所有监听器

1php artisan event:list

闭包监听器

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

1use App\Events\PodcastProcessed;
2use Illuminate\Support\Facades\Event;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Event::listen(function (PodcastProcessed $event) {
10 // ...
11 });
12}

可队列化的匿名事件监听器

当注册基于闭包的事件监听器时,你可以将监听器闭包包装在 Illuminate\Events\queueable 函数中,以指示 Laravel 使用队列执行监听器

1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Event::listen(queueable(function (PodcastProcessed $event) {
11 // ...
12 }));
13}

与队列化任务一样,你可以使用 onConnectiononQueuedelay 方法自定义队列化监听器的执行

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

如果你想处理匿名队列化监听器失败的情况,你可以在定义 queueable 监听器时为 catch 方法提供一个闭包。此闭包将接收事件实例和导致监听器失败的 Throwable 实例

1use App\Events\PodcastProcessed;
2use function Illuminate\Events\queueable;
3use Illuminate\Support\Facades\Event;
4use Throwable;
5 
6Event::listen(queueable(function (PodcastProcessed $event) {
7 // ...
8})->catch(function (PodcastProcessed $event, Throwable $e) {
9 // The queued listener failed...
10}));

通配符事件监听器

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

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

定义事件

事件类本质上是一个数据容器,其中包含与事件相关的信息。例如,假设 App\Events\OrderShipped 事件接收一个 Eloquent ORM 对象

1<?php
2 
3namespace App\Events;
4 
5use App\Models\Order;
6use Illuminate\Broadcasting\InteractsWithSockets;
7use Illuminate\Foundation\Events\Dispatchable;
8use Illuminate\Queue\SerializesModels;
9 
10class OrderShipped
11{
12 use Dispatchable, InteractsWithSockets, SerializesModels;
13 
14 /**
15 * Create a new event instance.
16 */
17 public function __construct(
18 public Order $order,
19 ) {}
20}

如你所见,此事件类不包含任何逻辑。它是已购买的 App\Models\Order 实例的容器。如果使用 PHP 的 serialize 函数序列化事件对象(例如,当使用 队列化监听器 时),事件使用的 SerializesModels trait 将优雅地序列化任何 Eloquent 模型。

定义监听器

接下来,让我们看一下示例事件的监听器。事件监听器在其 handle 方法中接收事件实例。当使用 --event 选项调用 make:listener Artisan 命令时,它将自动导入正确的事件类并在 handle 方法中类型提示事件。在 handle 方法中,你可以执行响应事件所需的任何操作

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderShipped;
6 
7class SendShipmentNotification
8{
9 /**
10 * Create the event listener.
11 */
12 public function __construct() {}
13 
14 /**
15 * Handle the event.
16 */
17 public function handle(OrderShipped $event): void
18 {
19 // Access the order using $event->order...
20 }
21}

你的事件监听器也可以在其构造函数中类型提示它们需要的任何依赖项。所有事件监听器都通过 Laravel 服务容器 解析,因此依赖项将自动注入。

停止事件传播

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

队列化事件监听器

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

要指定应将监听器排队,请将 ShouldQueue 接口添加到监听器类。由 make:listener Artisan 命令生成的监听器已将此接口导入到当前命名空间中,因此你可以立即使用它

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7 
8class SendShipmentNotification implements ShouldQueue
9{
10 // ...
11}

就是这样!现在,当调度由此监听器处理的事件时,事件调度器将使用 Laravel 的 队列系统 自动将监听器排队。如果在队列执行监听器时未抛出任何异常,则排队的任务将在完成处理后自动删除。

自定义队列连接、名称和延迟

如果你想自定义事件监听器的队列连接、队列名称或队列延迟时间,你可以在监听器类上定义 $connection$queue$delay 属性

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7 
8class SendShipmentNotification implements ShouldQueue
9{
10 /**
11 * The name of the connection the job should be sent to.
12 *
13 * @var string|null
14 */
15 public $connection = 'sqs';
16 
17 /**
18 * The name of the queue the job should be sent to.
19 *
20 * @var string|null
21 */
22 public $queue = 'listeners';
23 
24 /**
25 * The time (seconds) before the job should be processed.
26 *
27 * @var int
28 */
29 public $delay = 60;
30}

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

1/**
2 * Get the name of the listener's queue connection.
3 */
4public function viaConnection(): string
5{
6 return 'sqs';
7}
8 
9/**
10 * Get the name of the listener's queue.
11 */
12public function viaQueue(): string
13{
14 return 'listeners';
15}
16 
17/**
18 * Get the number of seconds before the job should be processed.
19 */
20public function withDelay(OrderShipped $event): int
21{
22 return $event->highPriority ? 0 : 60;
23}

有条件地队列化监听器

有时,你可能需要根据仅在运行时可用的某些数据来确定是否应将监听器排队。为了实现这一点,可以在监听器中添加 shouldQueue 方法,以确定是否应将监听器排队。如果 shouldQueue 方法返回 false,则不会将监听器排队

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderCreated;
6use Illuminate\Contracts\Queue\ShouldQueue;
7 
8class RewardGiftCard implements ShouldQueue
9{
10 /**
11 * Reward a gift card to the customer.
12 */
13 public function handle(OrderCreated $event): void
14 {
15 // ...
16 }
17 
18 /**
19 * Determine whether the listener should be queued.
20 */
21 public function shouldQueue(OrderCreated $event): bool
22 {
23 return $event->order->subtotal >= 5000;
24 }
25}

手动与队列交互

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

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8 
9class SendShipmentNotification implements ShouldQueue
10{
11 use InteractsWithQueue;
12 
13 /**
14 * Handle the event.
15 */
16 public function handle(OrderShipped $event): void
17 {
18 if (true) {
19 $this->release(30);
20 }
21 }
22}

队列化事件监听器和数据库事务

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

如果你的队列连接的 after_commit 配置选项设置为 false,你仍然可以通过在监听器类上实现 ShouldQueueAfterCommit 接口来指示特定的队列化监听器应在所有打开的数据库事务都已提交后调度

1<?php
2 
3namespace App\Listeners;
4 
5use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
6use Illuminate\Queue\InteractsWithQueue;
7 
8class SendShipmentNotification implements ShouldQueueAfterCommit
9{
10 use InteractsWithQueue;
11}

要了解有关解决这些问题的更多信息,请查看有关队列任务和数据库事务的文档。

处理失败的任务

有时,你的队列化事件监听器可能会失败。如果队列化监听器超过了队列工作进程定义的最大尝试次数,则将在你的监听器上调用 failed 方法。failed 方法接收事件实例和导致失败的 Throwable

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8use Throwable;
9 
10class SendShipmentNotification implements ShouldQueue
11{
12 use InteractsWithQueue;
13 
14 /**
15 * Handle the event.
16 */
17 public function handle(OrderShipped $event): void
18 {
19 // ...
20 }
21 
22 /**
23 * Handle a job failure.
24 */
25 public function failed(OrderShipped $event, Throwable $exception): void
26 {
27 // ...
28 }
29}

指定队列化监听器的最大尝试次数

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

你可以在监听器类上定义 $tries 属性,以指定在将监听器视为失败之前可以尝试多少次

1<?php
2 
3namespace App\Listeners;
4 
5use App\Events\OrderShipped;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Queue\InteractsWithQueue;
8 
9class SendShipmentNotification implements ShouldQueue
10{
11 use InteractsWithQueue;
12 
13 /**
14 * The number of times the queued listener may be attempted.
15 *
16 * @var int
17 */
18 public $tries = 5;
19}

除了定义监听器在失败之前可以尝试多少次之外,你还可以定义监听器不应再尝试的时间。这允许监听器在给定的时间范围内尝试任意次数。要定义监听器不应再尝试的时间,请向你的监听器类添加 retryUntil 方法。此方法应返回 DateTime 实例

1use DateTime;
2 
3/**
4 * Determine the time at which the listener should timeout.
5 */
6public function retryUntil(): DateTime
7{
8 return now()->addMinutes(5);
9}

指定队列化监听器的退避策略

如果你想配置 Laravel 在重试遇到异常的监听器之前应等待多少秒,你可以通过在监听器类上定义 backoff 属性来完成此操作

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

如果你的监听器退避时间需要更复杂的逻辑,你可以在监听器类上定义 backoff 方法

1/**
2 * Calculate the number of seconds to wait before retrying the queued listener.
3 */
4public function backoff(): int
5{
6 return 3;
7}

你可以通过从 backoff 方法返回退避值数组来轻松配置“指数”退避。在此示例中,第一次重试的重试延迟为 1 秒,第二次重试为 5 秒,第三次重试为 10 秒,如果还有剩余尝试次数,则每次后续重试均为 10 秒

1/**
2 * Calculate the number of seconds to wait before retrying the queued listener.
3 *
4 * @return array<int, int>
5 */
6public function backoff(): array
7{
8 return [1, 5, 10];
9}

调度事件

要调度事件,你可以调用事件上的静态 dispatch 方法。此方法通过 Illuminate\Foundation\Events\Dispatchable trait 在事件上可用。传递给 dispatch 方法的任何参数都将传递给事件的构造函数

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Events\OrderShipped;
6use App\Http\Controllers\Controller;
7use App\Models\Order;
8use Illuminate\Http\RedirectResponse;
9use Illuminate\Http\Request;
10 
11class OrderShipmentController extends Controller
12{
13 /**
14 * Ship the given order.
15 */
16 public function store(Request $request): RedirectResponse
17 {
18 $order = Order::findOrFail($request->order_id);
19 
20 // Order shipment logic...
21 
22 OrderShipped::dispatch($order);
23 
24 return redirect('/orders');
25 }
26}

如果你想有条件地调度事件,你可以使用 dispatchIfdispatchUnless 方法

1OrderShipped::dispatchIf($condition, $order);
2 
3OrderShipped::dispatchUnless($condition, $order);

在测试时,断言某些事件已被调度而无需实际触发其监听器可能会很有帮助。Laravel 的内置测试辅助工具使其变得轻而易举。

在数据库事务后调度事件

有时,你可能希望指示 Laravel 仅在活动数据库事务提交后才调度事件。为此,你可以在事件类上实现 ShouldDispatchAfterCommit 接口。

此接口指示 Laravel 在当前数据库事务提交之前不要调度事件。如果事务失败,则事件将被丢弃。如果在调度事件时没有正在进行的数据库事务,则事件将立即调度

1<?php
2 
3namespace App\Events;
4 
5use App\Models\Order;
6use Illuminate\Broadcasting\InteractsWithSockets;
7use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
8use Illuminate\Foundation\Events\Dispatchable;
9use Illuminate\Queue\SerializesModels;
10 
11class OrderShipped implements ShouldDispatchAfterCommit
12{
13 use Dispatchable, InteractsWithSockets, SerializesModels;
14 
15 /**
16 * Create a new event instance.
17 */
18 public function __construct(
19 public Order $order,
20 ) {}
21}

事件订阅器

编写事件订阅器

事件订阅器是可以从订阅器类本身订阅多个事件的类,允许你在单个类中定义多个事件处理程序。订阅器应定义一个 subscribe 方法,该方法将传递一个事件调度器实例。你可以调用给定调度器上的 listen 方法来注册事件监听器

1<?php
2 
3namespace App\Listeners;
4 
5use Illuminate\Auth\Events\Login;
6use Illuminate\Auth\Events\Logout;
7use Illuminate\Events\Dispatcher;
8 
9class UserEventSubscriber
10{
11 /**
12 * Handle user login events.
13 */
14 public function handleUserLogin(Login $event): void {}
15 
16 /**
17 * Handle user logout events.
18 */
19 public function handleUserLogout(Logout $event): void {}
20 
21 /**
22 * Register the listeners for the subscriber.
23 */
24 public function subscribe(Dispatcher $events): void
25 {
26 $events->listen(
27 Login::class,
28 [UserEventSubscriber::class, 'handleUserLogin']
29 );
30 
31 $events->listen(
32 Logout::class,
33 [UserEventSubscriber::class, 'handleUserLogout']
34 );
35 }
36}

如果你的事件监听器方法在订阅器本身中定义,你可能会发现从订阅器的 subscribe 方法返回事件和方法名称数组更方便。Laravel 将在注册事件监听器时自动确定订阅器的类名

1<?php
2 
3namespace App\Listeners;
4 
5use Illuminate\Auth\Events\Login;
6use Illuminate\Auth\Events\Logout;
7use Illuminate\Events\Dispatcher;
8 
9class UserEventSubscriber
10{
11 /**
12 * Handle user login events.
13 */
14 public function handleUserLogin(Login $event): void {}
15 
16 /**
17 * Handle user logout events.
18 */
19 public function handleUserLogout(Logout $event): void {}
20 
21 /**
22 * Register the listeners for the subscriber.
23 *
24 * @return array<string, string>
25 */
26 public function subscribe(Dispatcher $events): array
27 {
28 return [
29 Login::class => 'handleUserLogin',
30 Logout::class => 'handleUserLogout',
31 ];
32 }
33}

注册事件订阅器

在编写订阅器之后,如果处理程序方法遵循 Laravel 的事件发现约定,Laravel 将自动注册订阅器中的处理程序方法。否则,你可以使用 Event 外观模式的 subscribe 方法手动注册订阅器。通常,这应在应用程序 AppServiceProviderboot 方法中完成

1<?php
2 
3namespace App\Providers;
4 
5use App\Listeners\UserEventSubscriber;
6use Illuminate\Support\Facades\Event;
7use Illuminate\Support\ServiceProvider;
8 
9class AppServiceProvider extends ServiceProvider
10{
11 /**
12 * Bootstrap any application services.
13 */
14 public function boot(): void
15 {
16 Event::subscribe(UserEventSubscriber::class);
17 }
18}

测试

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

使用 Event 外观模式的 fake 方法,你可以阻止监听器执行,执行被测代码,然后使用 assertDispatchedassertNotDispatchedassertNothingDispatched 方法断言应用程序调度了哪些事件

1<?php
2 
3use App\Events\OrderFailedToShip;
4use App\Events\OrderShipped;
5use Illuminate\Support\Facades\Event;
6 
7test('orders can be shipped', function () {
8 Event::fake();
9 
10 // Perform order shipping...
11 
12 // Assert that an event was dispatched...
13 Event::assertDispatched(OrderShipped::class);
14 
15 // Assert an event was dispatched twice...
16 Event::assertDispatched(OrderShipped::class, 2);
17 
18 // Assert an event was not dispatched...
19 Event::assertNotDispatched(OrderFailedToShip::class);
20 
21 // Assert that no events were dispatched...
22 Event::assertNothingDispatched();
23});
1<?php
2 
3namespace Tests\Feature;
4 
5use App\Events\OrderFailedToShip;
6use App\Events\OrderShipped;
7use Illuminate\Support\Facades\Event;
8use Tests\TestCase;
9 
10class ExampleTest extends TestCase
11{
12 /**
13 * Test order shipping.
14 */
15 public function test_orders_can_be_shipped(): void
16 {
17 Event::fake();
18 
19 // Perform order shipping...
20 
21 // Assert that an event was dispatched...
22 Event::assertDispatched(OrderShipped::class);
23 
24 // Assert an event was dispatched twice...
25 Event::assertDispatched(OrderShipped::class, 2);
26 
27 // Assert an event was not dispatched...
28 Event::assertNotDispatched(OrderFailedToShip::class);
29 
30 // Assert that no events were dispatched...
31 Event::assertNothingDispatched();
32 }
33}

你可以将闭包传递给 assertDispatchedassertNotDispatched 方法,以断言是否调度了通过给定“真值测试”的事件。如果至少调度了一个通过给定真值测试的事件,则断言将成功

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

如果你只想断言事件监听器正在监听给定的事件,你可以使用 assertListening 方法

1Event::assertListening(
2 OrderShipped::class,
3 SendShipmentNotification::class
4);

调用 Event::fake() 后,将不会执行任何事件监听器。因此,如果你的测试使用依赖于事件的模型工厂,例如在模型的 creating 事件期间创建 UUID,你应该在之后使用你的工厂调用 Event::fake()

伪造事件子集

如果你只想伪造特定事件集的事件监听器,你可以将它们传递给 fakefakeFor 方法

1test('orders can be processed', function () {
2 Event::fake([
3 OrderCreated::class,
4 ]);
5 
6 $order = Order::factory()->create();
7 
8 Event::assertDispatched(OrderCreated::class);
9 
10 // Other events are dispatched as normal...
11 $order->update([...]);
12});
1/**
2 * Test order process.
3 */
4public function test_orders_can_be_processed(): void
5{
6 Event::fake([
7 OrderCreated::class,
8 ]);
9 
10 $order = Order::factory()->create();
11 
12 Event::assertDispatched(OrderCreated::class);
13 
14 // Other events are dispatched as normal...
15 $order->update([...]);
16}

你可以使用 except 方法伪造除指定事件集之外的所有事件

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

作用域事件伪造

如果你只想为测试的某一部分伪造事件监听器,你可以使用 fakeFor 方法

1<?php
2 
3use App\Events\OrderCreated;
4use App\Models\Order;
5use Illuminate\Support\Facades\Event;
6 
7test('orders can be processed', function () {
8 $order = Event::fakeFor(function () {
9 $order = Order::factory()->create();
10 
11 Event::assertDispatched(OrderCreated::class);
12 
13 return $order;
14 });
15 
16 // Events are dispatched as normal and observers will run ...
17 $order->update([...]);
18});
1<?php
2 
3namespace Tests\Feature;
4 
5use App\Events\OrderCreated;
6use App\Models\Order;
7use Illuminate\Support\Facades\Event;
8use Tests\TestCase;
9 
10class ExampleTest extends TestCase
11{
12 /**
13 * Test order process.
14 */
15 public function test_orders_can_be_processed(): void
16 {
17 $order = Event::fakeFor(function () {
18 $order = Order::factory()->create();
19 
20 Event::assertDispatched(OrderCreated::class);
21 
22 return $order;
23 });
24 
25 // Events are dispatched as normal and observers will run ...
26 $order->update([...]);
27 }
28}

Laravel 是最有效的方式,用于
构建、部署和监控软件。