* * * * *
[TOC]
## 簡介
Laravel 的事件提供了一個簡單的觀察者實現,能夠訂閱和監聽應用中發生的各種事件。事件類保存在?`app/Events`?目錄中,而這些事件的的監聽器則被保存在?`app/Listeners`?目錄下。這些目錄只有當你使用 Artisan 命令來生成事件和監聽器時才會被自動創建。
事件機制是一種很好的應用解耦方式,因為一個事件可以擁有多個互不依賴的監聽器。例如,如果你希望每次訂單發貨時向用戶發送一個 Slack 通知。你可以簡單地發起一個?`OrderShipped`?事件,讓監聽器接收之后轉化成一個 Slack 通知,這樣你就可以不用把訂單的業務代碼跟 Slack 通知的代碼耦合在一起了。
## 注冊事件和監聽器
Laravel 應用中的?`EventServiceProvider`?有個?`listen`?數組包含所有的事件(鍵)以及事件對應的監聽器(值)來注冊所有的事件監聽器,可以靈活地根據需求來添加事件。例如,讓我們增加一個?`OrderShipped`?事件:
~~~
/**
* 應用程序的事件監聽器映射。
*
* @var array
*/
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) {
//
});
}
~~~
#### 通配符事件監聽器
你可以在注冊監聽器時使用?`*`?通配符參數,這樣能夠在同一個監聽器上捕獲多個事件。通配符監聽器接受事件名稱作為其第一個參數,并將整個事件數據數組作為其第二個參數:
~~~
Event::listen('event.*', function ($eventName, array $data) {
//
});
~~~
## 定義事件
事件類其實就只是一個保存與事件相關的信息的數據容器。例如,假設我們生成的?`OrderShipped`?事件接收一個?[Eloquent ORM](http://www.hmoore.net/tonyyu/laravel_5_6/786272)?對象:
~~~
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use SerializesModels;
public $order;
/**
* 創建一個事件實例。
*
* @param Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
~~~
正像你看到的那樣,這個事件類中沒有包含其它邏輯。它只是一個被構建的?`Order`?對象的容器。如果使用 PHP 的?`serialize`?函數序列化事件對象,事件使用的?`SerializesModels`?trait 將會優雅地序列化任何 Eloquent 模型。
## 定義監聽器
接下來,讓我們看一下例子中事件的監聽器。事件監聽器在?`handle`?方法中接收事件實例。?`event:generate`?命令會自動加載正確的事件類和在?`handle`?加入的類型提示。在?`handle`?方法中,你可以執行任何必要的響應事件的操作:
~~~
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* 創建事件監聽器。
*
* @return void
*/
public function __construct()
{
//
}
/**
* 處理事件。
*
* @param OrderShipped $event
* @return void
*/
public function handle(OrderShipped $event)
{
// 使用 $event->order 來訪問 order ...
}
}
~~~
> {tip} 你的事件監聽器也可以在構造函數中加入任何依賴關系的類型提示。所有的事件監聽器都是通過 Laravel 的?[服務容器](http://www.hmoore.net/tonyyu/laravel_5_6/786056)?來解析的,因此所有的依賴都將會被自動注入。
#### 停止事件傳播
你可以通過在監聽器的?`handle`?方法中返回?`false`?來阻止事件被其他的監聽器獲取。
## 事件監聽器隊列
如果你的監聽器中要執行諸如發送郵件或者進行 HTTP 請求等比較慢的任務,你可以選擇將其交給隊列處理。在開始使用監聽器隊列之前,請確保在你的服務器或本地開發環境中能夠配置并啟動?[隊列](http://www.hmoore.net/tonyyu/laravel_5_6/786248)?監聽器。
要指定監聽器啟動隊列,只需將?`ShouldQueue`?接口添加到監聽器類。由 Artisan 命令?`event:generate`?生成的監聽器已經將此接口導入到當前命名空間中,因此你可以直接使用它:
~~~
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
//
}
~~~
當這個監聽器被事件調用時,事件調度器會自動使用 Laravel 的?[隊列系統](http://www.hmoore.net/tonyyu/laravel_5_6/786248)。如果在隊列中執行監聽器時沒有拋出異常,任務會在執行完成后自動從隊列中刪除。
#### 自定義隊列的連接和名稱
如果你想要自定義事件監聽器使用的隊列的連接和名稱,可以在監聽器類中定義?`$connection`?和?`$queue`?屬性:
~~~
<?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';
}
~~~
### 手動訪問隊列
如果你需要手動訪問監聽器下面隊列任務的?`delete`?和?`release`?方法,你可以添加?`Illuminate\Queue\InteractsWithQueue`?trait 來實現。這個 trait 會默認加載到生成的監聽器中,并提供對這些方法的訪問:
~~~
<?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);
}
}
}
~~~
### 處理失敗任務
事件監聽器的隊列任務可能會失敗,而如果監聽器的隊列任務超過了隊列中定義的最大嘗試次數,則會監聽器上調用?`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;
/**
* 事件處理器
*
* @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)
{
//
}
}
~~~
## 分發事件
如果要分發事件,你可以將事件實例傳遞給輔助函數?`event`。這個函數將會把事件分發到所有已經注冊的監聽器上。因為輔助函數?`event`?是全局可訪問的,所以你可以在應用中的任何地方調用它:
~~~
<?php
namespace App\Http\Controllers;
use App\Order;
use App\Events\OrderShipped;
use App\Http\Controllers\Controller;
class OrderController extends Controller
{
/**
* 運送給定的訂單。
*
* @param int $orderId
* @return Response
*/
public function ship($orderId)
{
$order = Order::findOrFail($orderId);
// 訂單的發貨邏輯...
event(new OrderShipped($order));
}
}
~~~
> {tip} 在測試時,Laravel?[內置的輔助測試函數](http://www.hmoore.net/tonyyu/laravel_5_6/786285#_39)?能不需要實際觸發監聽器就能對事件類型斷言。
## 事件訂閱者
### 編寫事件訂閱者
事件訂閱者是一個可以在自身內部訂閱多個事件的類,即能夠在單個類中定義多個事件處理器。訂閱者應該定義一個?`subscribe`?方法,這個方法接受一個事件分發器的實例。你可以調用給定的事件分發器上的?`listen`?方法來注冊事件監聽器:
~~~
<?php
namespace App\Listeners;
class UserEventSubscriber
{
/**
* 處理用戶登錄事件。
*/
public function onUserLogin($event) {}
/**
* 處理用戶注銷事件。
*/
public function onUserLogout($event) {}
/**
* 為訂閱者注冊監聽器。
*
* @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'
);
}
}
~~~
### 注冊事件訂閱者
訂閱者寫好后,就將其注冊到事件分發器中。你可以在?`EventServiceProvider`?類的?`$subscribe`?屬性中注冊訂閱者。例如,將?`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',
];
}
~~~
- 前言
- 翻譯說明
- 發行說明
- 升級指南
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- Homestead
- Valet
- 部署
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- Facades
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- URL
- Session
- 表單驗證
- 錯誤
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全相關
- 用戶認證
- Passport OAuth 認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試相關
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Scout 全文搜索
- Socialite 社會化登錄