# Laravel 的事件系統
- [簡介](#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 事件機制實現了一個簡單的觀察者模式,讓我們可以訂閱和監聽應用中出現的各種事件。事件類 (Event) 類通常保存在 `app/Events` 目錄下,而它們的監聽類 (Listener) 類被保存在 `app/Listeners` 目錄下。如果你在應用中看不到這些文件夾也不要擔心,因為當你使用 Artisan 命令來生成事件和監聽器時他們會被自動創建。
事件機制是一種很好的應用解耦方式,因為一個事件可以擁有多個互不依賴的監聽器。例如,每次把用戶的訂單發完貨后都希望給他發個 Slack 通知。這時候你可以發起一個 `OrderShipped` 事件,它會被監聽器接收到再傳遞給 Slack 通知模塊,這樣你就不用把訂單處理的代碼跟 Slack 通知的代碼耦合在一起了。
<a name="registering-events-and-listeners"></a>
## 注冊事件和監聽器
Laravel 應用中的 `EventServiceProvider` 提供了一個很方便的地方來注冊所有的事件監聽器。它的 `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 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 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` 屬性。
<?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';
}
<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;
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;
public function handle(OrderShipped $event)
{
//
}
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#mocking-events) 能讓這件事變得很容易。
<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',
];
}
---
> {note} 歡迎任何形式的轉載,但請務必注明出處,尊重他人勞動共創開源社區。
>
> 轉載請注明:本文檔由 Laravel China 社區 [laravel-china.org](https://laravel-china.org) 組織翻譯,詳見 [翻譯召集帖](https://laravel-china.org/topics/5756/laravel-55-document-translation-call-come-and-join-the-translation)。
>
> 文檔永久地址: https://d.laravel-china.org
- 說明
- 翻譯說明
- 發行說明
- 升級說明
- 貢獻導引
- 入門指南
- 安裝
- 配置信息
- 文件夾結構
- HomeStead
- Valet
- 核心架構
- 請求周期
- 服務容器
- 服務提供者
- 門面(Facades)
- Contracts
- 基礎功能
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 重定向
- Session
- 表單驗證
- 錯誤與日志
- 前端開發
- Blade 模板
- 本地化
- 前端指南
- 編輯資源 Mix
- 安全
- 用戶認證
- API認證
- 用戶授權
- 加密解密
- 哈希
- 重置密碼
- 綜合話題
- Artisan 命令行
- 廣播系統
- 緩存系統
- 集合
- 事件系統
- 文件存儲
- 輔助函數
- 郵件發送
- 消息通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫
- 快速入門
- 查詢構造器
- 分頁
- 數據庫遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 模型關聯
- Eloquent 集合
- 修改器
- API 資源
- 序列化
- 測試
- 快速入門
- HTTP 測試
- 瀏覽器測試 Dusk
- 數據庫測試
- 測試模擬器
- 官方擴展包
- Cashier 交易工具包
- Envoy 部署工具
- Horizon
- Passport OAuth 認證
- Scout 全文搜索
- Socialite 社交化登錄
- 交流說明