事件
介紹
Laravel 的事件提供了一個簡單的觀察者模式實現,讓您可以訂閱和監聽應用程式內發生的各種事件。事件類別通常儲存在 app/Events
目錄中,而它們的監聽器則儲存在 app/Listeners
中。如果您在應用程式中沒有看到這些目錄,請別擔心,因為當您使用 Artisan 控制台命令生成事件和監聽器時,它們將會自動建立。
事件是解耦應用程式各個方面的絕佳方式,因為單一事件可以有多個互不依賴的監聽器。例如,您可能希望在每次訂單出貨時向您的使用者發送 Slack 通知。您可以發起一個 App\Events\OrderShipped
事件,而不是將您的訂單處理程式碼耦合到 Slack 通知程式碼,監聽器可以接收該事件並使用它來派發 Slack 通知。
生成事件和監聽器
若要快速生成事件和監聽器,您可以使用 make:event
和 make:listener
Artisan 命令
1php artisan make:event PodcastProcessed2 3php artisan make:listener SendPodcastNotification --event=PodcastProcessed
為了方便起見,您也可以在不使用額外參數的情況下調用 make:event
和 make:listener
Artisan 命令。當您這樣做時,Laravel 將自動提示您輸入類別名稱,以及在建立監聽器時,它應該監聽的事件
1php artisan make:event2 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): void5{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
生產環境中的事件探索
為了提升應用程式的速度,您應該使用 optimize
或 event:cache
Artisan 命令快取應用程式所有監聽器的清單。通常,此命令應作為應用程式部署流程的一部分運行。此清單將被框架用於加速事件註冊過程。event:clear
命令可用於銷毀事件快取。
手動註冊事件
使用 Event
facade,您可以在應用程式 AppServiceProvider
的 boot
方法中手動註冊事件及其對應的監聽器
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
Closure 監聽器
通常,監聽器被定義為類別;但是,您也可以在應用程式 AppServiceProvider
的 boot
方法中手動註冊基於 Closure 的事件監聽器
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}
可佇列的匿名事件監聽器
當註冊基於 Closure 的事件監聽器時,您可以將監聽器 Closure 包裹在 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}
就像佇列任務一樣,您可以使用 onConnection
、onQueue
和 delay
方法來自訂佇列監聽器的執行
1Event::listen(queueable(function (PodcastProcessed $event) {2 // ...3})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));
如果您想處理匿名佇列監聽器失敗,您可以在定義 queueable
監聽器時,為 catch
方法提供一個 Closure。這個 Closure 將接收事件實例和導致監聽器失敗的 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 OrderShipped11{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
方法中接收事件實例。當調用 make:listener
Artisan 命令並使用 --event
選項時,它會自動導入正確的事件類別,並在 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): void18 {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|null14 */15 public $connection = 'sqs';16 17 /**18 * The name of the queue the job should be sent to.19 *20 * @var string|null21 */22 public $queue = 'listeners';23 24 /**25 * The time (seconds) before the job should be processed.26 *27 * @var int28 */29 public $delay = 60;30}
如果您想在運行時定義監聽器的佇列連線、佇列名稱或延遲,您可以在監聽器上定義 viaConnection
、viaQueue
或 withDelay
方法。
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(): string13{14 return 'listeners';15}16 17/**18 * Get the number of seconds before the job should be processed.19 */20public function withDelay(OrderShipped $event): int21{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): void14 {15 // ...16 }17 18 /**19 * Determine whether the listener should be queued.20 */21 public function shouldQueue(OrderCreated $event): bool22 {23 return $event->order->subtotal >= 5000;24 }25}
手動與佇列互動
如果您需要手動訪問監聽器底層佇列任務的 delete
和 release
方法,您可以使用 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 ShouldQueue10{11 use InteractsWithQueue;12 13 /**14 * Handle the event.15 */16 public function handle(OrderShipped $event): void17 {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 ShouldQueue11{12 use InteractsWithQueue;13 14 /**15 * Handle the event.16 */17 public function handle(OrderShipped $event): void18 {19 // ...20 }21 22 /**23 * Handle a job failure.24 */25 public function failed(OrderShipped $event, Throwable $exception): void26 {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 ShouldQueue10{11 use InteractsWithQueue;12 13 /**14 * The number of times the queued listener may be attempted.15 *16 * @var int17 */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(): DateTime7{8 return now()->addMinutes(5);9}
指定佇列監聽器退避策略
如果您想設定 Laravel 在重試遇到例外的監聽器之前應等待多少秒,您可以透過在監聽器類別上定義 backoff
屬性來實現。
1/**2 * The number of seconds to wait before retrying the queued listener.3 *4 * @var int5 */6public $backoff = 3;
如果您需要更複雜的邏輯來確定監聽器的退避時間,您可以在監聽器類別上定義 backoff
方法。
1/**2 * Calculate the number of seconds to wait before retrying the queued listener.3 */4public function backoff(): int5{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(): array7{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 Controller12{13 /**14 * Ship the given order.15 */16 public function store(Request $request): RedirectResponse17 {18 $order = Order::findOrFail($request->order_id);19 20 // Order shipment logic...21 22 OrderShipped::dispatch($order);23 24 return redirect('/orders');25 }26}
如果您想有條件地派發事件,可以使用 dispatchIf
和 dispatchUnless
方法。
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 ShouldDispatchAfterCommit12{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 UserEventSubscriber10{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): void25 {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 UserEventSubscriber10{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): array27 {28 return [29 Login::class => 'handleUserLogin',30 Logout::class => 'handleUserLogout',31 ];32 }33}
註冊事件訂閱者
在編寫訂閱者之後,如果它們遵循 Laravel 的 事件探索慣例,Laravel 將自動註冊訂閱者中的處理程式方法。否則,您可以使用 Event
facade 的 subscribe
方法手動註冊您的訂閱者。通常,這應該在應用程式 AppServiceProvider
的 boot
方法中完成。
1<?php 2 3namespace App\Providers; 4 5use App\Listeners\UserEventSubscriber; 6use Illuminate\Support\Facades\Event; 7use Illuminate\Support\ServiceProvider; 8 9class AppServiceProvider extends ServiceProvider10{11 /**12 * Bootstrap any application services.13 */14 public function boot(): void15 {16 Event::subscribe(UserEventSubscriber::class);17 }18}
測試
在測試派發事件的程式碼時,您可能希望指示 Laravel 不要實際執行事件的監聽器,因為監聽器的程式碼可以直接且獨立於派發對應事件的程式碼進行測試。當然,若要測試監聽器本身,您可以實例化監聽器實例並在測試中直接調用 handle
方法。
使用 Event
facade 的 fake
方法,您可以防止監聽器執行、執行受測程式碼,然後使用 assertDispatched
、assertNotDispatched
和 assertNothingDispatched
方法斷言應用程式派發了哪些事件。
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 TestCase11{12 /**13 * Test order shipping.14 */15 public function test_orders_can_be_shipped(): void16 {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}
您可以將 Closure 傳遞給 assertDispatched
或 assertNotDispatched
方法,以斷言已派發通過給定「真值測試」的事件。如果至少派發了一個通過給定真值測試的事件,則斷言將成功。
1Event::assertDispatched(function (OrderShipped $event) use ($order) {2 return $event->order->id === $order->id;3});
如果您只想斷言事件監聽器正在監聽給定事件,您可以使用 assertListening
方法。
1Event::assertListening(2 OrderShipped::class,3 SendShipmentNotification::class4);
在調用 Event::fake()
之後,將不會執行任何事件監聽器。因此,如果您的測試使用依賴事件的模型工廠(例如在模型的 creating
事件期間建立 UUID),您應該在使用工廠之後調用 Event::fake()
。
偽造事件子集
如果您只想偽造特定事件集的事件監聽器,您可以將它們傳遞給 fake
或 fakeFor
方法。
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 TestCase11{12 /**13 * Test order process.14 */15 public function test_orders_can_be_processed(): void16 {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}