责任链模式的核心是动态组装可跳过、可中断、可复用的处理器链,而非硬编码条件分支;需通过抽象基类+setNext()实现松耦合连接,避免依赖固化,并确保request不可变以防止状态污染。

责任链模式的核心是让多个处理器按顺序尝试处理请求
PHP 实现责任链,关键不是“怎么写类”,而是避免把 handle() 写成硬编码的 if-else 链或 switch。真正的责任链必须支持动态组装、可跳过、可中断、可复用。如果你只是写几个 if ($type === 'a') { ... } elseif ($type === 'b') { ... },那不算责任链,只是条件分支。
用抽象基类 + setNext() 构建可插拔链条
每个处理器继承统一抽象类,通过 setNext() 显式连接下一个节点,而不是在内部 new 具体类。这样测试、替换、跳过某个环节才真正可行。
常见错误:在 handle() 里直接调用 new ValidatorA()->handle() —— 这会让依赖固化,无法 mock,也无法在运行时调整顺序。
- 所有处理器实现同一个接口或继承同一抽象类,强制定义
handle()和setNext() -
setNext()接收的是同类型对象(如HandlerInterface),不是具体类名 - 每个
handle()必须显式判断是否“不处理”,然后调用$this->next?->handle($request),不能漏掉?(PHP 8.0+ 空安全操作符) - 不要在基类里自动调用
next—— 有些场景需要终止(比如权限拒绝后不该继续审核)
实际使用中要注意 request 的传递方式和状态污染
责任链里最常踩的坑是共享一个 mutable 的 $request 对象,前面的处理器改了字段,后面的逻辑被悄悄影响。比如 $request->status = 'validated' 后,后续处理器可能误判流程阶段。
立即学习“PHP免费学习笔记(深入)”;
- 推荐把
$request设计为不可变(immutable):每次修改返回新实例,或用clone+ 属性覆盖 - 如果用数组传参,注意深拷贝 ——
$next->handle($request)前别忘了$request = array_merge([], $request)或用json_decode(json_encode($request), true)(仅小数据) - 调试时加日志输出当前处理器名和
$request关键字段,能快速定位哪一环意外改了值 - PHP 8.0+ 可考虑用
readonly class包装 request,从语言层杜绝篡改
性能敏感场景下避免递归调用栈溢出
当链很长(比如 50+ 节点)且每层都调用 handle(),PHP 默认栈深度可能触发 Fatal error: Maximum function nesting level。这不是设计问题,是执行限制。
- 用循环代替递归:主入口持有一个处理器数组,
foreach遍历并调用$handler->handle($request),每个处理器只负责“自己干或不干”,不主动调next - 此时
setNext()失去意义,改用外部组装数组:$chain = [$auth, $rateLimit, $validation, $logging] - 若需中途中断(如鉴权失败立即退出),在循环内检查处理器返回值(如
return false或抛HandlerStoppedException) - 这种扁平化写法更易 debug,也避免了
handle()方法签名被强迫带$next参数
责任链最难的部分从来不是代码结构,而是决定“谁该处理什么”以及“什么时候该停”。业务规则一变,链条顺序、跳过条件、终止策略全得重审 —— 别指望靠设计模式自动解决语义问题。



















