## 钩子和行为
行为是一个比较抽象的概念,可以把行为理解成在应用执行过程中的一个动作。行为抽离出来的目的是为了让开发者无需改动框架和应用,而在外围通过扩展或者配置来改变或新增一些功能。
我们把行为作用的位置称做为钩子,当应用程序运行到这个钩子的时候,就会被拦截下来,统一执行相关的行为,类似于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()`方法修改值,从而修改入口方法。