ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CodeIgniter Hooks 钩子机制完全指南:在不改动核心文件的前提下扩展框架执行流程

CodeIgniter Hooks 钩子机制完全指南:在不改动核心文件的前提下扩展框架执行流程 CodeIgniter Hooks 钩子机制完全指南在不改动核心文件的前提下扩展框架执行流程【免费下载链接】CodeIgniterOpen Source PHP Framework (originally from EllisLab)项目地址: https://gitcode.com/gh_mirrors/co/CodeIgniterHooks钩子是 CodeIgniter 框架提供的一套事件回调机制它允许你在框架固定执行流程的特定节点插入自己的类方法或函数从而在不修改system/核心文件的前提下扩展框架行为。本文以 CodeIgniter 3.x 的 Hooks 功能为核心完整讲解钩子的启用方式、七种标准 Hook 点、配置写法与源码级执行原理读完你可以在自己的应用中熟练定义单个、多个乃至匿名函数形式的钩子并理解pre_system到post_system每个节点的确切触发时机。一、Hooks 是什么为不可改动的核心流程开一扇窗CodeIgniter 从入口文件index.php开始会按照固定顺序完成初始化基准测试与钩子类 → 路由解析 → 安全检查 → 加载控制器 → 渲染视图 → 输出响应这一整套执行流程完整流程见 Application Flow。官方文档明确指出CodeIgniters Hooks feature provides a means to tap into and modify the inner workings of the framework without hacking the core files.——这正是 Hooks 的设计初衷。与直接修改system/core/下源码升级框架时会丢失改动相比Hooks 把定制点全部收敛到应用目录内钩子定义文件位于application/config/hooks.php属于你的应用而非框架核心钩子脚本存放在application/目录内部的任意子目录通常为application/hooks/框架核心的 CodeIgniter.php 中只负责在特定阶段调用$EXT-call_hook(xxx)具体的钩子脚本完全由你的应用决定。典型应用场景包括在控制器加载前统一做权限校验、在控制器实例化后注入公共数据、在页面输出前对最终 HTML 做后处理、用自定义机制接管缓存读取与页面展示等。二、启用 Hooks一个配置项开关Hooks 功能默认是关闭的。要全局启用它在 application/config/config.php 中把enable_hooks改为TRUE$config[enable_hooks] TRUE;该配置项在仓库中的默认值为FALSE见 application/config/config.php。从源码看这个开关的作用非常直接CI_Hooks类的构造函数会先检查该项未启用时直接返回不加载任何钩子定义见 system/core/Hooks.phppublic function __construct(CI_Config $config) { log_message(info, Hooks Class Initialized); // If hooks are not enabled in the config file // there is nothing else to do if ($config-item(enable_hooks) FALSE) { return; } // ... }也就是说只有enable_hooks为TRUE时hooks.php才会被加载、$hook数组才会生效。三、定义 Hook数组原型与五个字段钩子定义在application/config/hooks.php文件中。每个钩子是一个关联数组原型如下$hook[pre_controller] array( class MyClass, function Myfunction, filename Myclass.php, filepath hooks, params array(beer, wine, snacks) );数组下标$hook[pre_controller]对应你希望挂载的 Hook 点名称下面五个字段逐项说明字段是否必填说明class可选要调用的类名。如果你更想用一个过程式函数而非类方法将此留空即可但此时function必须提供function必填要调用的函数名或类方法名filename必填包含该类/函数的脚本文件名filepath必填存放脚本的目录名相对application/目录结尾不要带斜杠params可选传给脚本的参数可以是任意值数组、标量等关于filepath的定位规则文档强调Your script must be located in a directory INSIDE your application/ directory——脚本必须位于application/内部。例如脚本位于application/hooks/→filepath填hooks脚本位于application/hooks/utilities/→filepath填hooks/utilities不要写尾部斜杠。结合源码可以确认路径的真实拼接逻辑。在 system/core/Hooks.php 的_run_hook()方法中if ( ! isset($data[filepath], $data[filename])) { return FALSE; } $filepath APPPATH.$data[filepath]./.$data[filename]; if ( ! file_exists($filepath)) { return FALSE; }即最终文件路径 APPPATHfilepath/filename其中APPPATH指向你的application/目录。若文件不存在钩子会静默失败返回FALSE不会抛出异常。3.1 参数如何传递params字段中定义的内容会作为单个参数传给钩子的函数/方法。继续看_run_hook()的实现system/core/Hooks.php$class empty($data[class]) ? FALSE : $data[class]; $function empty($data[function]) ? FALSE : $data[function]; $params isset($data[params]) ? $data[params] : ;随后调用时无论是类方法还是普通函数都统一以$function($params)的形式传入见 system/core/Hooks.php 与 system/core/Hooks.php。因此上面的例子中Myfunction会收到array(beer, wine, snacks)作为它的第一个参数class MyClass { public function Myfunction($params) { // $params array(beer, wine, snacks) } }3.2 类钩子与过程式函数钩子类形式class非空时框架会require_once该文件、实例化类并把实例缓存在$_objects属性中同一类多次触发只实例化一次见 system/core/Hooks.php然后调用对应方法函数形式class留空时框架require_once文件后直接调用全局函数见 system/core/Hooks.php。四、用匿名函数闭包定义 Hook如果你希望钩子逻辑很短、不必单独建立文件可以直接用 PHP 闭包定义钩子语法更简洁$hook[post_controller] function() { /* do something here */ };从源码看_run_hook()第一步就是检测可调用对象并直接执行system/core/Hooks.php// Closures/lambda functions and array($object, method) callables if (is_callable($data)) { is_array($data) ? $data[0]-{$data[1]}() : $data(); return TRUE; }所以除了闭包形如array($object, method)的可调用数组同样会被识别并直接执行——这为钩子提供了更灵活的写法。另外注意闭包形式不会经过filepath/filename检查因此无需也无法指定文件位置。五、同一 Hook 点的多次调用一个 Hook 点可以挂载多个脚本只需把数组声明改为多维并在下标后加[]$hook[pre_controller][] array( class MyClass, function MyMethod, filename Myclass.php, filepath hooks, params array(beer, wine, snacks) ); $hook[pre_controller][] array( class MyOtherClass, function MyOtherMethod, filename Myotherclass.php, filepath hooks, params array(red, yellow, blue) );注意每个下标后的方括号$hook[pre_controller][]。它让同一 Hook 点可以绑定多个脚本定义顺序就是执行顺序——先定义的先执行。源码中的判断逻辑与此完全对应system/core/Hooks.phppublic function call_hook($which ) { if ( ! $this-enabled OR ! isset($this-hooks[$which])) { return FALSE; } if (is_array($this-hooks[$which]) ! isset($this-hooks[$which][function])) { foreach ($this-hooks[$which] as $val) { $this-_run_hook($val); } } else { $this-_run_hook($this-hooks[$which]); } return TRUE; }这里有个值得注意的细节框架通过该节点的值是否含有function键来区分单钩子与多钩子。单个数组形式含function键直接执行多维数组形式不含function键则逐个遍历执行且严格按照你在hooks.php中的书写顺序。六、七个标准 Hook 点及其精确触发时机CodeIgniter 共提供七个标准 Hook 点下表汇总了每个点的触发时机与典型用途Hook 点触发时机此时已完成的工作典型用途pre_system系统执行的最早期仅加载了 Benchmark 类和 Hooks 类本身极早期的环境准备、常量定义注意此时几乎什么都还不可用pre_controller任何控制器被调用之前所有基类、路由解析、安全检查均已完毕统一鉴权、请求预处理post_controller_constructor控制器被实例化之后、任何方法调用之前控制器构造函数已执行向控制器注入公共数据、设置视图变量post_controller控制器完全执行完毕之后控制器方法已执行结果后处理、日志记录display_override覆盖_display()方法最终页面已生成尚未发送给浏览器用自己的方式输出页面见 6.1 节cache_override覆盖_display_cache()方法尚未检查缓存用自定义缓存展示机制替代内置缓存post_system最终渲染页面已发送给浏览器之后整个系统执行结束收尾清理、性能统计下面结合 system/core/CodeIgniter.php 中的实际调用点逐一确认这些时机的源码依据。6.1pre_system最早的一个节点钩子类在框架初始化早期就被实例化紧接着立刻触发pre_systemsystem/core/CodeIgniter.php$EXT load_class(Hooks, core, $CFG); // ... $EXT-call_hook(pre_system);文档特别提醒Only the benchmark and hooks class have been loaded at this point. No routing or other processes have happened.此时基准测试类和钩子类是仅有的已加载组件路由、安全、输入过滤等都还没有发生。因此在这个 Hook 点里你拿不到CI_URI、CI_Router等对象只能做最基础的环境准备例如根据请求设置自定义常量、修改$_SERVER等。6.2pre_controller控制器调用之前在控制器实例化之前路由解析和缓存检查早已完成缓存命中会直接exit。代码中的调用位置system/core/CodeIgniter.php$EXT-call_hook(pre_controller);此时*All base classes, routing, and security checks have been done*——所有基类、路由与安全检查均已完成是执行全局鉴权、请求级别预处理的最佳时机。6.3post_controller_constructor实例化之后、方法调用之前控制器通过$CI new $class();完成实例化后立即触发该钩子system/core/CodeIgniter.php$CI new $class(); // ... $EXT-call_hook(post_controller_constructor);此时控制器构造函数已执行完毕但控制器方法尚未被调用后续才执行call_user_func_array(array($CI, $method), $params)见 system/core/CodeIgniter.php。适合做所有控制器共用的初始化比如批量设置视图数据、加载公共辅助函数。6.4post_controller控制器执行完毕控制器方法执行并完成基准测试标记后触发system/core/CodeIgniter.php$BM-mark(controller_execution_time_( .$class. / .$method. )_end); // ... $EXT-call_hook(post_controller);Called immediately after your controller is fully executed.适合做请求日志、业务结果的后处理。6.5display_override接管页面输出默认情况下框架在post_controller之后调用$OUT-_display()把最终页面发送给浏览器。而display_override钩子可以完全覆盖这一行为system/core/CodeIgniter.phpif ($EXT-call_hook(display_override) FALSE) { $OUT-_display(); }也就是说一旦你定义了display_override钩子且其执行成功框架不再调用内置的_display()改用你自己的输出方式。文档给出了标准的取值方法$this-CI get_instance(); // 最终渲染数据通过 Output 组件的 get_output() 获取 $data $this-CI-output-get_output();get_output()是 system/core/Output.php 中定义的公开方法用于取回已渲染的最终输出字符串。典型场景把页面内容写入模板引擎、压缩后再输出、或写入自定义响应头。注意display_override是一个override型钩子与普通钩子不同——框架关心它的返回值来判断是否继续走内置逻辑。6.6cache_override接管缓存展示在系统启动阶段框架会先检查是否存在可用的缓存文件cache_override允许你用自定义方法替代内置的_display_cache()system/core/CodeIgniter.phpif ($EXT-call_hook(cache_override) FALSE $OUT-_display_cache($CFG, $URI) TRUE) { exit; }只有当钩子返回FALSE即未定义或执行失败时框架才会回落到内置的_display_cache()定义于 system/core/Output.php。这让你可以用自己的缓存展示机制例如从 Redis 而非文件缓存中读取并输出页面。相关背景可参考 Output 库文档。6.7post_system系统收尾页面发送完毕、整个系统执行结束后触发system/core/CodeIgniter.php$EXT-call_hook(post_system);文档描述为Called after the final rendered page is sent to the browser, at the end of system execution.适合做收尾工作如统计本次请求耗时、释放外部资源等。七、源码级执行细节与注意事项7.1 钩子脚本的加载与实例化缓存在_run_hook()中类形式的钩子会被缓存复用system/core/Hooks.php首次触发时require_once脚本并new $class()实例存入$this-_objects[$class]后续同一类再次触发时直接从缓存取对象调用方法避免重复实例化。同时框架会校验class_exists()与method_exists()不满足则返回FALSE。7.2 防止无限循环的_in_progress标志_run_hook()中有一个防递归保护system/core/Hooks.php// Safety - Prevents run-away loops // If the script being called happens to have the same // hook call within it a loop can happen if ($this-_in_progress TRUE) { return; }如果某个钩子脚本内部又触发了同名钩子比如在pre_controller钩子里再次调用会触发pre_controller的代码_in_progress标志会阻止其无限递归这是一个值得了解的安全设计。7.3 环境专属的钩子定义文件除了通用的application/config/hooks.php框架还支持按环境加载application/config/{ENVIRONMENT}/hooks.php见 system/core/Hooks.php。两个文件都会被加载可用来区分开发/生产环境的不同钩子配置。ENVIRONMENT常量由index.php定义通常为development/testing/production。7.4 测试中的钩子注册方式在仓库的测试框架中Hooks 类被注册为核心扩展之一tests/mocks/ci_testcase.php里的$config[core_classes][hooks] ext将CI_Hooks映射为可被 mock 的扩展类。这从侧面印证了钩子类与load_class()体系的关系——任何第三方库都可以通过类似方式扩展或替换核心类这也是不 hacking 核心文件这一设计哲学的一部分。八、实战示例完整的钩子落地流程综合以上内容一个完整的钩子使用流程如下第 1 步启用钩子。在 application/config/config.php 中设置$config[enable_hooks] TRUE;。第 2 步编写钩子脚本。在application/hooks/下创建Myclass.php?php defined(BASEPATH) OR exit(No direct script access allowed); class MyClass { public function Myfunction($params) { // 例如记录钩子触发时收到的参数 log_message(debug, Hook triggered with: .json_encode($params)); } }第 3 步注册钩子。在 application/config/hooks.php 中追加$hook[pre_controller] array( class MyClass, function Myfunction, filename Myclass.php, filepath hooks, params array(beer, wine, snacks) );第 4 步验证。触发任意请求观察日志中是否出现 Hook triggered with 记录若同时需要多个动作可改用$hook[pre_controller][] ...追加更多钩子。注意事项速查脚本必须位于application/内部filepath不带尾部斜杠function必填class可选留空即按全局函数处理params会作为单个参数整体传入同一 Hook 点多脚本时定义顺序即执行顺序display_override与cache_override属于 override 型钩子框架依据其返回值决定是否回落内置逻辑pre_system阶段组件极少只能访问 Benchmark 与 Hooks 自身。九、小结Hooks 是 CodeIgniter 框架可扩展而不破坏核心理念的直接体现通过enable_hooks一个开关、application/config/hooks.php一个文件以及七个精心设计的 Hook 点你可以在框架生命周期的任意关键节点插入自己的逻辑。理解 system/core/Hooks.php 中call_hook()/_run_hook()的实现以及 system/core/CodeIgniter.php 中各处call_hook()的调用位置就能精准把握每个节点的触发时机与可用资源从而写出既安全又高效的钩子代码。无论是全局鉴权、视图数据注入、输出后处理还是缓存机制定制Hooks 都是首选且官方推荐的实现路径。【免费下载链接】CodeIgniterOpen Source PHP Framework (originally from EllisLab)项目地址: https://gitcode.com/gh_mirrors/co/CodeIgniter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表