新版的事件系統可以看成是`5.1`版本行為系統的升級版,事件系統相比行為系統強大的地方在于事件本身可以是一個類,并且可以更好的支持事件訂閱者。
事件相比較中間件的優勢是事件比中間件更加精準定位(或者說粒度更細),并且更適合一些業務場景的擴展。例如,我們通常會遇到用戶注冊或者登錄后需要做一系列操作,通過事件系統可以做到不侵入原有代碼完成登錄的操作擴展,降低系統的耦合性的同時,也降低了BUG的可能性。
>[danger] 事件系統的所有操作都通過`think\facade\Event`類進行靜態調用
>[danger] `V6.0.3+`版本開始,事件機制不能關閉
## 定義事件
事件系統使用了觀察者模式,提供了解耦應用的更好方式。在你需要監聽事件的位置,例如下面我們在用戶完成登錄操作之后添加如下事件觸發代碼:
```
// 觸發UserLogin事件 用于執行用戶登錄后的一系列操作
Event::trigger('UserLogin');
```
或者使用助手函數
```
event('UserLogin');
```
這里`UserLogin`表示一個事件標識,如果你定義了單獨的事件類,你可以使用事件類名(甚至可以傳入一個事件類實例)。
```
// 直接使用事件類觸發
event('app\event\UserLogin');
```
事件類可以通過命令行快速生成
```
php think make:event UserLogin
```
默認會生成一個`app\event\UserLogin`事件類,也可以指定完整類名生成。
我們可以給事件類添加方法
```
namespace app\event;
use app\model\User;
class UserLogin
{
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
```
一般事件類無需繼承任何其它類。
你可以給事件類綁定一個事件標識,一般建議直接在應用的`event.php`事件定義文件中批量綁定。
```
return [
'bind' => [
'UserLogin' => 'app\event\UserLogin',
// 更多事件綁定
],
];
```
如果你需要動態綁定,可以使用
```
Event::bind(['UserLogin' => 'app\event\UserLogin']);
```
>[danger] ThinkPHP的事件系統不依賴事件類,如果沒有額外的需求,僅通過事件標識也可以使用,省去定義事件類的麻煩。
如果你沒有定義事件類的話,則無需綁定。對于大部分的場景,可能確實不需要定義事件類。
你可以在`event`方法中傳入一個事件參數
```
// user是當前登錄用戶對象實例
event('UserLogin', $user);
```
如果是定義了事件類,可以直接傳入事件對象實例
```
// user是當前登錄用戶對象實例
event(new UserLogin($user));
```
## 事件監聽
你可以手動注冊一個事件監聽
```
Event::listen('UserLogin', function($user) {
//
});
```
或者使用監聽類來執行監聽
```
Event::listen('UserLogin', 'app\listener\UserLogin');
```
可以通過命令行快速生成一個事件監聽類
```
php think make:listener UserLogin
```
默認會生成一個`app\listener\UserLogin`事件監聽類,也可以指定完整類名生成。
事件監聽類只需要定義一個`handle`方法,支持依賴注入。
~~~
<?php
namespace app\listener;
class UserLogin
{
public function handle($user)
{
// 事件監聽處理
}
}
~~~
在`handle`方法中如果返回了`false`,則表示監聽中止,將不再執行該事件后面的監聽。
一般建議直接在事件定義文件中定義對應事件的監聽。
```
return [
'bind' => [
'UserLogin' => 'app\event\UserLogin',
// 更多事件綁定
],
'listen' => [
'UserLogin' => ['app\listener\UserLogin'],
// 更多事件監聽
],
];
```
`V6.0.9+`版本開始,事件監聽可以支持通配符,例如:
```
// 監聽所有的模型事件
Event::listen('model.*', 'app\listener\ModelListen');
```
## 事件訂閱
可以通過事件訂閱機制,在一個監聽器中監聽多個事件,例如通過命令行生成一個事件訂閱者類,
```
php think make:subscribe User
```
默認會生成`app\subscribe\User`類,或者你可以指定完整類名生成。
然后你可以在事件訂閱類中添加不同事件的監聽方法,例如。
~~~
<?php
namespace app\subscribe;
class User
{
public function onUserLogin($user)
{
// UserLogin事件響應處理
}
public function onUserLogout($user)
{
// UserLogout事件響應處理
}
}
~~~
監聽事件的方法命名規范是`on`+事件標識(駝峰命名),如果希望統一添加事件前綴標識,可以定義`eventPrefix`屬性。
~~~
<?php
namespace app\subscribe;
class User
{
protected $eventPrefix = 'User';
public function onLogin($user)
{
// UserLogin事件響應處理
}
public function onLogout($user)
{
// UserLogout事件響應處理
}
}
~~~
如果希望自定義訂閱方式(或者方法規范),可以定義`subscribe`方法實現。
~~~
<?php
namespace app\subscribe;
use think\Event;
class User
{
public function onUserLogin($user)
{
// UserLogin事件響應處理
}
public function onUserLogout($user)
{
// UserLogout事件響應處理
}
public function subscribe(Event $event)
{
$event->listen('UserLogin', [$this,'onUserLogin']);
$event->listen('UserLogout',[$this,'onUserLogout']);
}
}
~~~
然后在事件定義文件注冊事件訂閱者
```
return [
'bind' => [
'UserLogin' => 'app\event\UserLogin',
// 更多事件綁定
],
'listen' => [
'UserLogin' => ['app\listener\UserLogin'],
// 更多事件監聽
],
'subscribe' => [
'app\subscribe\User',
// 更多事件訂閱
],
];
```
如果需要動態注冊,可以使用
```
Event::subscribe('app\subscribe\User');
```
## 內置事件
內置的系統事件包括:
| 事件| 描述 | 參數 |
| --- | --- | --- |
| AppInit | 應用初始化標簽位 | 無 |
| HttpRun | 應用開始標簽位 | 無 |
| HttpEnd | 應用結束標簽位 | 當前響應對象實例 |
| LogWrite | 日志write方法標簽位 | 當前寫入的日志信息 |
| RouteLoaded| 路由加載完成 | 無 |
| LogRecord| 日志記錄`V6.0.8+` | 無 |
>[danger] `AppInit`事件定義必須在全局事件定義文件中定義,其它事件支持在應用的事件定義文件中定義。
原來`5.1`的一些行為標簽已經廢棄,所有取消的標簽都可以使用中間件更好的替代。可以把中間件看成處理請求以及響應輸出相關的特殊事件。事實上,中間件的`handler`方法只是具有特殊的參數以及返回值而已。
數據庫操作的回調也稱為查詢事件,是針對數據庫的CURD操作而設計的回調方法,主要包括:
| 事件 | 描述 |
| --- | --- |
| before\_select | `select`查詢前回調 |
| before\_find | `find`查詢前回調 |
| after\_insert | `insert`操作成功后回調 |
| after\_update | `update`操作成功后回調 |
| after\_delete | `delete`操作成功后回調 |
> 查詢事件的參數就是當前的查詢對象實例。
模型事件包含:
| 鉤子 | 對應操作 |
| --- | --- |
| after\_read | 查詢后 |
| before\_insert | 新增前 |
| after\_insert | 新增后 |
| before\_update | 更新前 |
| after\_update | 更新后 |
| before\_write | 寫入前 |
| after\_write | 寫入后 |
| before\_delete | 刪除前 |
| after\_delete | 刪除后 |
`before_write`和`after_write`事件無論是新增還是更新都會執行。
> 模型事件方法的參數就是當前的模型對象實例。
- 序言
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 架構
- 請求流程
- 架構總覽
- 入口文件
- 多應用模式
- URL訪問
- 容器和依賴注入
- 服務
- 門面
- 中間件
- 事件
- 路由
- 路由定義
- 變量規則
- 路由地址
- 路由參數
- 路由中間件
- 路由分組
- 資源路由
- 注解路由
- 路由綁定
- 域名路由
- MISS路由
- 跨域請求
- URL生成
- 控制器
- 控制器定義
- 基礎控制器
- 空控制器
- 資源控制器
- 控制器中間件
- 請求
- 請求對象
- 請求信息
- 輸入變量
- 請求類型
- HTTP頭信息
- 偽靜態
- 參數綁定
- 請求緩存
- 響應
- 響應輸出
- 響應參數
- 重定向
- 文件下載
- 數據庫
- 連接數據庫
- 分布式數據庫
- 查詢構造器
- 查詢數據
- 添加數據
- 更新數據
- 刪除數據
- 查詢表達式
- 鏈式操作
- where
- table
- alias
- field
- strict
- limit
- page
- order
- group
- having
- join
- union
- distinct
- lock
- cache
- cacheAlways
- comment
- fetchSql
- force
- partition
- failException
- sequence
- replace
- extra
- duplicate
- procedure
- 聚合查詢
- 分頁查詢
- 時間查詢
- 高級查詢
- 視圖查詢
- JSON字段
- 子查詢
- 原生查詢
- 獲取查詢參數
- 查詢事件
- 獲取器
- 事務操作
- 存儲過程
- 數據集
- 數據庫驅動
- 模型
- 定義
- 模型字段
- 新增
- 更新
- 刪除
- 查詢
- 查詢范圍
- JSON字段
- 獲取器
- 修改器
- 搜索器
- 數據集
- 自動時間戳
- 只讀字段
- 軟刪除
- 類型轉換
- 模型輸出
- 模型事件
- 模型關聯
- 一對一關聯
- 一對多關聯
- 遠程一對多
- 遠程一對一
- 多對多關聯
- 多態關聯
- 關聯預載入
- 關聯統計
- 關聯輸出
- 虛擬模型
- 視圖
- 模板變量
- 視圖過濾
- 模板渲染
- 模板引擎
- 視圖驅動
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 調試模式
- Trace調試
- SQL調試
- 變量調試
- 遠程調試
- 驗證
- 驗證器
- 驗證規則
- 錯誤信息
- 驗證場景
- 路由驗證
- 內置規則
- 表單令牌
- 注解驗證
- 雜項
- 緩存
- Session
- Cookie
- 多語言
- 上傳
- 命令行
- 啟動內置服務器
- 查看版本
- 自動生成應用目錄
- 創建類庫文件
- 清除緩存文件
- 生成數據表字段緩存
- 生成路由映射緩存
- 輸出路由定義
- 自定義指令
- Debug輸出級別
- 擴展庫
- 數據庫遷移工具
- Workerman
- think助手工具庫
- 驗證碼
- Swoole
- 附錄
- 助手函數
- 升級指導
- 更新日志