💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 钩子和行为 行为是一个比较抽象的概念,可以把行为理解成在应用执行过程中的一个动作。行为抽离出来的目的是为了让开发者无需改动框架和应用,而在外围通过扩展或者配置来改变或新增一些功能。 我们把行为作用的位置称做为钩子,当应用程序运行到这个钩子的时候,就会被拦截下来,统一执行相关的行为,类似于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()`方法修改值,从而修改入口方法。