news 2026/4/19 14:59:14

状态模式(State):订单状态机(待支付 → 已支付 → 已发货)在 Laravel 中如何实现?是否使用状态模式?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
状态模式(State):订单状态机(待支付 → 已支付 → 已发货)在 Laravel 中如何实现?是否使用状态模式?

Laravel 本身并未内置状态机组件,但“订单状态流转”这类场景正是状态模式(State Pattern)的经典用武之地。虽然 Laravel 核心未强制使用状态模式,但在复杂业务系统中,通过状态模式实现订单状态机是推荐的最佳实践,它能有效避免“巨型 if-else”或“状态硬编码”,使状态转换逻辑清晰、可扩展、可测试。


一、状态模式的核心思想(GoF 定义)

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类

  • Context(上下文):拥有状态的对象(如Order);
  • State(状态接口):定义状态行为(如handlePayment());
  • ConcreteState(具体状态):实现具体行为(如PendingState,PaidState);
  • 关键将状态相关的行为封装在状态类中,而非 Context 中

在订单系统中:

  • Context=Order模型;
  • State=OrderState接口;
  • ConcreteState=PendingState,PaidState,ShippedState

二、Laravel 中实现订单状态机的两种方式

方式 1:简单状态字段(反模式)
// app/Models/Order.phpclassOrderextendsModel{publicfunctionmarkAsPaid(){if($this->status!=='pending'){thrownewLogicException('Order is not pending');}$this->status='paid';$this->save();}publicfunctionship(){if($this->status!=='paid'){thrownewLogicException('Order is not paid');}$this...status='shipped';$this->save();}}
问题:
  • 状态逻辑散落在模型中
  • 新增状态需修改模型(违反开闭原则);
  • 状态转换规则隐式(需阅读代码才能知道paid → shipped是否合法);
  • 难以测试(需构造特定状态)。

这是“状态字段 + 行为方法”的混合体,不是状态模式


方式 2:状态模式实现(推荐)
步骤 1:定义状态接口
// app/Orders/OrderState.phpinterfaceOrderState{publicfunctionmarkAsPaid(Order$order):void;publicfunctionship(Order$order):void;publicfunctioncancel(Order$order):void;}
步骤 2:实现具体状态
// app/Orders/States/PendingState.phpclassPendingStateimplementsOrderState{publicfunctionmarkAsPaid(Order$order):void{$order->status='paid';$order->state=newPaidState();// ← 关键:状态对象变更$order->save();}publicfunctionship(Order$order):void{thrownewLogicException('Cannot ship pending order');}publicfunctioncancel(Order$order):void{$order->status='cancelled';$order->state=newCancelledState();$order->save();}}// app/Orders/States/PaidState.phpclassPaidStateimplementsOrderState{publicfunctionmarkAsPaid(Order$order):void{thrownewLogicException('Order is already paid');}publicfunctionship(Order$order):void{$order->status='shipped';$order->state=newShippedState();$order->save();}publicfunctioncancel(Order$order):void{// 已支付订单取消需退款RefundService::process($order);$order->status='cancelled';$order->state=newCancelledState();$order->save();}}
步骤 3:Order 模型持有状态对象
// app/Models/Order.phpclassOrderextendsModel{protected$casts=['state'=>StateCaster::class,// ← 自定义 caster 序列化状态];publicfunction__construct(array$attributes=[]){parent::__construct($attributes);$this->state=$this->state??newPendingState();}publicfunctionmarkAsPaid():void{$this->state->markAsPaid($this);}publicfunctionship():void{$this->state->ship($this);}}
步骤 4:自定义 Caster(序列化状态对象)
// app/Orders/StateCaster.phpclassStateCasterimplementsCastsAttributes{publicfunctionget($model,string$key,$value,array$attributes){if(!$value)returnnewPendingState();$class='App\\Orders\\States\\'.ucfirst($attributes['status']).'State';returnnew$class();}publicfunctionset($model,string$key,$value,array$attributes){return$value::class;// 存储类名}}

状态行为由状态类封装,Order 模型仅委托调用


三、状态模式 vs 简单状态字段

特性简单状态字段状态模式
状态行为散落在模型中封装在状态类中
新增状态修改模型(违反 OCP)新增状态类(符合 OCP)
状态规则隐式(需读代码)显式(状态类即文档)
可测试性需构造状态直接测试状态类
复杂逻辑模型臃肿逻辑分散到状态类

状态模式让“状态转换”成为一等公民


四、Laravel 生态中的状态机包

虽然可手动实现,但 Laravel 社区有成熟的状态机包:

1.spatie/laravel-model-states(推荐)
  • 专为 Eloquent 设计;
  • 支持状态转换、守卫、事件;
  • 无需手动管理状态对象。
示例:
// app/Models/Order.phpuseSpatie\ModelStates\HasStates;classOrderextendsModel{useHasStates;protectedfunctionregisterStates():void{$this->addState('status',OrderStatus::class)->allowTransition(Pending::class,Paid::class)->allowTransition(Paid::class,Shipped::class)->allowTransition([Paid::class,Shipped::class],Cancelled::class);}}// app/Models/States/OrderStatus.phpabstractclassOrderStatusextendsState{abstractpublicfunctionlabel():string;}classPendingextendsOrderStatus{publicfunctionlabel():string{return'Pending';}publicfunctionpay():void{$this->model->transitionTo(Paid::class);}}

