Eloquent 的 Attribute 不提供流程管理或状态机能力,需用 spatie/laravel-states 实现受控状态流转,复杂流程应交由独立 Process Manager 处理。

Eloquent 的 Attribute 本身不提供“流程管理器”或“状态机”能力,Process Manager 和 States 也不是 Laravel 原生 Eloquent 的内置概念 —— 这类功能必须通过组合扩展实现,否则直接写在 get* / set* 访问器里只会导致逻辑耦合、状态不可追踪、并发下出错。
为什么不能把状态流转写在 Eloquent Accessor/Mutator 里
Accessor(如 getOrderStatusAttribute)和 Mutator(如 setOrderStatusAttribute)是被动计算/转换层,不是状态协调入口。它们没有生命周期钩子、无法拦截非法跃迁、也不记录变更上下文。
- 你无法阻止
$order->status = 'shipped'在订单还是draft时被直接赋值 - 状态变更副作用(如发通知、扣库存)没法统一触发,容易漏或重复
- 并发请求同时修改同一模型时,
if ($this->status === 'pending') $this->status = 'processing'不具备原子性 - Laravel 的
casts或attributes数组也仅做类型转换,不承载业务规则
用 Laravel States 实现可验证的状态流转
spatie/laravel-states 是目前最贴合你描述的“属性流程管理器状态”的方案:它把状态建模为独立类,强制跃迁需经 transitionTo(),支持条件校验、事件广播、历史记录。
- 安装后定义状态类,例如
PendingState、ProcessingState、ShippedState,每个类继承State并实现canTransitionTo() - 在模型中声明状态属性:
protected $casts = ['status' => OrderStatus::class]; - 触发流转必须调用:
$order->status->transitionTo(ProcessingState::class),失败会抛InvalidStateTransition - 所有跃迁自动触发
StateTransitioned事件,你在监听器里处理发短信、更新关联表等副作用 - 底层仍存字符串(如
'processing')到数据库,不影响查询,但行为受控
复杂流程需要 Process Manager 时,别塞进模型里
当状态依赖外部系统响应(如支付网关回调)、需跨多个模型协调(订单+库存+物流单)、或含人工环节(审核待确认),就得抽离成独立服务。Eloquent 模型只负责持久化,不承担编排职责。
立即学习“PHP免费学习笔记(深入)”;
- 新建
OrderFulfillmentProcess类,构造函数接收Order实例,内部封装各步骤方法:verifyPayment()、reserveInventory()、scheduleDelivery() - 每步失败时抛出自定义异常(如
PaymentVerificationFailed),由调用方决定重试或降级 - 关键节点写入
process_logs表,字段含order_id、step、status、error,而不是靠模型$order->last_process_step这种弱字段 - 避免在
Order的saving事件里启动整个流程 —— 事件应轻量,流程启动应显式调用,比如app(OrderFulfillmentProcess::class)->start($order)
常见踩坑:混淆「状态显示」和「状态控制」
很多人把前端按钮是否禁用、文案怎么渲染(“已发货” or “待支付”)和后端状态决策混在一起,结果改个 UI 就要动核心逻辑。
- 前端展示用
status_label计算属性(accessor)没问题,但它只读、无副作用 - 后端状态变更入口必须唯一且受控,比如只有
OrderService::confirmPayment()能让订单进paid状态 - 不要给状态字段加
fillable,禁止$order->update(['status' => 'delivered'])这种直写 - 数据库字段保持简单(
VARCHAR(20)),别用 JSON 存整个状态机配置 —— 配置应代码化、可测试、可版本控制
真正的难点不在怎么写 transition 方法,而在于厘清谁有权触发哪一步、失败后如何回滚、以及日志能否还原完整路径。模型属性只是状态的快照,不是流程的控制器。



















