本文目录导读:

在 PHP 中设计工作流(Workflow)没有统一的银弹,核心在于根据项目的复杂度选择不同的架构模式,工作流设计的关键在于将业务逻辑与流程控制分离。
以下是 PHP 工作流设计的阶梯式指南,从简单到复杂,以及对应的代码示例。
第一层:基础状态机(适合简单流程)
如果流程固定(如:待审核 -> 通过 -> 完成),不需要复杂的并发控制,使用状态机(State Machine)是最直接的方式。
核心概念:状态(Status)、事件(Event)、迁移(Transition)。 优点:极简、易读、性能高、无需引入重型中间件。 缺点:不适合复杂的回退、条件分支和长流程。
设计示例(使用 symfony/workflow):
use Symfony\Component\Workflow\Definition;
use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
// 1. 定义状态和迁移
$places = ['created', 'pending', 'approved', 'rejected'];
$transitions = [
new Transition('submit', ['created'], ['pending']),
new Transition('approve', ['pending'], ['approved']),
new Transition('reject', ['pending'], ['rejected']),
];
$definition = new Definition($places, $transitions);
$marking = new MethodMarkingStore(true, 'status'); // 将状态保存到 entity 的 status 属性
$workflow = new Workflow($definition, $marking);
// 2. 使用
$order = new Order(); // 假设有 order 实体
$workflow->apply($order, 'submit'); // 触发 submit 事件
echo $order->getStatus(); // 输出: pending
第二层:状态模式 + 策略模式(适合中大型业务逻辑)
当状态迁移不仅仅是简单的布尔判断,还伴随复杂的业务动作(如审批通过后要发邮件、扣库存、写日志)时,单纯的状态机无法满足,此时需要使用 PHP 设计模式 来封装。
核心思想:每个状态是一个类,每个动作是一个方法,通过上下文(Context)切换状态。
设计示例:
// 状态接口
interface OrderState
{
public function approve(OrderContext $context);
public function reject(OrderContext $context);
public function getStatus();
}
// 具体状态类:待审核
class PendingState implements OrderState
{
public function approve(OrderContext $context)
{
// 执行具体的审批业务逻辑(发邮件、调用外部API等)
$context->setState(new ApprovedState());
}
public function reject(OrderContext $context)
{
$context->setState(new RejectedState());
}
public function getStatus() { return 'pending'; }
}
// 最终状态类:已通过
class ApprovedState implements OrderState
{
// 已通过后,不能再次审批或驳回,方法留空或抛异常
public function approve(OrderContext $context) { throw new \Exception('Invalid action'); }
public function reject(OrderContext $context) { throw new \Exception('Invalid action'); }
public function getStatus() { return 'approved'; }
}
// 上下文类(核心门面)
class OrderContext
{
private OrderState $state;
public function __construct(OrderState $state) {
$this->state = $state;
}
public function setState(OrderState $state) {
$this->state = $state;
}
public function approve() {
$this->state->approve($this);
}
public function reject() {
$this->state->reject($this);
}
}
// 使用
$order = new OrderContext(new PendingState());
$order->approve();
$order->reject(); // 此时状态是 Approved,会抛出异常
第三层:流程编排引擎(适合复杂审批流、微服务)
如果流程是动态配置的(比如运营后台可以自由拖拽流程节点),或者涉及多个系统间的异步调用,就需要引入流程引擎。
推荐方案:
-
BPMN 2.0 引擎:使用成熟的
Camunda或Flowable。- 它们提供了基于 XML 的流程图定义,有可视化界面。
- PHP 通过 REST API 调用引擎服务(通常这些引擎是 Java 写的)。
- 优点:功能强大,支持并行网关、排他网关、定时器。
- 缺点:需要额外的服务部署,学习成本高,对于纯 PHP 团队运维成本大。
-
自研事件驱动工作流: 以任务队列(Redis Queue / RabbitMQ) + 规则引擎为基础构建。 流程定义存在数据库中,每个节点的下一步通过配置决定,任务消息在队列中流转。
设计思路:
- 流程定义表:定义流程 ID,节点列表。
- 流程实例表:记录当前走到哪一步,携带数据。
- 工作节点处理器:每个节点对应一个 PHP 类,实现统一的
handle()方法。 - 路由逻辑:根据当前节点的返回结果,决定下一个节点。
// 1. 定义节点处理器接口 interface NodeHandlerInterface { // 返回下一个节点的名称,或者 null 表示流程结束 public function handle(array $payload): ?string; } // 2. 具体节点(人工审批节点) class ApprovalNode implements NodeHandlerInterface { public function handle(array $payload): ?string { // 模拟人工操作,实际中可能等待回调 $approved = $payload['approved'] ?? false; return $approved ? 'complete' : 'resubmit'; } } // 3. 流程引擎核心(伪代码) class WorkflowEngine { private array $nodes; // ['start' => HandlerA, 'approval' => ApprovalNode] public function execute(string $startNode, array $payload): void { $currentNode = $startNode; while ($currentNode !== null) { $handler = $this->nodes[$currentNode]; // 获取处理器 $nextNode = $handler->handle($payload); // 执行并获取下一步 $this->updateProcessInstance($payload['process_id'], $nextNode); // 更新数据库流程状态 $currentNode = $nextNode; } } }
关键设计原则与避坑指南
-
不要把所有的状态逻辑写在
Model里。 如果在一个order类的if/elseif里写了 10 个状态判断,代码必然会在某次需求迭代中不可维护,务必把状态和业务动作封装成类。 -
事务与幂等性。 工作流的执行通常伴随数据库写入,如果因为异常导致流程中断,需要考虑分布式事务(尽量使用本地消息表+任务队列)或幂等性设计(同一任务只能执行一次)。
-
回调与异步的配合。 流程中常有“等待第三方回调”(如支付成功、人工审批),此时不要阻塞线程。 推荐做法:工作流运行到“等待节点”时,将状态持久化为
waiting,并生成唯一的token存入 Redis 并设置过期时间;当回调到达时,携带该 token 找到流程实例,再继续往下走。 -
可追溯性(审计日志)。 无论多简单的流程,都要有工作流日志表(记录:谁、什么时间、将谁从状态A改到了状态B、备注是什么),这是排查生产事故的救命稻草。
如何选择?
| 场景 | 推荐方案 |
|---|---|
| 后台管理简单审批(如文章审核) | 方案一:symfony/workflow 状态机 |
| 电商订单核心生命周期(包含复杂库存、优惠券等联动业务) | 方案二:状态模式 + 策略模式 + 事件监听 |
| 大型 OA 系统 / 需要画流程图动态配置 | 方案三:引入成熟 BPM 引擎 (Camunda) |
| 微服务架构内部的跨服务数据一致性流程 | 方案三:自研事件驱动(Saga 模式)+ 消息队列 |