ThinkPHP 多应用模式下 `app_init` 钩子"装死"两小时:我差点把 Event 源码翻个底朝天

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 86 浏览 0 回复

昨晚折腾一个新项目,多应用架构,前后端分离。想做个全局的接口签名验证,顺手在 app_init 里绑了个监听。结果你们猜怎么着?php think run 跑起来,断点打进去,死活不走我的钩子。Event 类都 dump 出来了,监听数组里明明有我注册的闭包,就是不被触发。

我第一反应是缓存。清 runtime、清 opcache、甚至重启了电脑(对,就是病急乱投医)。没用。第二反应是注册时机太晚,把代码从某个控制器构造函数挪到 AppServiceboot,再挪到 event.php 配置文件的 listen 数组。还是没用。

凌晨一点半,我盯着 App.php 的源码逐行跟,终于发现坑在哪了——多应用模式下,框架是先走 app_init然后才解析当前应用。而我的签名验证逻辑里顺手用了 app('http')->getName() 来判断是 api 还是 admin,结果那个阶段应用名还没初始化,直接抛了个内部异常被吞掉了,钩子链提前中断,后面全白搭。

更骚的是,这个异常因为发生在 App::initialize 的 try-catch 里,日志级别默认没开 debug 的话,你只能在 trace 里看到一行模糊的 "Exception in app_init",具体啥错根本不告诉你。我愣是用 register_shutdown_function 才抓到真实错误信息。

最后改成在 HttpRun 事件里做判断,或者干脆用 RouteLoaded,应用名稳了、路由也解析完了。但这两个钩子的触发时机和文档里那句"应用初始化完成"的描述,说实话对多应用场景讲得不够透,新手很容易踩。

顺手记个排查顺序,下次再遇到钩子不生效,按这个来,别像我一样熬夜翻源码:

1. 先看注册代码有没有被执行到(最简单的 echo 'here' 或者写个临时文件)
2. 再看 Event 的 listener 数组里有没有你的标识(防止配置覆盖或命名空间拼错)
3. 然后检查钩子回调里有没有静默抛异常(特别是依赖某些尚未初始化的服务)
4. 最后确认你绑的是不是"你以为的那个钩子",ThinkPHP 里 AppInitHttpRunRouteLoaded 差一个字,时机天差地别

现在那坨临时写的 register_shutdown_function 排查代码我还留着,注释写的是"凌晨 01:47,记住这个教训"。

评论0
回复 · 0
还没有回复
微信客服 微信客服