# 鉤子和行為
## 鉤子和行為
ThinkPHP中的行為是一個比較抽象的概念,你可以把行為想象成在應用執行過程中的一個動作。在框架的執行流程中,例如路由檢測是一個行為,靜態緩存是一個行為,用戶權限檢測也是行為,大到業務邏輯,小到瀏覽器檢測、多語言檢測等等都可以當做是一個行為,甚至說你希望給你的網站用戶的第一次訪問彈出`Hello,world!`這些都可以看成是一種行為,把這些行為抽離出來的目的是為了讓你無需改動框架和應用,而在外圍通過擴展或者配置來改變或者增加一些功能。
而不同的行為之間也具有位置共同性,比如,有些行為的作用位置都是在應用執行前,有些行為都是在模板輸出之后,我們把這些行為發生作用的位置稱之為鉤子,當應用程序運行到這個鉤子的時候,就會被攔截下來,統一執行相關的行為,類似于`AOP`編程中的“切面”的概念,給某一個鉤子綁定相關行為就成了一種類`AOP`編程的思想。
一個鉤子可以注冊多個行為,執行到某個鉤子位置后,會按照注冊的順序依次執行相關的行為。但在某些特殊的情況下,你可以設置某個鉤子只能執行一次行為,又或者你可以在一個鉤子的某個行為中返回`false`來強制終止后續的行為執行;一個行為可以同時注冊到多個不同的鉤子上,完全看應用的需求來設計。
鉤子的位置必須是事先設計好的,無論是框架還是應用的,要設置一個鉤子,只需要在相關的位置添加一行代碼(事先需要引入`think\facade\Hook`類):
> #### Hook::listen('鉤子名稱','參數','是否只有一次有效返回值');
除了鉤子名稱之外,其它參數都是可選的,注意`5.1`版本第二個參數不支持引用傳值。
系統核心設計提供了一些可能會需要的鉤子(位置),盡可能的方便應用的擴展而不必改動框架核心,按照執行順序依次如下:
鉤子描述參數`app_init`應用初始化標簽位無`app_dispatch`應用調度標簽位無`app_begin`應用開始標簽位無`module_init`模塊初始化標簽位無`action_begin`控制器開始標簽位當前的callback參數`view_filter`視圖輸出過濾標簽位當前模板渲染輸出內容`app_end`應用結束標簽位當前響應對象實例`log_write`日志write方法標簽位當前寫入的日志信息`log_write_done`日志寫入完成標簽位`response_send`響應發送標簽位當前響應對象`response_end`輸出結束標簽位當前響應對象實例> 其中`log_write`鉤子僅在調用`Log::write`方法的時候執行。
>
> `view_filter`鉤子`v5.1.3+`版本中已經廢除,改用視圖類的`filter`方法過濾。
## 行為定義
行為類的定義很簡單,一般來說只需要定義一個行為入口方法`run`即可,例如:
```
namespace app\index\behavior;
class Test
{
public function run($params)
{
// 行為邏輯
}
}
```
可以在行為方法中使用依賴注入,例如:
```
namespace app\index\behavior;
use think\Request;
class Test
{
public function run(Request $request, $params)
{
// 行為邏輯
}
}
```
行為的入口方法名稱支持自定義,如果需要更改在應用公共文件中添加下面的代碼即可:
```
Hook::portal('portal');
```
入口方法名稱就變成了`portal`。
行為類并不需要繼承任何類,相對比較靈活。如果行為類需要綁定到多個鉤子,可以采用如下定義:
```
namespace app\index\behavior;
class Test
{
public function appInit($params)
{
}
public function appEnd($params)
{
}
}
```
該行為綁定到`app_init`和`app_end`鉤子后 就會調用相關的方法,方法名就是鉤子名稱的駝峰命名(首字母小寫)。
## 行為綁定
行為定義完成后,就需要綁定到某個標簽位置才能生效,否則是不會執行的。
使用`think\facade\Hook`類的add方法注冊行為,例如:
```
// 注冊 app\index\behavior\CheckLang行為類到app_init標簽位
Hook::add('app_init','app\\index\\behavior\\CheckLang');
//注冊 app\admin\behavior\CronRun行為類到app_init標簽位
Hook::add('app_init','app\\admin\\behavior\\CronRun');
```
如果要批量注冊行為的話,可以使用:
```
Hook::add('app_init',['app\\index\\behavior\\CheckAuth','app\\index\\behavior\\CheckLang','app\\admin\\behavior\\CronRun']);
```
當應用運行到`app_init`標簽位的時候,就會依次調用`app\index\behavior\CheckAuth`、`app\index\behavior\CheckLang`和`app\admin\behavior\CronRun`行為。如果其中一個行為中有中止代碼的話則后續不會執行,如果返回`false`則當前標簽位的后續行為將不會執行,但應用將繼續運行。
我們也可以直接在應用目錄下面或者模塊的目錄下面定義`tags.php`文件來統一定義行為,定義格式如下:
```
return [
'app_init'=> [
'app\\index\\behavior\\CheckAuth',
'app\\index\\behavior\\CheckLang'
],
'app_end'=> [
'app\\admin\\behavior\\CronRun'
]
]
```
如果應用目錄下面和模塊目錄下面的`tags.php`都定義了`app_init`的行為綁定的話,會采用合并模式,如果希望覆蓋,那么可以在模塊目錄下面的`tags.php`中定義如下:
```
return [
'app_init'=> [
'app\\index\\behavior\\CheckAuth',
'_overlay'=>true
],
'app_end'=> [
'app\\admin\\behavior\\CronRun'
]
]
```
如果某個行為標簽定義了`'_overlay' =>true` 就表示覆蓋之前的相同標簽下面的行為定義。
## 閉包支持
可以不用定義行為直接把閉包函數綁定到某個標簽位,例如:
```
Hook::add('app_init',function(){
echo 'Hello,world!';
});
```
如果標簽位有傳入參數的話,閉包也可以支持傳入參數,例如:
```
Hook::listen('action_init',$params);
Hook::add('action_init',function($params){
var_dump($params);
});
```
## 直接執行行為
如果需要,你也可以不綁定行為標簽,直接調用某個行為,使用:
```
// 執行 app\index\behavior\CheckAuth行為類的run方法 并引用傳入params參數
$result = Hook::exec('app\\index\\behavior\\CheckAuth',$params);
```
直接執行行為的時候,執行的是run方法,如果需要執行行為類的其它方法,可以使用
```
// 執行 app\index\behavior\CheckAuth行為類的hello方法 并引用傳入params參數
$result = Hook::exec(['app\\index\\behavior\\CheckAuth','hello'], $params);
```
- 序言
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 架構
- 架構總覽
- 入口文件
- URL訪問
- 模塊設計
- 命名空間
- 容器和依賴注入
- Facade
- 鉤子和行為
- 中間件
- 路由
- 路由定義
- 變量規則
- 路由地址
- 閉包支持
- 路由參數
- 路由緩存
- 跨域請求
- 注解路由
- 路由分組
- MISS路由
- 資源路由
- 快捷路由
- 路由別名
- 路由綁定
- 域名路由
- URL生成
- 控制器
- 控制器定義
- 前置操作
- 跳轉和重定向
- 空操作和空控制器
- 分層控制器
- 資源控制器
- 請求
- 請求對象
- 請求信息
- 輸入變量
- 請求類型
- HTTP頭信息
- 偽靜態
- 參數綁定
- 請求緩存
- 響應
- 響應輸出
- 響應參數
- 重定向
- 數據庫
- 連接數據庫
- 查詢構造器
- 查詢數據
- 添加數據
- 更新數據
- 刪除數據
- 查詢表達式
- 鏈式操作
- 聚合查詢
- 時間查詢
- 高級查詢
- 視圖查詢
- JSON字段
- 子查詢
- 原生查詢
- 查詢事件
- 事務操作
- 監聽SQL
- 存儲過程
- 數據集
- 分布式數據庫
- 模型
- 定義
- 新增
- 更新
- 刪除
- 查詢
- JSON字段
- 獲取器
- 修改器
- 自動時間戳
- 只讀字段
- 軟刪除
- 類型轉換
- 數據完成
- 查詢范圍
- 模型輸出
- 模型事件
- 模型關聯
- 一對一關聯
- 一對多關聯
- 遠程一對多
- 多對多關聯
- 多態關聯
- 關聯預載入
- 關聯統計
- 關聯輸出
- 視圖
- 視圖渲染
- 視圖賦值
- 視圖過濾
- 模板引擎
- 模板
- 變量輸出
- 使用函數
- 運算符
- 原樣輸出
- 模板注釋
- 模板布局
- 模板繼承
- 包含文件
- 輸出替換
- 標簽庫
- 內置標簽
- 循環標簽
- 比較標簽
- 條件判斷
- 資源文件加載
- 標簽嵌套
- 原生PHP
- 定義標簽
- 標簽擴展
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 調試模式
- Trace調試
- 性能調試
- SQL調試
- 變量調試
- 遠程調試
- 驗證
- 驗證器
- 驗證規則
- 錯誤信息
- 驗證場景
- 路由驗證
- 內置規則
- 獨立驗證
- 靜態調用
- 表單令牌
- 雜項
- 緩存
- Session
- Cookie
- 多語言
- 分頁
- 上傳
- 命令行
- 啟動內置服務器
- 自動生成目錄結構
- 創建類庫文件
- 生成類庫映射文件
- 清除緩存文件
- 生成配置緩存文件
- 生成數據表字段緩存
- 生成路由映射緩存
- 自定義指令
- 擴展庫
- 驗證碼
- 圖像處理
- Time
- 數據庫遷移工具
- Workerman
- MongoDb
- 單元測試
- 安全和性能
- 安全建議
- 優化建議
- 附錄
- 助手函數
- 升級指導
- 更新日志