ThinkPHP 5.1/5.2 是唯一支持原生行为扩展的稳定版本;TP6+ 已移除 Hook 机制,硬套会报错或静默失效。行为类须实现 public function run($params = null),无构造参数,由反射自动实例化;必须在 application/tags.php 中以关联数组注册 PSR-4 兼容的完整命名空间类,钩子名仅限内置标签或手动触发的自定义名;自定义钩子需显式调用 Hook::listen() 触发,且注意事务未提交、Request 未初始化等执行时机问题;TP6 应使用 think-event 组件替代旧 Hook。

ThinkPHP 5.1/5.2 是唯一能用原生行为扩展的稳定版本;TP6+ 已彻底移除 Hook 机制,硬套会直接报 Class 'think\Hook' not found 或静默失效。
行为类必须实现 run() 方法,且不能带构造参数
框架通过反射自动实例化行为类,不传任何参数。如果你写了 __construct(Config $config),框架会跳过该类——不报错,也不执行。
-
run()方法签名必须是public function run($params = null),即使你不用参数也得保留 - 参数类型由钩子点决定:
app_init不传参,view_filter传$content,action_begin传控制器实例 - 别在
run()里调request()或input()——app_init阶段Request还没创建,会返回null - 想复用服务?用
app('cache')或app('log'),别依赖构造注入
app/tags.php 是唯一有效的注册位置
写在控制器、中间件或模型里等于白写。框架只在启动时加载一次 app/tags.php(注意:TP5.1+ 默认路径是 application/tags.php,不是 app/tags.php)。
- 文件必须
return关联数组,格式为'钩子名' => ['完整类名'] - 类名必须是 PSR-4 兼容的完整命名空间,比如
app\behavior\CheckAuth - 钩子名只能是内置标签(如
app_init、action_begin)或你手动触发的自定义名(如user_login_success) - 多个行为按数组顺序执行,前一个若修改了
$params(引用传递),后一个能感知到
自定义钩子必须手动触发,不能靠框架自动调度
框架内置钩子(如 app_begin)由 App::run() 自动调用;你定义的新钩子(比如 after_payment)必须在业务代码里显式写 HooK::listen('after_payment', $data) 才会执行。
立即学习“PHP免费学习笔记(深入)”;
- 自定义钩子名建议全小写+下划线,避免和内置冲突(例如别叫
app_init) -
HooK::listen()第二个参数是你要传给所有绑定行为的$params,类型任意,但所有行为的run()都得能处理它 - 事务场景要小心:在
model->save()后立刻触发钩子,此时事务可能未提交,查数据库看不到最新数据 - 调试是否生效?别用
dump(),改用trace()或日志;也可在控制器里查HooK::getListeners('your_hook_name')看是否注册成功
别在行为里做耗时操作,也别用 HooK::add() 动态注册核心钩子
行为类每次触发都会新建实例,挂在高频钩子(如 view_parse)上容易拖慢响应。更关键的是:HooK::add() 只影响后续 listen() 调用,而框架核心流程里的钩子早已注册完毕——加了也白加。
- 远程请求、大文件读写、复杂计算等操作,应改用队列或异步任务
- 动态注册仅适合插件热加载等特殊场景,且必须确保调用时机早于首次钩子触发(比如放在全局中间件或
BaseController构造函数里) - 如果用了
HooK::add(),记得加静态标识防止重复注册,否则每次请求都新增监听器 - TP6 用户注意:不要试图复制 TP5 的
HooK类,它不会工作——TP6 已完全切换到 PSR-14 事件系统,要用think-event组件
最常被忽略的一点:行为类的文件路径、命名空间、run() 方法签名、tags.php 返回格式,四者必须严丝合缝。少一个 \、多一个空格、漏掉 return,都会导致静默失效——它不会报错,只是不执行。



















