事件系統(tǒng)
事件系統(tǒng)
事件系統(tǒng)
事件系統(tǒng)介紹
Laravel 的事件提供了一個簡單的觀察者實現(xiàn),能夠訂閱和監(jiān)聽應用中發(fā)生的各種事件。事件類通常存放在 app/Events
目錄下,而這些事件類的監(jiān)聽器則存放在 app/Listeners
目錄下。如果在你的應用中你沒有看到這些目錄,不用擔心,它們會在你使用 Artisan 控制臺命令生成事件與監(jiān)聽器的時候自動創(chuàng)建。
事件系統(tǒng)為應用各個方面的解耦提供了非常棒的方法,因為單個事件可以擁有多個互不依賴的監(jiān)聽器。舉個例子,你可能希望每次訂單發(fā)貨時向用戶推送一個 Slack 通知。你可以簡單地發(fā)起一個可以被監(jiān)聽器接收并轉化為 Slack 通知的 OrderShipped
事件,而不是將訂單處理代碼和 Slack 通知代碼耦合在一起。
注冊事件和監(jiān)聽器
Laravel 應用中的 EventServiceProvider
為注冊所有的事件監(jiān)聽器提供了一個便利的場所。其中, listen
屬性包含了所有事件 (鍵) 以及事件對應的監(jiān)聽器 (值) 的數(shù)組。當然,你可以根據應用的需要,添加多個事件到 listen
屬性包含的數(shù)組中。舉個例子,我們來添加一個 OrderShipped
事件:
/** * 應用程序的事件監(jiān)聽器映射 * * @var array */ protected $listen = [ 'App\Events\OrderShipped' => [ 'App\Listeners\SendShipmentNotification', ], ];
生成事件 & 監(jiān)聽器
當然,手動創(chuàng)建事件和監(jiān)聽器的文件是件麻煩事。而在這里,你只需要將監(jiān)聽器和事件添加到 EventServiceProvider
中,而后使用 event:generate
命令。這個命令會生成在 EventServiceProvider
中列出的所有事件和監(jiān)聽器。當然,已經存在的事件和監(jiān)聽器將保持不變:
php artisan event:generate
手動注冊事件
通常,事件是在 EventServiceProvider
的 $listen
數(shù)組中注冊;然而,你也可以在 EventServiceProvider
的 boot
方法中手動注冊基于閉包的這些事件:
/** * 注冊應用中的其它事件 * * @return void */ public function boot(){ parent::boot(); Event::listen('event.name', function ($foo, $bar) { // }); }
通配符事件監(jiān)聽器
你可以在注冊監(jiān)聽器時使用 *
作為通配符參數(shù),這樣可以在同一個監(jiān)聽器上捕獲多個事件。通配符監(jiān)聽器接收事件名作為其第一個參數(shù),并將整個事件數(shù)據數(shù)組作為其第二個參數(shù):
Event::listen('event.*', function ($eventName, array $data) { // });
定義事件
事件類是一個保存與事件相關信息的容器。例如,假設我們生成的 OrderShipped
事件接收一個 Eloquent ORM 對象:
<?php namespace App\Events; use App\Order; use Illuminate\Queue\SerializesModels; class OrderShipped{ use SerializesModels; public $order; /** * 創(chuàng)建一個事件實例。 * * @param \App\Order $order * @return void */ public function __construct(Order $order) { $this->order = $order; } }
如你所見,這個事件類中沒有包含其它邏輯。它只是一個購買的 Order
的實例的容器。如果使用 PHP 的 serialize
函數(shù)序列化事件對象,事件使用的 SerializesModels
trait 將會優(yōu)雅地序列化任何 Eloquent 模型。
定義監(jiān)聽器
接下來,讓我們看一下例子中事件的監(jiān)聽器。事件監(jiān)聽器在 handle
方法中接收實例。 event:generate
命令會自動加載正確的事件類,并且在 handle
方法中加入事件的類型提示。在 handle
方法中,你可以執(zhí)行任何必要的響應事件的操作:
<?php namespace App\Listeners; use App\Events\OrderShipped; class SendShipmentNotification{ /** * 創(chuàng)建事件監(jiān)聽器。 * * @return void */ public function __construct() { // } /** * 處理事件。 * * @param \App\Events\OrderShipped $event * @return void */ public function handle(OrderShipped $event) { // 使用 $event->order 來訪問 order ... } }
{tip} 你的事件監(jiān)聽器也可以在構造函數(shù)中加入任何依賴關系的類型提示。所有的事件監(jiān)聽器都是通過 Laravel 的 服務容器 來解析的,因此所有的依賴都將會被自動注入。
停止事件傳播
有時,你可以通過在監(jiān)聽器的 handle
方法中返回 false
來阻止事件被其它的監(jiān)聽器獲取。
事件監(jiān)聽器隊列
如果你的監(jiān)聽器中要執(zhí)行諸如發(fā)送電子郵件或進行 HTTP 請求等比較慢的任務,你可以選擇將其丟給隊列處理。在開始使用隊列監(jiān)聽器之前,請確保在你的服務器或者本地開發(fā)環(huán)境中能夠 配置隊列 并啟動一個隊列監(jiān)聽器。
要指定監(jiān)聽器啟動隊列,你可以在監(jiān)聽器類中增加 ShouldQueue
接口。由 Artisan 命令 event:generate
生成的監(jiān)聽器已經將此接口導入到當前命名空間中,因此你可以直接使用:
<?php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue{ // }
就是這個!當這個監(jiān)聽器被事件調用時,事件調度器會自動使用 Laravel 的 隊列系統(tǒng)。如果在隊列中執(zhí)行監(jiān)聽器時沒有拋出異常,任務會在執(zhí)行完成后自動從隊列中刪除。
自定義隊列連接 & 隊列名稱
如果你想要自定義事件監(jiān)聽器所使用的隊列的連接和名稱,你可以在監(jiān)聽器類中定義 $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; }
手動訪問隊列
如果你需要手動訪問監(jiān)聽器下面隊列任務的 delete
和 release
方法,你可以通過使用 Illuminate\Queue\InteractsWithQueue
trait 來實現(xiàn)。這個 trait 會默認加載到生成的監(jiān)聽器中,并提供對這些方法的訪問:
<?php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue{ use InteractsWithQueue; /** * 處理事件。 * * @param \App\Events\OrderShipped $event * @return void */ public function handle(OrderShipped $event) { if (true) { $this->release(30); } } }
處理失敗任務
有時事件監(jiān)聽器的隊列任務可能會失敗。如果監(jiān)聽器的隊列任務超過了隊列中定義的最大嘗試次數(shù),則會在監(jiān)聽器上調用 failed
方法。 failed
方法接收事件實例和導致失敗的異常作為參數(shù):
<?php namespace App\Listeners;use App\Events\OrderShipped; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue{ use InteractsWithQueue; /** * 處理事件。 * * @param \App\Events\OrderShipped $event * @return void */ public function handle(OrderShipped $event) { // } /** * 處理失敗任務。 * * @param \App\Events\OrderShipped $event * @param \Exception $exception * @return void */ public function failed(OrderShipped $event, $exception) { // } }
分發(fā)事件
如果要分發(fā)事件,你可以將事件實例傳遞給輔助函數(shù) event
。該輔助函數(shù)將會把事件分發(fā)到所有該事件相應的已經注冊了的監(jiān)聽器上。 event
輔助函數(shù)可以全局使用,你可以在應用中的任何位置進行調用:
<?php namespace App\Http\Controllers; use App\Order;use App\Events\OrderShipped; use App\Http\Controllers\Controller; class OrderController extends Controller{ /** * 將傳遞過來的訂單發(fā)貨 * * @param int $orderId * @return Response */ public function ship($orderId) { $order = Order::findOrFail($orderId); // 訂單發(fā)貨邏輯 ... event(new OrderShipped($order)); } }
{tip} 在測試時,只需要斷言特定事件被分發(fā),而不需要真正地觸發(fā)監(jiān)聽器。 Laravel 的 內置測試輔助函數(shù) 可以輕松做到這一點。
事件訂閱者
編寫事件訂閱者
事件訂閱者是可以在自身內部訂閱多個事件的類,即能夠在單個類中定義多個事件處理器。訂閱者應該定義一個 subscribe
方法,這個方法接收一個事件分發(fā)器實例。你可以調用給定事件分發(fā)器上的 listen
方法來注冊事件監(jiān)聽器:
<?php namespace App\Listeners; class UserEventSubscriber{ /** * 處理用戶登錄事件。 */ public function onUserLogin($event) {} /** * 處理用戶注銷事件。 */ public function onUserLogout($event) {} /** * 為訂閱者注冊監(jiān)聽器 * * @param \Illuminate\Events\Dispatcher $events */ public function subscribe($events) { $events->listen( 'Illuminate\Auth\Events\Login', 'App\Listeners\UserEventSubscriber@onUserLogin' ); $events->listen( 'Illuminate\Auth\Events\Logout', 'App\Listeners\UserEventSubscriber@onUserLogout' ); } }
注冊事件訂閱者
在編寫完訂閱者之后,就可以通過事件分發(fā)器對訂閱者進行注冊。你可以在 EventServiceProvider
中的 $subscribe
屬性中注冊訂閱者。例如,讓我們將 UserEventSubscriber
添加到數(shù)組列表中:
<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider{ /** * 應用中事件監(jiān)聽器的映射。 * * @var array */ protected $listen = [ // ]; /** * 需要注冊的訂閱者類。 * * @var array */ protected $subscribe = [ 'App\Listeners\UserEventSubscriber', ]; }