[ Laravel 5.8 文件 ] 進階系列 —— 事件
簡介
Laravel 事件提供了簡單的觀察者模式實現,允許你訂閱和監聽應用中的事件。事件類通常存放在app/Events
目錄,監聽器存放在app/Listeners
。如果你在應用中沒有看到這些目錄,不要擔心,它們會在你使用 Artisan 命令生成事件和監聽器的時候自動建立。
事件為應用功能模組解耦提供了行之有效的解決辦法,因為單個事件可以有多個監聽器而這些監聽器之間並不相互依賴。例如,你可能想要在每次訂單傳送時給使用者傳送一個 Slack 通知,有了事件之後,你大可不必將訂單處理程式碼和 Slack 通知程式碼耦合在一起,而只需要簡單觸發一個可以被監聽器接收並處理為 Slack 通知的OrderShipped
事件即可。
註冊事件/監聽器
Laravel 自帶的EventServiceProvider
為事件監聽器註冊提供了方便之所。其中的listen
屬性包含了事件(鍵)和對應監聽器(值)陣列。如果應用需要,你可以新增多個事件到該陣列。下面讓我們新增一個OrderShipped
事件:
/** * 應用的事件監聽器對映. * * @var array * @translator laravelacademy.org */ protected $listen = [ 'App\Events\OrderShipped' => [ 'App\Listeners\SendShipmentNotification', ], ];
生成事件/監聽器類
當然,手動為每個事件和監聽器建立檔案是很笨重的,取而代之地,我們只需簡單新增監聽器和事件到EventServiceProvider
然後執行event:generate
命令。該命令將會生成羅列在EventServiceProvider
中的所有事件和監聽器。當然,已存在的事件和監聽器不會被重複建立:
php artisan event:generate
手動註冊事件
通常,我們需要通過EventServiceProvider
的$listen
陣列註冊事件,此外,你還可以在EventServiceProvider
的boot
方法中手動註冊基於閉包的事件:
/** * 註冊應用的其它事件. * * @return void */ public function boot() { parent::boot(); Event::listen('event.name', function ($foo, $bar) { // }); }
萬用字元事件監聽器
你甚至還可以使用萬用字元*
來註冊監聽器,這樣就可以通過同一個監聽器捕獲多個事件。萬用字元監聽器接收整個事件資料陣列作為引數:
$events->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; /** * 建立一個新的事件例項. * * @paramOrder$order * @return void */ public function __construct(Order $order) { $this->order = $order; } }
正如你所看到的,該事件類不包含任何特定邏輯,只是一個存放被購買的Order
物件的容器,如果事件物件被序列化的話,事件使用的SerializesModels
trait 將會使用 PHP 的serialize
函式序列化所有 Eloquent 模型。
定義監聽器
接下來,讓我們看看示例事件的監聽器,事件監聽器在handle
方法中接收事件例項,event:generate
命令將會自動在handle
方法中匯入相應的事件類和型別提示事件。在handle
方法內,你可以執行任何需要的邏輯以響應事件:
<?php namespace App\Listeners; use App\Events\OrderShipped; class SendShipmentNotification { /** * 建立事件監聽器. * * @return void */ public function __construct() { // } /** * 處理事件. * * @paramOrderShipped$event * @return void */ public function handle(OrderShipped $event) { // 使用 $event->order 發訪問訂單... } }
注:事件監聽器還可以在構造器中型別提示任何需要的依賴,所有事件監聽器通過服務容器解析,所以依賴會自動注入。
停止事件繼續往下傳播
有時候,你希望停止事件被傳播到其它監聽器,你可以通過從監聽器的handle
方法中返回false
來實現。
事件監聽器佇列
如果監聽器將要執行耗時任務比如傳送郵件或者傳送 HTTP 請求,那麼將監聽器放到佇列是一個不錯的選擇。在佇列化監聽器之前,確保已經配置好佇列並且在伺服器或本地環境啟動一個佇列監聽器。
要指定某個監聽器需要放到佇列,只需要讓監聽器類實現ShouldQueue
介面即可,通過 Artisan 命令event:generate
生成的監聽器類已經將這個介面匯入當前名稱空間,所有你可以直接拿來使用:
<?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 { /** * 任務將被推送到的連線名稱. * * @var string|null */ public $connection = 'sqs'; /** * 任務將被推送到的連線名稱. * * @var string|null */ public $queue = 'listeners'; /** * 任務被處理之前的延遲時間(秒) * * @var int */ public $delay = 60; }
手動訪問佇列
如果你需要手動訪問底層佇列任務的delete
和release
方法,在生成的監聽器中,預設匯入的Illuminate\Queue\InteractsWithQueue
trait 為這兩個方法提供了訪問許可權:
<?php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue; public function handle(OrderShipped $event) { if (true) { $this->release(30); } } }
處理失敗任務
有時候佇列中的事件監聽器可能會執行失敗。如果佇列中的監聽器任務執行時超出了佇列程序定義的最大嘗試次數,監聽器上的failed
方法會被呼叫,failed
方法接收事件例項和導致失敗的異常:
<?php namespace App\Listeners; use App\Events\OrderShipped; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class SendShipmentNotification implements ShouldQueue { use InteractsWithQueue; public function handle(OrderShipped $event) { // } public function failed(OrderShipped $event, $exception) { // } }
分發事件
要分發一個事件,可以傳遞事件例項到輔助函式event
,這個輔助函式會分發事件到所有註冊的監聽器。由於輔助函式event
全域性有效,所以可以在應用的任何地方呼叫它:
<?php namespace App\Http\Controllers; use App\Order; use App\Events\OrderShipped; use App\Http\Controllers\Controller; class OrderController extends Controller { /** * 處理給定訂單. * * @paramint$orderId * @return Response */ public function ship($orderId) { $order = Order::findOrFail($orderId); // 訂單處理邏輯... event(new OrderShipped($order)); } }
注:測試的時候,只需要斷言特定事件被分發,無需真正觸發監聽器,Laravel自帶的測試函式讓這一實現輕而易舉。
事件訂閱者
編寫事件訂閱者
事件訂閱者是指那些在類本身中訂閱多個事件的類,通過事件訂閱者你可以在單個類中定義多個事件處理器。訂閱者需要定義一個subscribe
方法,該方法中傳入一個事件分發器例項。你可以在給定的分發器中呼叫listen
方法註冊事件監聽器:
<?php namespace App\Listeners; class UserEventSubscriber { /** * 處理使用者登入事件. * @translator laravelacademy.org */ public function onUserLogin($event) {} /** * 處理使用者退出事件. */ public function onUserLogout($event) {} /** * 為訂閱者註冊監聽器. * * @paramIlluminate\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' ); } }
註冊事件訂閱者
編寫好訂閱者之後,就可以通過事件分發器對訂閱者進行註冊,你可以使用EventServiceProvider
提供的$subcribe
屬性來註冊訂閱者。例如,讓我們新增一個UserEventSubscriber
:
<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; class EventServiceProvider extends ServiceProvider { /** * 應用的事件監聽器對映. * * @var array */ protected $listen = [ // ]; /** * 要註冊的訂閱者類. * * @var array */ protected $subscribe = [ 'App\Listeners\UserEventSubscriber', ]; }