【进阶】Hook机制

<br/> >i 本文档的最新修订日期是: > **2023-10-13** # 综述 Hook机制,也就是通常所说的钩子,是一种不侵入原有代码的前提下,改变其执行过程或改变其中数据的一种方式。钩子最常见的实现为“插件”,基于Hook机制实现的插件功能,可极大地提升应用的开发便捷性。 本框架中的Hook机制的实现分为两步,先添加(注册)钩子,再调用已添加(注册)的钩子,一个钩子可以顺序执行多个方法。 # 调用钩子 使用 <font color="#c7254e">`Hook`</font> 模块的 <font color="#c7254e">`call()`</font> 方法完成钩子调用,使用方法可参见模块说明文档。 # 路径规定&命名空间 所有通过Hook调用的方法所对应的代码文件,均应当放在 <font color="#c7254e">`/lib/plugin/`</font> 目录下,框架中将会为钩子执行的方法自动加上 <font color="#c7254e">`plugin\`</font> 命名空间前缀,因此在使用时无需指定命名空间 <font color="#c7254e">`plugin\`</font> 。 # 参数 钩子的方法是顺序执行的,即对于同一个钩子,第一个被执行的方法获得的参数就是调用钩子时传入的参数,此后被执行的方法所获得的参数是前一个方法的返回值,也就是前一个方法修改完参数后传递给下一个方法,这也就意味着后续方法所获得的传入参数并不一定与调用钩子时传入的参数相同。 如果调用钩子时没有传入参数,则方法得到的传参将是一个空数组。 # 钩子方法 无论调用钩子时是否传递了参数,执行的方法都会被传入的一个 <font color="#c7254e">`Array`</font> 类型的参数,因此执行的方法必须符合如下样式: ```php <?php namespace plugin; class ClassName { public static function func1($Para) { } } ``` 如果调用钩子时传递了参数,则 <font color="#c7254e">`$Para`</font> 与调用钩子时传递的参数相同,否则为一个空数组。 # 返回值 执行的方法可以不返回值,但如果需要返回,则返回值必须与参数保持 <font color="#c7254e">`结构相同`</font> 。 结构相同是指返回值数组与传入的参数数组相比,不应当丢失元素、元素的数据类型不应改变、原则上不新增元素,否则,可能会使调用钩子的代码无法继续执行。 # hook.apiphp文件 <font color="#c7254e">`hook.apiphp`</font> 文件是本框架中用以添加(注册)钩子的配置文件,此文件应视为PHP文件,其内代码语法与PHP语法规则一致。 文件中使用 <font color="#c7254e">`Hook`</font> 模块的 <font color="#c7254e">`add()`</font> 方法完成钩子的添加(注册),使用方法可参见模块说明文档。以下是一个hook.apiphp的文件示例: ```php <?php \core\Hook::add([ [ 'app_user-login_success' => ['User::loignSuccess','User::getToken'] ] ]); ``` 即当钩子 <font color="#c7254e">`app_user-login_success`</font> 被调用时,会依次执行 <font color="#c7254e">`plugin`</font> 命名空间下 <font color="#c7254e">`User::loignSuccess`</font> 和 <font color="#c7254e">`User::getToken`</font> 这两个方法。 子目录下 <font color="#c7254e">`hook.apiphp`</font> 文件中的内容,会在模板文件被编译时,添加进缓存文件中。 # 添加(注册)钩子 在本框架中,可通过三种方式完成钩子的添加(注册),三种方式均有其适用的场景。 钩子的命名应当遵循 <font color="#c7254e">`三段式`</font> 结构,之间用下划线连接,命名规范为<font color="#c7254e">`钩子归属_功能名称_动作/状态`</font> 。 例如,<font color="#c7254e">`cms_userLogin_success`</font> 代表CMS系统在用户登录过程中,登陆成功状态下调用的钩子。 规范的钩子命名有利于提升代码可读性及后期维护。 ## 方法1:通过模板根目录的配置文件 在模板文件目录的根目录(即 <font color="#c7254e">`/source/`</font>)下新建文件 <font color="#c7254e">`hook.apiphp`</font> 。 由于此文件的加载位于框架路由工作之前,因此在此文件中添加(注册)的钩子,在所有模板文件中均可用。此种方法能够添加(注册)的钩子类型最多,例如可在此文件中添加钩子对框架的路由过程进行干预,而其他方式添加(注册)的钩子,由于加载时机已在路由过程之后,因此无法对框架的路由过程进行干预。 ## 方法2:通过子目录的配置文件 在模板文件目录(即 <font color="#c7254e">`/source/`</font>)的任意子目录下新建文件 <font color="#c7254e">`hook.apiphp`</font> 。 此文件对同一级的模板文件及所有子目录的模板文件有效,例如对于下面的目录结构: ``` / 站点根目录 │ └─source 模板目录 ├─hook.apiphp 钩子配置文件1 ├─initial.php ├─auth │ ├─hook.apiphp 钩子配置文件2 │ ├─login.php │ └─regist │ ├─hook.apiphp 钩子配置文件3 │ └─step1.php └─sms ├─hook.apiphp 钩子配置文件4 └─sendCode.php ``` 各钩子配置文件的作用范围如下: <font color="#c7254e">`/source/initial.php`</font> 受到钩子配置文件 <font color="#c7254e">`1`</font> 影响。 <font color="#c7254e">`/source/auth/login.php`</font> 受到钩子配置文件 <font color="#c7254e">`1~2`</font> 影响。 <font color="#c7254e">`/source/auth/regist/step1.php`</font> 受到钩子配置文件 <font color="#c7254e">`1~3`</font> 影响。 <font color="#c7254e">`/source/sms/sendCode.php`</font> 受到钩子配置文件 <font color="#c7254e">`1和4`</font> 影响。 在多个钩子配置文件共同产生影响时,父一级的钩子配置文件的优先级大于子一级的钩子配置文件,即其中的钩子先被添加(注册)。 ## 方法3:在代码中直接添加(注册)钩子 只需在钩子被调用之前,使用 <font color="#c7254e">`Hook`</font> 模块的 <font color="#c7254e">`add()`</font> 方法完成钩子的添加(注册),使用方法可参见模块说明文档。 ## 适用场景 在代码中直接添加(注册)钩子会侵入原有代码,除非应急调试时使用,其他情况下 <font color="#c7254e">`不推荐`</font> 。 根目录的hook.apiphp适用于必须位于此文件中的钩子(例如与框架路由有关的钩子),或一些全局性的钩子。对于小型项目,也可在此配置文件中添加(注册)所有的钩子。 对于大型项目,可能有数百个钩子,此时应根据钩子的调用位置,将其分散于子目录中,以提高加载性能。 ## hook.apiphp的移除 如果不再需要hook.apiphp,可以将hook.apiphp修改为空文件。 如果需要删除hook.apiphp,则需要强制编译此配置文件同级目录和子目录下的所有模板文件,或删除 <font color="#c7254e">`/temp/cache/`</font> 目录下的对应位置的缓存,以便在下一次代码被执行时进行自动编译。 强制编译使用 <font color="#c7254e">`Cache`</font> 模块的 <font color="#c7254e">`Compile()`</font> 方法完成,使用方法可参见模块说明文档。 # 样例 样例位于 <font color="#c7254e">`/source/hookdemo.php`</font> 文件中。