Laravel中间件通过Illuminate\Pipeline的闭包链实现洋葱模型:先array_reverse翻转中间件数组,再用array_reduce逆序构建嵌套闭包,每个handle()必须调用$next($request)才能放行,否则中断链条。

要搞清楚 Laravel 框架怎么把中间件一层层串起来、最终让请求穿过所有中间件再抵达控制器,必须直击 Illuminate\Pipeline 的源码执行逻辑——它不靠魔法,只靠闭包嵌套与 array_reduce 的逆序折叠。
Pipeline 初始化与中间件注册
所有管道始于 Kernel::handle() 调用 $this->sendRequestThroughRouter($request),后者内部 new Pipeline($this->app)->send($request)->through($this->middleware)->then(...)
其中 through() 方法仅做一件事:将中间件列表(类名数组或闭包数组)赋值给 $this->pipes 属性。这一步看似简单,但【$this->pipes 必须是索引数组,且键必须连续从 0 开始】,否则 array_reduce 在逆序遍历时会跳过某些中间件。
注意:through() 不做任何实例化或验证,纯粹是“存起来”。中间件类是否真实存在、能否 resolve,要等到 then() 执行时才触发。
then() 启动管道执行:array_reduce 的洋葱剥法
then() 是整个管道真正开始转动的开关。它接收一个终点闭包 $destination(通常是路由分发器),并用 array_reduce 构建嵌套闭包链:
第一步:调用 array_reverse($this->pipes) 把中间件顺序彻底翻转。比如原始数组是 [A, B, C],反转后变成 [C, B, A]。
第二步:以 $destination 为初始值,对反转后的数组逐个调用 $this->carry()。carry() 返回一个新闭包,该闭包接收 $passable(即 Request),并在内部调用当前中间件的 handle(),同时把“剩下的管道+终点”打包进 $next 参数传下去。
关键点在于:array_reduce 每次迭代都把上一轮返回的闭包,当作下一轮 $next 的入参。最终得到的不是中间件执行结果,而是一个可立即执行的顶层闭包——它一调用,就触发 C → B → A → $destination 的完整链路。
这正是“洋葱模型”的实现本质:外层闭包包裹内层,最外层最先执行前置逻辑,最里层最先拿到请求,也最先执行响应后置逻辑。
中间件 handle() 的接力规则
每个中间件的 handle(Request $request, Closure $next): Response 方法,必须显式调用 $next($request) 才能延续链条。
方法一:放行请求
直接 return $next($request); —— 这是最常见写法,请求继续向内穿透。
方法二:拦截并返回响应
if (!$request->hasValidToken()) { return response('Forbidden', 403); }
【此时绝不能写 exit 或 die,也不能漏掉 return】——漏 return 会导致函数无返回值,PHP 返回 null,最终响应体为空;exit/die 则会中断 PHP 进程,Response 头无法发送,浏览器卡死。
方法三:修改请求后放行
$request = $request->merge(['user_id' => 123]);
return $next($request);
这种写法常见于日志中间件或参数预处理中间件,修改后的 $request 会作为下一个中间件的输入。


















