上一节定好了「代码放哪层」,本节看「请求怎么流过这些层」。生命周期是全书的总地图:报错堆栈、性能毛刺、日志埋点,全部要挂在这条动线上才能看懂。读完本节,你应当能对着堆栈说出请求走到了哪一站,并能说出事件系统在动线上的几个广播点分别适合做什么。
一个 HTTP 请求进入 ThinkPHP 后的完整旅程可以概括成八段:入口文件加载框架 → 装载环境与配置 → 初始化应用并加载事件与服务 → 全局中间件前置处理 → 路由检测与分拣 → 容器实例化控制器并执行动作 → 生成响应对象 → 中间件后置处理后输出。其中第三段和最后一段之间夹着的,就是你在 2.1 里写的那点业务代码——绝大多数开发时间,你只活在第五段到第七段,但排查问题时必须看得见全貌。
入口本身薄得超出直觉。public 目录下的 index.php 只做很少的事:定义运行常量、加载 Composer 自动加载器、实例化应用容器、执行 HTTP 应用并把响应发回。所有「框架启动」的重活都发生在容器的初始化链条里,这正是下一节容器章节的伏笔。

把上面的分段图换成时序,注意两条规则:控制器的动作方法与它的依赖(比如 2.1 里的 OrderService)由容器自动装配;请求对象贯穿始终,任何一段都能读它。
时序图里有一处值得停顿:中间件的回程。请求进去时走「前置」,响应回来时还会走「后置」——所以一个记录耗时的中间件只需要在进入时记时间、退出时算差值,两个时机天然分布在同一段代码的前后。
生命周期里的关键节点,框架会对外广播事件。监听的注册集中在应用目录的事件定义里,监听器是普通类:
// app/event.php:事件与监听器的绑定 return [ 'listen' => [ 'AppInit' => ['app\listener\WarmCache'], 'HttpRun' => [], 'RouteLoaded' => ['app\listener\RouteAudit'], 'LogWrite' => ['app\listener\LogMeter'], 'HttpResponseEnd'=> ['app\listener\SlowRequestAlarm'], ], ];
// 监听器:响应结束后检查慢请求并告警 namespace app\listener; use think\App; class SlowRequestAlarm { public function handle(App $app) { $cost = microtime(true) - $app->beginTime; if ($cost > 2.0) { // 写告警日志或推送值班群,此处从简 \think\facade\Log::warning('slow request: ' . round($cost, 2) . 's ' . $app->request->url()); } } }
业务事件同理:2.1 下单流程里的 event('OrderPaid', $order) 就是一次主动广播,你可以为它绑定「发短信」「加积分」「通知卖家」任意多个监听器。事件的本质是把「发生了什么」和「要做什么」拆开——下单服务从此不认识短信服务,新增一个后续动作只需要注册新监听器,服务层一行不改。代价是调用链变得隐形,排错时要先查事件绑定表才知道动作被谁接走了。判断标准很朴素:后续动作失败不应影响主流程、且数量可能增长时用事件;强一致、必须立刻完成的动作老老实实同步调用。
背景:练习项目偶尔慢,想在开发期就看见慢请求。操作:注册一个 HttpResponseEnd 监听器,读应用对象上记录的开始时间,超过阈值就写一条 warning 日志;再在配置里开启 Trace 调试(第 8 章详述),对照日志与 Trace 的耗时分布。结果:开发期能稳定捕捉到慢于阈值吐出的警告,堆栈指向具体 URL。解读:这枚探针其实就是生产环境慢查询告警的原型,第 8 章的性能章节会把阈值、采样与通知渠道补全。变式:把监听器改成统计每请求的数据库查询条数,观察哪几个页面查询数异常多——这个信号在第 9 节 ORM 的关联预载入里会给出解法。
⚠️ 常见坑:监听器里不要写重逻辑或再抛业务异常——框架广播点失败可能影响整个请求;监听器的本分是「旁路观察与登记」,要执行重活请丢进队列(第 8 章)。
💡 关键直觉:报错时先看堆栈最底部那行自己写的类——框架管道占的堆栈只是「道路」,你的代码才是「事故车」。
广播好用,但有三类场景不该用。其一,主链路的必要步骤——扣库存、写订单这类不做业务就不成立的动作,必须显式调用,包成事件监听等于把「必须发生」伪装成「最好发生」,哪天有人注释掉监听注册,业务就静默残废。其二,需要返回值的协作——事件是单向广播,监听器改不动主流程;发现自己在监听器里改写请求参数、影响主流程判断,说明这本来就该是服务层的显式调用。其三,强一致的两步写——「写订单再写积分」若要求同成败,应该放同一事务,拆成事件广播就把事务边界拆没了(4.4 会展开)。
一句话边界:事件只承载「发生了,关心的人自己看着办」的通知;凡是「必须、有顺序、要回话」的协作,都用显式调用。装上速度探针后再把这节的边界读一遍,你会在自己的项目里认出至少一处用错了的事件。
动线上的对象从哪来?下一节拆开容器:依赖注入与服务绑定的机关全在这里。