# 事件系統
- [簡介](#introduction)
- [注冊事件 & 監聽器](#registering-events-and-listeners)
- [生成事件 & 監聽器](#generating-events-and-listeners)
- [手動注冊事件](#manually-registering-events)
- [定義事件](#defining-events)
- [定義監聽器](#defining-listeners)
- [事件監聽器隊列](#queued-event-listeners)
- [手動訪問隊列](#manually-accessing-the-queue)
- [處理失敗任務](#handling-failed-jobs)
- [分發事件](#dispatching-events)
- [事件訂閱者](#event-subscribers)
- [編寫事件訂閱者](#writing-event-subscribers)
- [注冊事件訂閱者](#registering-event-subscribers)
<a name="introduction"></a>
## 事件系統介紹
Laravel 的事件提供了一個簡單的觀察者實現,能夠訂閱和監聽應用中發生的各種事件。事件類通常存放在 `app/Events` 目錄下,而這些事件類的監聽器則存放在 `app/Listeners` 目錄下。如果在你的應用中你沒有看到這些目錄,不用擔心,它們會在你使用 Artisan 控制臺命令生成事件與監聽器的時候自動創建。
事件系統為應用各個方面的解耦提供了非常棒的方法,因為單個事件可以擁有多個互不依賴的監聽器。舉個例子,你可能希望每次訂單發貨時向用戶推送一個 Slack 通知。你可以簡單地發起一個可以被監聽器接收并轉化為 Slack 通知的 `OrderShipped` 事件,而不是將訂單處理代碼和 Slack 通知代碼耦合在一起。
<a name="registering-events-and-listeners"></a>
## 注冊事件和監聽器
Laravel 應用中的 `EventServiceProvider` 為注冊所有的事件監聽器提供了一個便利的場所。其中, `listen` 屬性包含了所有事件 (鍵) 以及事件對應的監聽器 (值)的數組。當然,你可以根據應用的需要,添加多個事件到 `listen` 屬性包含的數組中。舉個例子,我們來添加一個 `OrderShipped` 事件:
/**
* 應用程序的事件監聽器映射
*
* @var array
*/
protected $listen = [
'App\Events\OrderShipped' => [
'App\Listeners\SendShipmentNotification',
],
];
<a name="generating-events-and-listeners"></a>
### 生成事件 & 監聽器
當然,手動創建事件和監聽器的文件是件麻煩事。而在這里,你只需要將監聽器和事件添加到 `EventServiceProvider` 中,而后使用 `event:generate` 命令。這個命令會生成在 `EventServiceProvider` 中列出的所有事件和監聽器。當然,已經存在的事件和監聽器將保持不變:
php artisan event:generate
<a name="manually-registering-events"></a>
### 手動注冊事件
通常,事件是在 `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) {
//
});
<a name="defining-events"></a>
## 定義事件
事件類是一個保存與事件相關信息的容器。例如,假設我們生成的 `OrderShipped` 事件接收一個 [Eloquent ORM](/docs/{{version}}/eloquent) 對象:
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
class OrderShipped
{
use SerializesModels;
public $order;
/**
* 創建一個事件實例。
*
* @param \App\Order $order
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
如你所見,這個事件類中沒有包含其它邏輯。它只是一個購買的 `Order` 的實例的容器。如果使用 PHP 的 `serialize` 函數序列化事件對象,事件使用的 `SerializesModels` trait 將會優雅地序列化任何 Eloquent 模型。
<a name="defining-listeners"></a>
## 定義監聽器
接下來,讓我們看一下例子中事件的監聽器。事件監聽器在 `handle` 方法中接收實例。 `event:generate` 命令會自動加載正確的事件類,并且在 `handle` 方法中加入事件的類型提示。在 `handle` 方法中,你可以執行任何必要的響應事件的操作:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
class SendShipmentNotification
{
/**
* 創建事件監聽器。
*
* @return void
*/
public function __construct()
{
//
}
/**
* 處理事件。
*
* @param \App\Events\OrderShipped $event
* @return void
*/
public function handle(OrderShipped $event)
{
// 使用 $event->order 來訪問 order ...
}
}
> {tip} 你的事件監聽器也可以在構造函數中加入任何依賴關系的類型提示。所有的事件監聽器都是通過 Laravel 的 [服務容器](/docs/{{version}}/container) 來解析的,因此所有的依賴都將會被自動注入。
#### 停止事件傳播
有時,你可以通過在監聽器的 `handle` 方法中返回 `false` 來阻止事件被其它的監聽器獲取。
<a name="queued-event-listeners"></a>
## 事件監聽器隊列
如果你的監聽器中要執行諸如發送電子郵件或進行 HTTP 請求等比較慢的任務,你可以選擇將其丟給隊列處理。在開始使用隊列監聽器之前,請確保在你的服務器或者本地開發環境中能夠 [配置隊列](/docs/{{version}}/queues) 并啟動一個隊列監聽器。
要指定監聽器啟動隊列,你可以在監聽器類中增加 `ShouldQueue` 接口。由 Artisan 命令 `event:generate` 生成的監聽器已經將此接口導入到當前命名空間中,因此你可以直接使用:
<?php
namespace App\Listeners;
use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendShipmentNotification implements ShouldQueue
{
//
}
就是這個!當這個監聽器被事件調用時,事件調度器會自動使用 Laravel 的 [隊列系統](/docs/{{version}}/queues)。如果在隊列中執行監聽器時沒有拋出異常,任務會在執行完成后自動從隊列中刪除。
#### 自定義隊列連接 & 隊列名稱
如果你想要自定義事件監聽器所使用的隊列的連接和名稱,你可以在監聽器類中定義 `$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;
}
<a name="manually-accessing-the-queue"></a>
### 手動訪問隊列
如果你需要手動訪問監聽器下面隊列任務的 `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);
}
}
}
<a name="handling-failed-jobs"></a>
### 處理失敗任務
有時事件監聽器的隊列任務可能會失敗。如果監聽器的隊列任務超過了隊列中定義的最大嘗試次數,則會在監聽器上調用 `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)
{
//
}
}
<a name="dispatching-events"></a>
## 分發事件
如果要分發事件,你可以將事件實例傳遞給輔助函數 `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 的 [內置測試輔助函數](/docs/{{version}}/mocking#event-fake) 可以輕松做到這一點。
<a name="event-subscribers"></a>
## 事件訂閱者
<a name="writing-event-subscribers"></a>
### 編寫事件訂閱者
事件訂閱者是可以在自身內部訂閱多個事件的類,即能夠在單個類中定義多個事件處理器。訂閱者應該定義一個 `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'
);
}
}
<a name="registering-event-subscribers"></a>
### 注冊事件訂閱者
在編寫完訂閱者之后,就可以通過事件分發器對訂閱者進行注冊。你可以在 `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',
];
}
- 入門指南
- 安裝
- 部署
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- URL
- Session
- 表單驗證
- 錯誤
- 日志
- 前端開發
- Blade 模板
- 本地化
- 腳手架
- 編譯資源 Mix
- 安全相關
- 用戶認證
- API 認證
- 綜合話題
- 命令行
- 廣播
- 緩存
- 集合
- 事件
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 速查表
- Artisan
- Auth
- Blade
- Cache
- Collection
- Composer
- Config
- Container
- Cookie
- DB
- Environment
- Event
- File
- Helper
- Input
- Lang
- Log
- Model
- Pagination
- Queue
- Redirect
- Request
- Response
- Route
- SSH
- Schema
- Security
- Session
- Storage
- String
- URL
- UnitTest
- Validation
- View