Laravel工作流用状态机吗

wen PHP项目 17

本文目录导读:

Laravel工作流用状态机吗

  1. 什么是状态机?为什么适合 Laravel?
  2. Laravel 中实现状态机的常用方案
  3. 什么时候应该用状态机?
  4. 不使用状态机可能遇到的问题

在 Laravel 工作流设计中,状态机是一个非常推荐且常用的核心模式,尤其是处理复杂的业务流转(如订单状态、审批流程、工单系统)。

简单直接的回答:不一定强制使用,但强烈建议使用

为了让你更清晰地理解,我从以下几个角度分析:

什么是状态机?为什么适合 Laravel?

状态机是一种数学模型,它定义了一个对象只能处于有限数量的“状态”之一,并且通过“事件”或“动作”在状态之间进行“转换”。

  • 状态pending, approved, rejected, shipped
  • 转换submit 动作将 pending 转换为 approved
  • 守卫(Guard):转换发生的条件(只有管理员才能执行 approve)。
  • 动作(Action):转换发生时执行的副作用(发送邮件、记录日志)。

为什么适合 Laravel?

  • 业务清晰:用代码显式定义所有允许的状态和转换,避免数据库中出现非法状态(例如直接从 pending 跳到 shipped)。
  • 可维护性:状态和转换逻辑集中管理,而不是分散在控制器、模型事件中。
  • 可扩展性:添加新状态或转换只需修改状态机配置,无需大改业务代码。

Laravel 中实现状态机的常用方案

Laravel 生态中有两个非常流行的包,它们本质上都是状态机的实现,但使用方式不同:

A. 专业状态机包:spatie/laravel-model-states

  • 核心思想:将“状态”本身定义为一个 (Class),每个状态都是一个独立的类,拥有自己的行为、属性和转换规则。
  • 优点
    • 面向对象:状态可以拥有方法(isTransitionsAllowed()serialize())。
    • 类型安全:IDE 自动补全强。
    • 职责清晰:状态转换逻辑封装在状态类内部。
  • 适用场景非常复杂的工作流,例如审批流(每个状态都有不同的表单、权限、通知逻辑)。

示例代码(简化):

// 1. 定义状态类
class PendingState extends OrderState
{
    public function transitionTo(OrderState $nextState): OrderState
    {
        if ($nextState instanceof PaidState) {
            // 执行支付逻辑
            Mail::to($this->model->user)->send(new OrderPaidMail());
            return $nextState;
        }
        throw new InvalidTransitionException($this, $nextState);
    }
}
// 2. 在 Model 中使用
class Order extends Model
{
    use HasStates;
    // 定义状态列和对应的状态类映射
    protected $casts = [
        'status' => OrderState::class, // 自动反序列化
    ];
}
// 3. 使用
$order->status->transitionTo(new PaidState($order));

B. 枚举 + 业务逻辑层:spatie/laravel-enum + 自定义 Service

  • 核心思想:用 枚举(Enum) 定义状态列表,用 Service 类 封装转换逻辑。
  • 优点
    • 简单、轻量,不需要额外依赖太大的包。
    • 对于状态数少、转换逻辑简单的场景非常合适。
  • 适用场景中等复杂度的工作流,例如文章发布状态(草稿、已发布、已删除)。

示例代码(简化):

// 1. 定义枚举
class OrderStatus extends Enum
{
    const PENDING = 'pending';
    const PAID = 'paid';
    const SHIPPED = 'shipped';
}
// 2. 在 Service 中定义转换规则
class OrderService
{
    public function pay(Order $order): void
    {
        $this->ensureTransitionAllowed($order->status, OrderStatus::PAID);
        DB::transaction(function () use ($order) {
            $order->status = OrderStatus::PAID;
            $order->save();
            // 执行动作
            event(new OrderPaidEvent($order));
        });
    }
    private function ensureTransitionAllowed(string $from, string $to): void
    {
        $allowedMap = [
            OrderStatus::PENDING => [OrderStatus::PAID],
            OrderStatus::PAID => [OrderStatus::SHIPPED],
            // ...
        ];
        if (!in_array($to, $allowedMap[$from] ?? [])) {
            throw new \LogicException("Cannot transition from {$from} to {$to}");
        }
    }
}

什么时候应该用状态机?

场景 推荐方案 理由
简单状态(如:是否启用,草稿/发布) 枚举 + Service 状态机方案过于沉重,枚举足够。
中等复杂(如:订单状态) 两种均可 如果未来可能变复杂,建议直接上状态机。
非常复杂(如:工作流审批、多级审核、状态行为差异大) spatie/laravel-model-states 状态类的多态性可以很好地处理每个状态独有的逻辑。
审批流(Workflow) 状态机 + 工作流引擎(如 symfony/workflow 如果涉及并行、回滚、历史记录等高级功能,可以考虑 Symfony Workflow 包(有 Laravel 适配版本)。

不使用状态机可能遇到的问题

  • 状态膨胀if-else ifswitch-case 在多个控制器、队列、事件监听器中重复出现。
  • 隐形漏洞$order->update(['status' => 'shipped']) 直接修改数据库,绕过了所有业务逻辑(如发货前必须已支付)。
  • 难以审计:无法轻易知道一个状态为何可以转换到另一个状态,以及转换时发生了什么。

在 Laravel 工作流中,强烈推荐使用状态机模式,尤其是当你的业务逻辑包含明确的、有限的状态和转换规则时。

  • spatie/laravel-model-states:如果你希望状态本身拥有自己的方法、逻辑,并且喜欢强类型和面向对象。
  • Enum + Service:如果你的状态转换逻辑简单,团队规模小,希望快速开发。
  • 不处理:如果工作流极其复杂(涉及并行、分支、合并),可以考虑专门的工作流引擎(如 Ruby 的 Workflow 或 PHP 的 Symfony Workflow),但 Laravel 生态中 spatie/laravel-model-states 已经能覆盖 90% 的业务场景了。

建议你从 spatie/laravel-model-states 入手,它的文档清晰,社区活跃,且设计优雅。

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