Pipeline是严格契约驱动的串行执行工具,必须显式调用$next($passable)才能继续流程,否则静默终止;through支持闭包、类名(自动解析+DI)或[类,方法]数组,但类需实现handle($passable, Closure $next);仅适用于可插拔、输入输出一致的多步处理,HTTP请求应使用框架中间件而非手动Pipeline。

别把 Laravel 的 Pipeline 当成“流程控制语法糖”或 HTTP 请求的中间件平替——它是个严格契约驱动的串行执行工具,只在你明确需要解耦、可插拔、统一输入输出的多步处理时才值得用。
为什么 $next($passable) 必须显式调用
每个 pipe 闭包或类的 handle 方法里,不写 return $next($passable),流程就静默终止。PHP 不会报错,但后续所有步骤完全不执行,then() 拿到的可能是 null 或上一步意外返回的值。
- 常见错误现象:
then()回调里$user是null,或模型字段没更新 - 根本原因:某个 pipe 里只做了副作用(如
session()->flash()),却忘了return $next($passable) - 调试技巧:在每个 pipe 开头加
throw new \Exception(gettype($passable));,快速定位类型断裂点 - 注意:返回
redirect()或response()也能中断,但后续 pipe 仍会收到这个响应对象——不是“跳过”,而是“继续传一个非预期类型”
through() 里传类名 vs 闭包的区别
传类名(如 ValidateOrder::class)由容器自动解析,支持依赖注入;传闭包则变量捕获发生在 pipeline 构建时,无法动态换实例。
- 类必须实现
handle($passable, Closure $next),且第二个参数类型提示必须是Closure,否则容器解析失败 - 闭包适合快速验证逻辑,比如开发期临时加日志:
function ($order, $next) { \Log::debug('before lock'); return $next($order); } - 传
[AuthCheck::class, 'handle']也是合法的,但不如直接写类名简洁;不推荐用['App\Pipes\AuthCheck', 'process']这种非标准方法名 - 避免混合使用:同一个
through()数组里既传类名又传闭包,会增加维护认知负担
什么时候该用 Pipeline,而不是写普通方法链
只有当这组操作满足「可复用 + 可插拔 + 输入输出契约一致」时,Pipeline 才带来真实收益。否则就是过度设计。
- 适合场景:订单创建后「校验库存 → 扣减库存 → 生成订单号 → 发送通知 → 记录审计日志」,每步都只依赖上一步返回的
$order实例 - 不适合场景:控制器里简单判断
if ($request->has('search')) { ... },硬套 pipeline 只会让代码更难读 - 关键区别:方法链(
$order->validate()->reserve()->notify())轻量但难动态增删步骤;Pipeline支持运行时条件组装(如根据租户配置跳过某步),也方便单元测试隔离每个 pipe - 性能影响极小,但调试成本明显上升——不能用
dd()直接断点进某个 pipe,得靠日志或异常抛出定位
HTTP 请求处理请绕开手动 Pipeline
不要在控制器里用 app(Pipeline::class)->send($request)->through([...])->then(...) 处理请求逻辑。Laravel 的中间件机制已经封装了异常转换、响应返回、日志记录等完整能力,手动 pipeline 会绕过这些,导致 bug 难以排查。
- 典型错误:
EnsureUserCanEditPost::class这类标准中间件,handle签名是(Request $request, Closure $next),而Pipeline默认只传一个参数,直接报Undefined variable: next - 正确做法:把逻辑拆成标准中间件,在
app/Http/Kernel.php的$middlewareGroups里注册,路由用middleware(['can:edit,post'])绑定 - 例外情况:CLI 命令、队列任务、事件监听器这类无 HTTP 生命周期的上下文,
Pipeline是合理选择 - 如果真想复用中间件类,必须重写其
handle方法为单参数,但这破坏了与框架中间件体系的兼容性,不推荐
最常被忽略的一点:管道里每一步的返回值类型必须稳定。A 步返回 Collection,B 步却假设是 Model,运行时才会暴露问题——类型断裂比逻辑错误更难调试。


















