职责链模式通过可变上下文驱动节点自主决策,各Handler基于状态判断是否处理、终止或透传,统一返回passed/next/errors;链动态组装、支持插拔与加签,驳回重提由节点内状态检查实现。

职责链模式不是把审批步骤“串起来”,而是让每个审批节点自主决定“我该不该管、管完还用不用往下传”。关键在于上下文状态能被所有节点读写,且跳过、加签、驳回重提这些动作都靠节点内部逻辑驱动,而不是靠外部硬编码控制流程走向。
统一可变上下文,承载审批状态与业务数据
不能用原始数组传参,必须封装成可变对象(如 DTO 类或标准 stdClass 实例)。每个 Handler 都能往里面写字段:比如 approved_by、approved_at、finance_sign、errors 等。这样后续节点才能基于前序结果做判断。
- 初始 Context 至少包含业务主键(如 order_id)、核心参数(amount、department、urgency)和空状态字段(如 ['passed' => true, 'errors' => []])
- FinanceHandler 审批通过后,主动写入 $context['finance_sign'] = ['user_id' => 123, 'at' => time()]
- DirectorHandler 的 canHandle() 可检查 isset($context['finance_sign']) && $context['amount'] > 5000,满足才介入
每个节点自主决策是否参与、是否终止、是否透传
Handler 不必强制调用 next,而是根据当前上下文状态决定行为路径。返回值结构需统一,推荐含三个关键键:passed(整体是否通过)、next(是否继续流转)、errors(本节点产生的错误)。
- ManagerHandler 处理金额 ≤ 3000 的单子:满足则设 'passed' => true,'next' => false;不满足直接 return ['passed' => true, 'next' => true, 'errors' => []]
- 驳回场景:FinanceHandler 检查到发票缺失,返回 ['passed' => false, 'next' => false, 'errors' => ['invoice_missing']],链自然中断
- 兜底逻辑必须存在:若所有 canHandle() 都返回 false 或所有 handle() 均未设 'passed',最终应抛出 UnhandledRequestException,避免静默失败
动态组装链,支持运行时插拔与策略切换
链的构建不能写死在 Controller 或 Service 初始化里,而要从配置或缓存中读取 Handler 列表,并按 getOrder() 排序后注入 next 引用。
- 配置文件 config/approval_chain.php 返回 ['FinanceHandler', 'LegalHandler', 'DirectorHandler'],按 getOrder() 升序排列后构建链
- 某次上线需临时关闭 LegalHandler?只需在配置中移除类名,或让其 canHandle() 恒返回 false
- 加签不新增节点:FinanceHandler 内部检测到金额超 50000,主动调用 (new LegalHandler())->handle($context),并合并 errors,再决定是否继续传给 DirectorHandler
驳回重提自动跳过已审节点,靠状态驱动而非流程硬编码
重提时不再走完整链,而是由每个节点自己查数据库或缓存,判断“上次是否已审且未变更关键字段”,从而决定是否跳过。
- ManagerHandler::handle() 开头查 Cache::get("approval:{$context['order_id']}:manager"),存在且 status == 'approved',则直接 return ['passed' => true, 'next' => true]
- 若申请人修改了金额,Cache 中对应 key 被清空,该节点就会重新执行审批逻辑
- 所有跳过逻辑集中在各 Handler 内部,不污染链结构,也不增加 Controller 层分支判断


