这是状态模式的现代化、Laravel 化实现

2.winzou/state-machine
  • 更通用的状态机库;
  • 需手动集成到模型。

五、与你工程理念的深度对齐

你的原则在状态模式中的体现
关注点分离状态行为与模型数据分离
可扩展性新增状态无需修改现有代码
可测试性状态类可独立单元测试
避免硬编码状态转换规则显式声明
SOLID 遵循符合开闭原则(OCP)、单一职责(SRP)

六、何时使用状态模式?

场景推荐方式
简单状态(< 3 个,无复杂逻辑)简单状态字段 + 守卫方法
复杂状态机(> 3 个状态,有转换规则、副作用)状态模式spatie/laravel-model-states
需要审计日志、事件、守卫使用状态机包

状态模式不是银弹,但在复杂业务中是必要解耦手段


七、完整最佳实践示例(使用 Spatie 包)

1. 安装
composerrequire spatie/laravel-model-states
2. 定义状态
// app/Models/States/OrderStatus.phpabstractclassOrderStatusextendsState{abstractpublicfunctioncanBePaid():bool;abstractpublicfunctionpay(Order$order):void;}classPendingextendsOrderStatus{publicfunctioncanBePaid():bool{returntrue;}publicfunctionpay(Order$order):void{$order->transitionTo(Paid::class);Mail::to($order->user)->send(newOrderPaidMail($order));}}classPaidextendsOrderStatus{publicfunctioncanBePaid():bool{returnfalse;}publicfunctionpay(Order$order):void{thrownewLogicException('Already paid');}}
3. 模型集成
classOrderextendsModel{useHasStates;protectedfunctionregisterStates():void{$this->addState('status',OrderStatus::class)->default(Pending::class)->allowTransition(Pending::class,Paid::class);}}
4. 使用
$order=Order::find(1);if($order->status->canBePaid()){$order->status->pay($order);}

状态行为、转换规则、副作用全部封装,模型保持纯净


结语

虽然 Laravel 核心未内置状态模式,但在订单、工单、审批流等复杂状态场景中,状态模式是解决“状态爆炸”和“逻辑混乱”的利器。它通过:

状态接口 + 具体状态类 + 上下文委托

实现了:

  • 状态行为的封装与隔离
  • 状态转换规则的显式声明
  • 业务逻辑的高内聚、低耦合

正如你所坚持的:好的架构不是预测所有变化,而是让变化发生时,修改最小化
状态模式正是这一理念的典范——当你新增一个“已退货”状态,只需写一个类,订单模型一行代码不动

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 19:11:34

3步掌握TwitchLeecher:高效下载Twitch直播录像的实用指南

3步掌握TwitchLeecher&#xff1a;高效下载Twitch直播录像的实用指南 【免费下载链接】TwitchLeecher Twitch Leecher - The Broadcast Downloader 项目地址: https://gitcode.com/gh_mirrors/tw/TwitchLeecher 你是否曾为错过精彩的Twitch直播而遗憾&#xff1f;或者想…

作者头像 李华
网站建设 2026/4/18 12:45:21

【Java毕设源码分享】基于springboot+vue的图书推荐系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/19 16:22:10

23、组件导向架构学习与实践

组件导向架构学习与实践 1. 链表枚举流程 在处理链表元素时,会经历以下流程: 1. 调用 LinkedListEnumerable.GetEnumerator() 方法,返回一个 IEnumerator 实例。 2. 代码调用 LinkedListEnumerable.MoveNext() 方法。 3. MoveNext() 方法的实现返回 True 表示…

作者头像 李华
网站建设 2026/4/17 14:06:42

27、数据处理与持久化相关技术解析

数据处理与持久化相关技术解析 1. 任务标记的使用 Visual Basic Express和Visual Studio产品支持嵌入任务标记。例如在源代码示例中,有如下注释: TODO: Finish implementing the class这里的“TODO”全为大写,这种注释被称为任务,会被Visual Basic Express在任务列表窗…

作者头像 李华
网站建设 2026/4/18 22:19:17

33、.NET 应用配置与动态加载全解析

.NET 应用配置与动态加载全解析 1. 架构概述 在应用开发中,约定架构和配置架构各有其独特之处。约定架构的优势在于,它不受配置文件中定义内容的限制,因为其背后存在通用逻辑。在实现约定架构时,并非摒弃配置,而是为用户和代码实现做出一些假设。通常仍会有配置文件,但…

作者头像 李华
网站建设 2026/4/18 7:51:26

一劳永逸!RWTS-PDFwriter:macOS虚拟打印机完美解决方案

一劳永逸&#xff01;RWTS-PDFwriter&#xff1a;macOS虚拟打印机完美解决方案 【免费下载链接】RWTS-PDFwriter An OSX print to pdf-file printer driver 项目地址: https://gitcode.com/gh_mirrors/rw/RWTS-PDFwriter 还在为macOS系统下PDF转换而烦恼吗&#xff1f;R…

作者头像 李华