PHP 工作流怎么设计

wen PHP项目 5

本文目录导读:

PHP 工作流怎么设计

  1. 第一层:基础状态机(适合简单流程)
  2. 第二层:状态模式 + 策略模式(适合中大型业务逻辑)
  3. 第三层:流程编排引擎(适合复杂审批流、微服务)
  4. 关键设计原则与避坑指南
  5. 总结:如何选择?

在 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,会抛出异常

第三层:流程编排引擎(适合复杂审批流、微服务)

如果流程是动态配置的(比如运营后台可以自由拖拽流程节点),或者涉及多个系统间的异步调用,就需要引入流程引擎

推荐方案

  1. BPMN 2.0 引擎:使用成熟的 CamundaFlowable

    • 它们提供了基于 XML 的流程图定义,有可视化界面。
    • PHP 通过 REST API 调用引擎服务(通常这些引擎是 Java 写的)。
    • 优点:功能强大,支持并行网关、排他网关、定时器。
    • 缺点:需要额外的服务部署,学习成本高,对于纯 PHP 团队运维成本大。
  2. 自研事件驱动工作流: 以任务队列(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;
            }
        }
    }

关键设计原则与避坑指南

  1. 不要把所有的状态逻辑写在 Model。 如果在一个 order 类的 if/elseif 里写了 10 个状态判断,代码必然会在某次需求迭代中不可维护,务必把状态和业务动作封装成类。

  2. 事务与幂等性。 工作流的执行通常伴随数据库写入,如果因为异常导致流程中断,需要考虑分布式事务(尽量使用本地消息表+任务队列)或幂等性设计(同一任务只能执行一次)。

  3. 回调与异步的配合。 流程中常有“等待第三方回调”(如支付成功、人工审批),此时不要阻塞线程。 推荐做法:工作流运行到“等待节点”时,将状态持久化为 waiting,并生成唯一的 token 存入 Redis 并设置过期时间;当回调到达时,携带该 token 找到流程实例,再继续往下走。

  4. 可追溯性(审计日志)。 无论多简单的流程,都要有工作流日志表(记录:谁、什么时间、将谁从状态A改到了状态B、备注是什么),这是排查生产事故的救命稻草。


如何选择?

场景 推荐方案
后台管理简单审批(如文章审核) 方案一symfony/workflow 状态机
电商订单核心生命周期(包含复杂库存、优惠券等联动业务) 方案二:状态模式 + 策略模式 + 事件监听
大型 OA 系统 / 需要画流程图动态配置 方案三:引入成熟 BPM 引擎 (Camunda)
微服务架构内部的跨服务数据一致性流程 方案三:自研事件驱动(Saga 模式)+ 消息队列

抱歉,评论功能暂时关闭!