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