2.3 请求生命周期:一张工单的完整流转


2.3 请求生命周期:一张工单的完整流转

本节摘要:每个 HTTP 请求都要走完同一条流水线:public 下的 index.php 收单,bootstrap 完成应用装配,HTTP 内核把请求依次送过全局中间件、路由匹配、路由中间件,再到控制器产出响应,最后逐层"回程"穿出中间件并执行收尾任务。本节按 Laravel 11 的新装配方式拆解每一站,读完你能对"请求卡在哪一站"做出准确判断。

为什么要专讲流水线

前两节备齐了工具、认识了调度中心,这一节把流水线本身讲清。它在工程上的价值非常具体:排障时你至少要知道"这个 500 是还没进路由就炸了"和"进控制器之后才炸"是完全不同的两个排查方向;性能优化时你要知道哪些活每张工单都干一遍、哪些活可以提前干一次。不理解流转顺序,这些判断都无从谈起。

流水线全景

第一站是 public/index.php,Web 服务器(Nginx 或 artisan serve)把所有请求都导向它。它只干一件事:加载自动加载器,然后要求 bootstrap 目录里的应用完成装配。第二站的装配在 Laravel 11 里集中在 bootstrap/app.php:

<?php // bootstrap/app.php(Laravel 11 新写法:链式装配) use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { // 往 web 组追加自己的门卫,第三章会展开中间件细节 $middleware->web(append: [ \App\Http\Middleware\EnsureTeamHeader::class, ]); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();

老版本(10 及以前)的读者注意:这里不再有 app/Http/Kernel.php,全局中间件、路由组、异常渲染都改在这一个文件里用流式方法配置。这就是第一章版本演进里"骨架瘦身"的具体所指。

图 2-3:请求在内核里的进出场路线

图 2-3:请求在内核里的进出场路线

管道机制:中间件为什么能"拦"

内核的执行核心是管道(Pipeline):请求按顺序穿过一组回调,每层处理完把请求递给下一层,控制器返回后又按相反顺序穿回来。用一段简化伪代码看穿它:

<?php // 简化模型:这就是"洋葱"式穿层 $pipeline = array_reduce( array_reverse($middleware), function ($next, $layer) { return function ($request) use ($layer, $next) { return $layer->handle($request, $next); // 每层可前置检查、可后置加工 }; }, function ($request) { return $router->dispatch($request); // 最里层:真正派工给控制器 } );

两件事值得你记住。第一,回程是真实存在的:中间件在 next(request) 之后写的代码,在响应生成后才执行,所以"统计耗时"这类后置逻辑要写在后面。第二,任何一层直接返回而不调 $next,请求就到此为止——auth 门卫把没登录的访客拦下重定向,用的就是这个机制。

控制台请求与收尾

同一个应用还服务命令行:artisan 命令走 console 路由入口(Laravel 11 的 routes/console.php),跳过 HTTP 中间件直接进命令调度。这解释了一个常见困惑——为什么 Web 里生效的中间件在 artisan 命令里"不存在":它们根本不在同一条流水线上。

无论哪条流水线,收尾都靠 terminate:响应发出后框架会调用标记了 TerminateMiddleware 的 terminate 方法和可终止的事件订阅者。发慢邮件、推埋点这类"用户不必等"的活,放在这里或队列里,别堵在主流程。

⚠️ 常见坑:在全局中间件里写了重量级数据库查询,等于给每张工单都加了这道工序。全局层只放真正每个请求都必须做的事,其余按路由组挂载。

三个高频追问

改了 bootstrap/app.php 为什么不生效?
两种情况:生产环境跑过配置类缓存,新装配没重载进内存,重跑缓存命令或重启 PHP 进程即可;OPcache 关闭了时间戳校验的环境(生产调优常态),代码更新必须触发进程重载——这两个行为在第九章部署一节会正式收编进部署脚本。

terminate 里能往响应里写内容吗?
不能。terminate 阶段响应已经发给浏览器,此时做的事用户完全无感,它适合发慢日志、推送埋点、清理临时资源。想在响应里体现什么,必须在控制器返回之前完成。

Artisan 命令有没有中间件这道闸?
没有 HTTP 中间件,但有独立的命令行守卫:命令可以通过签名约束(没有配置签名则拒绝执行)与调度环境检查来把关。这就是上一节末尾强调的"两条流水线":Web 与命令行共用应用容器,但请求门禁各走各的。

异常在流水线的哪一层被接住?
全局中间件之外、框架内核有一层统一的异常渲染:任何一层抛出的异常,最终都由 bootstrap/app.php 里 withExceptions 注册的处理规则转成响应。所以你看到的一个 500 页面,可能是路由层、控制器层、模板层任何一处抛出来的——这也是排障时要先看日志堆栈、而不是猜位置的原因。

本节要点回顾

  • 六层路线:收单、装配、全局中间件、路由匹配、路由中间件、控制器,回程再穿出并 terminate。
  • Laravel 11 装配:bootstrap/app.php 一个文件管路由、中间件、异常,Kernel 文件已成历史。
  • 管道与洋葱:前置拦、后置加工、不调 next 即拦截,三个行为撑起了整个中间件体系。
  • 排障定位法:先判断炸在哪一层,再进对应工位查——这是本节给你的最大实战红利。

流水线讲完了,下一章站到第一线去:路由进件。你会看到每条路由就是这条流水线上的登记记录,而中间件正是你刚认识的管道上的门卫。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U