## 鉤子和行為
行為是一個比較抽象的概念,可以把行為理解成在應用執行過程中的一個動作。行為抽離出來的目的是為了讓開發者無需改動框架和應用,而在外圍通過擴展或者配置來改變或新增一些功能。
我們把行為作用的位置稱做為鉤子,當應用程序運行到這個鉤子的時候,就會被攔截下來,統一執行相關的行為,類似于AOP編程中的切面的概念。給某一個鉤子綁定相關行為就成了一種類AOP編程的思想。
### **鉤子和行為的加載**
首頁是在App類應用初始化的時候,加載行為的擴展文件(tags.php);
```
// 加載行為擴展文件
if (is_file($path . 'tags.php')) {
$tags = include $path . 'tags.php';
if (is_array($tags)) {
$this->hook->import($tags);
}
}
```
是調用Hook類的import方法導入插件的。顯示遍歷數組,然后對每個數組元素執行Hook的add方法,將動態的添加行為擴展到某個標簽
```
public function import(array $tags, $recursive = true)
{
if ($recursive) {
foreach ($tags as $tag => $behavior) {
$this->add($tag, $behavior);
}
} else {
$this->tags = $tags + $this->tags;
}
}
```
Hook的tags就是存放鉤子和行為的映射。最終是通過add方法,將 擴展文件中定義的鉤子行為映射存儲到Hook的tags屬性中。
```
public function add($tag, $behavior, $first = false)
{
isset($this->tags[$tag]) || $this->tags[$tag] = [];
if (is_array($behavior) && !is_callable($behavior)) {
// 是否覆蓋
if (!array_key_exists('_overlay', $behavior)) {
$this->tags[$tag] = array_merge($this->tags[$tag], $behavior);
} else {
unset($behavior['_overlay']);
$this->tags[$tag] = $behavior;
}
} elseif ($first) {
// 添加到開頭
array_unshift($this->tags[$tag], $behavior);
} else {
$this->tags[$tag][] = $behavior;
}
}
```
> 分析add方法。該方法是動態的將鉤子和行為的綁定關系添加到Hook的tags屬性中。方法中判斷$behavior數組是否存在key`_oerlay`。若存在,則覆蓋某個鉤子上的行為,若不存在,則合并鉤子上的行為。如果應用目錄下面和模塊目錄下面的`tags.php`都定義了`app_init`的行為綁定的話,會采用合并模式,如果希望覆蓋,那么可以在模塊目錄下面的`tags.php`中定義如下:
```
return [
'app_init'=> [
'app\\index\\behavior\\CheckAuth',
'_overlay'=>true // 覆蓋
],
'app_end'=> [
'app\\admin\\behavior\\CronRun'
]
]
```
> 繼續分析add方法。看一下add方法的第三個參數,該參數是設置是否將該行為添加到某個鉤子的第一位。應為行為執行是按照順序執行的。所以添加到第一位的行為,優先執行。
### **行為的觸發**
觸發行為是通過Hook類中的listen方法操作的。該方法傳入一個標簽名,然后Hook類根據這個標簽名找到這個標簽上面所綁定行為,循環遍歷,依次執行。
```
public function listen($tag, $params = null, $once = false)
{
$results = [];
$tags = $this->get($tag);
var_dump($tags);
foreach ($tags as $key => $name) {
$results[$key] = $this->execTag($name, $tag, $params);
// 若返回false,則結束循環,不執行下一個行為
if (false === $results[$key] || (!is_null($results[$key]) && $once)) {
break;
}
}
return $once ? end($results) : $results;
}
```
若行為中有和標簽名相同的方法,則該方法就是行為的入口方法。若沒有,則入口方法名為Hook類的$portal屬性所定義的值。 默認是`run`方法。可以通過`Hook::portal()`方法修改值,從而修改入口方法。