用可调用对象数组+递归调用handle()实现职责链式Middleware,每个Handler接收$request和$next参数,不调用$next即中断,调用则继续;需手动串联、处理空数组边界及异常兜底。

PHP里怎么用职责链模拟Middleware?
直接说结论:用可调用对象数组 + 递归调用 handle() 方法,比硬套设计模式更轻量、更贴近实际Web中间件行为。职责链不是必须“每个节点都继承同一抽象类”,PHP里函数或闭包本身就能当Handler,关键在控制权传递逻辑。
怎么写一个能中断/跳转的Handler链?
核心是让每个Handler接收两个参数:$request(当前上下文)和$next(下一个Handler)。不调用$next就自然中断;调用它才继续向下——这和Laravel/Slim的中间件语义完全一致。
常见错误是把$next写成固定函数名或静态调用,导致无法动态组合。正确做法是把$next作为参数传入,由上层决定“下一个是谁”:
// 示例:带权限校验的链
$authHandler = function ($request, $next) {
if (empty($request['user_id'])) {
return ['error' => 'Unauthorized'];
}
return $next($request, $next); // 继续执行下一个
};
$loggingHandler = function ($request, $next) {
error_log("Request: " . json_encode($request));
return $next($request, $next);
};
// 手动串联(顺序即执行顺序)
$middlewareChain = [$authHandler, $loggingHandler, function ($request, $next) {
return ['data' => 'ok'];
}];
// 执行入口:从第一个开始,把下一个Handler作为$next传入
$result = $middlewareChain[0]($request, function ($req, $n) use ($middlewareChain, &$i) {
$i = array_search($n, $middlewareChain) + 1;
return $i < count($middlewareChain) ? $middlewareChain[$i]($req, $n) : null;
});
为什么不用抽象类+next()方法?
PHP中过度封装反而增加心智负担。真实项目里中间件常来自不同来源:框架内置、第三方包、自己写的闭包、invokable class。强行统一接口会导致:
立即学习“PHP免费学习笔记(深入)”;
- 每次新增中间件都要写一个类,哪怕只有一行逻辑
-
next()方法名容易和协程/Generator的next()混淆 - 无法直接用
array_reduce()或call_user_func_array()做链式调用
更实用的做法是约定统一签名:function ($request, $next): mixed。这样连Laravel的Illuminate\Http\Request对象也能塞进去,只要$next能接住就行。
怎么处理异常穿透和返回值统一?
Middleware链最易被忽略的是错误边界。如果某个Handler抛出异常,默认会中断整个链且无法捕获——这不是“职责链”,是“断链”。必须显式包裹:
使用场景:API响应需要统一格式(如['code'=>0, 'data'=>...]),但某个中间件可能抛InvalidArgumentException或返回null。
建议在链底加一层兜底Handler:
$finalHandler = function ($request, $next) {
try {
$result = $next($request, $next);
return is_array($result) && isset($result['error'])
? ['code' => 400, 'message' => $result['error']]
: ['code' => 0, 'data' => $result];
} catch (Throwable $e) {
return ['code' => 500, 'message' => $e->getMessage()];
}
};
注意:$next在这里不是“下一个”,而是你传进来的最终业务逻辑。真正的链组装要靠外部循环或递归闭包,别指望PHP自动帮你串起来。
复杂点在于嵌套层级深时,$next引用容易丢失或错位;最容易被忽略的是“空中间件数组”的边界处理——没Handler时该返回默认值还是抛异常,得提前约定好。



















