Laravel 13 状态模式深度解析 状态模式是一种行为型设计模式,它允许对象在其内部状态改变时改变其行为。本文将深入探讨 Laravel 13 中状态模式的高级用法。
状态模式基础 什么是状态模式 状态模式将对象的行为封装在不同的状态类中,让对象在其内部状态改变时看起来像是改变了其类。
1 2 3 4 5 6 7 8 9 10 <?php namespace App \Contracts ;interface StateInterface { public function handle ( ): void ; public function getName ( ): string ; }
订单状态管理 订单状态接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php namespace App \Contracts \States ;interface OrderStateInterface { public function process ( ): void ; public function cancel ( ): void ; public function ship ( ): void ; public function deliver ( ): void ; public function refund ( ): void ; public function getStatusName ( ): string ; public function getAvailableTransitions ( ): array ; }
基础订单状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <?php namespace App \States \Order ;use App \Models \Order ;use App \Contracts \States \OrderStateInterface ;abstract class BaseOrderState implements OrderStateInterface { protected Order $order ; public function __construct (Order $order ) { $this ->order = $order ; } public function process ( ): void { throw new \InvalidArgumentException ( "Cannot process order in {$this->getStatusName()} state" ); } public function cancel ( ): void { throw new \InvalidArgumentException ( "Cannot cancel order in {$this->getStatusName()} state" ); } public function ship ( ): void { throw new \InvalidArgumentException ( "Cannot ship order in {$this->getStatusName()} state" ); } public function deliver ( ): void { throw new \InvalidArgumentException ( "Cannot deliver order in {$this->getStatusName()} state" ); } public function refund ( ): void { throw new \InvalidArgumentException ( "Cannot refund order in {$this->getStatusName()} state" ); } public function getAvailableTransitions ( ): array { return []; } }
具体状态实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php namespace App \States \Order ;class PendingState extends BaseOrderState { public function process ( ): void { $this ->order->update ([ 'status' => 'processing' , 'processed_at' => now (), ]); event (new \App\Events\OrderProcessed ($this ->order)); } public function cancel ( ): void { $this ->order->update ([ 'status' => 'cancelled' , 'cancelled_at' => now (), ]); event (new \App\Events\OrderCancelled ($this ->order)); } public function getStatusName ( ): string { return 'pending' ; } public function getAvailableTransitions ( ): array { return ['processing' , 'cancelled' ]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?php namespace App \States \Order ;class ProcessingState extends BaseOrderState { public function ship ( ): void { $this ->order->update ([ 'status' => 'shipped' , 'shipped_at' => now (), ]); event (new \App\Events\OrderShipped ($this ->order)); } public function cancel ( ): void { $this ->order->update ([ 'status' => 'cancelled' , 'cancelled_at' => now (), ]); $this ->order->payment->refund (); event (new \App\Events\OrderCancelled ($this ->order)); } public function getStatusName ( ): string { return 'processing' ; } public function getAvailableTransitions ( ): array { return ['shipped' , 'cancelled' ]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php namespace App \States \Order ;class ShippedState extends BaseOrderState { public function deliver ( ): void { $this ->order->update ([ 'status' => 'delivered' , 'delivered_at' => now (), ]); event (new \App\Events\OrderDelivered ($this ->order)); } public function getStatusName ( ): string { return 'shipped' ; } public function getAvailableTransitions ( ): array { return ['delivered' ]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php namespace App \States \Order ;class DeliveredState extends BaseOrderState { public function refund ( ): void { $this ->order->update ([ 'status' => 'refunded' , 'refunded_at' => now (), ]); $this ->order->payment->refund (); event (new \App\Events\OrderRefunded ($this ->order)); } public function getStatusName ( ): string { return 'delivered' ; } public function getAvailableTransitions ( ): array { return ['refunded' ]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php namespace App \States \Order ;class CancelledState extends BaseOrderState { public function getStatusName ( ): string { return 'cancelled' ; } public function getAvailableTransitions ( ): array { return []; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php namespace App \States \Order ;class RefundedState extends BaseOrderState { public function getStatusName ( ): string { return 'refunded' ; } public function getAvailableTransitions ( ): array { return []; } }
状态工厂 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php namespace App \Factories ;use App \Models \Order ;use App \States \Order \{PendingState , ProcessingState , ShippedState , DeliveredState , CancelledState , RefundedState };use InvalidArgumentException ;class OrderStateFactory { public static function create (Order $order ): OrderStateInterface { return match ($order ->status) { 'pending' => new PendingState ($order ), 'processing' => new ProcessingState ($order ), 'shipped' => new ShippedState ($order ), 'delivered' => new DeliveredState ($order ), 'cancelled' => new CancelledState ($order ), 'refunded' => new RefundedState ($order ), default => throw new InvalidArgumentException ("Unknown order status: {$order->status} " ), }; } }
订单上下文 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <?php namespace App \Models ;use App \Contracts \States \OrderStateInterface ;use App \Factories \OrderStateFactory ;class Order extends Model { protected ?OrderStateInterface $state = null ; public function getState ( ): OrderStateInterface { if ($this ->state === null ) { $this ->state = OrderStateFactory ::create ($this ); } return $this ->state; } public function setState (OrderStateInterface $state ): void { $this ->state = $state ; } public function process ( ): void { $this ->getState ()->process (); $this ->refreshState (); } public function cancel ( ): void { $this ->getState ()->cancel (); $this ->refreshState (); } public function ship ( ): void { $this ->getState ()->ship (); $this ->refreshState (); } public function deliver ( ): void { $this ->getState ()->deliver (); $this ->refreshState (); } public function refund ( ): void { $this ->getState ()->refund (); $this ->refreshState (); } protected function refreshState ( ): void { $this ->refresh (); $this ->state = null ; } }
文档审批状态 文档状态接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php namespace App \Contracts \States ;interface DocumentStateInterface { public function submit ( ): void ; public function approve ( ): void ; public function reject ( ): void ; public function publish ( ): void ; public function archive ( ): void ; public function getStatus ( ): string ; }
文档状态实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php namespace App \States \Document ;use App \Models \Document ;use App \Contracts \States \DocumentStateInterface ;class DraftState implements DocumentStateInterface { protected Document $document ; public function __construct (Document $document ) { $this ->document = $document ; } public function submit ( ): void { $this ->document->update ([ 'status' => 'pending_review' , 'submitted_at' => now (), ]); } public function approve ( ): void { throw new \Exception ('Cannot approve a draft document' ); } public function reject ( ): void { throw new \Exception ('Cannot reject a draft document' ); } public function publish ( ): void { throw new \Exception ('Cannot publish a draft document' ); } public function archive ( ): void { $this ->document->update (['status' => 'archived' ]); } public function getStatus ( ): string { return 'draft' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?php namespace App \States \Document ;use App \Models \Document ;use App \Contracts \States \DocumentStateInterface ;class PendingReviewState implements DocumentStateInterface { protected Document $document ; public function __construct (Document $document ) { $this ->document = $document ; } public function submit ( ): void { throw new \Exception ('Document is already submitted' ); } public function approve ( ): void { $this ->document->update ([ 'status' => 'approved' , 'approved_at' => now (), 'approved_by' => auth ()->id (), ]); } public function reject ( ): void { $this ->document->update ([ 'status' => 'rejected' , 'rejected_at' => now (), 'rejected_by' => auth ()->id (), ]); } public function publish ( ): void { throw new \Exception ('Cannot publish a document pending review' ); } public function archive ( ): void { $this ->document->update (['status' => 'archived' ]); } public function getStatus ( ): string { return 'pending_review' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php namespace App \States \Document ;use App \Models \Document ;use App \Contracts \States \DocumentStateInterface ;class ApprovedState implements DocumentStateInterface { protected Document $document ; public function __construct (Document $document ) { $this ->document = $document ; } public function submit ( ): void { throw new \Exception ('Document is already approved' ); } public function approve ( ): void { throw new \Exception ('Document is already approved' ); } public function reject ( ): void { throw new \Exception ('Cannot reject an approved document' ); } public function publish ( ): void { $this ->document->update ([ 'status' => 'published' , 'published_at' => now (), ]); } public function archive ( ): void { $this ->document->update (['status' => 'archived' ]); } public function getStatus ( ): string { return 'approved' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php namespace App \States \Document ;use App \Models \Document ;use App \Contracts \States \DocumentStateInterface ;class PublishedState implements DocumentStateInterface { protected Document $document ; public function __construct (Document $document ) { $this ->document = $document ; } public function submit ( ): void { throw new \Exception ('Document is already published' ); } public function approve ( ): void { throw new \Exception ('Document is already published' ); } public function reject ( ): void { throw new \Exception ('Cannot reject a published document' ); } public function publish ( ): void { throw new \Exception ('Document is already published' ); } public function archive ( ): void { $this ->document->update ([ 'status' => 'archived' , 'archived_at' => now (), ]); } public function getStatus ( ): string { return 'published' ; } }
用户账户状态 账户状态接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php namespace App \Contracts \States ;interface AccountStateInterface { public function activate ( ): void ; public function suspend ( ): void ; public function ban ( ): void ; public function close ( ): void ; public function canLogin ( ): bool ; public function canPerformAction (string $action ): bool ; public function getStatus ( ): string ; }
账户状态实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?php namespace App \States \Account ;use App \Models \User ;use App \Contracts \States \AccountStateInterface ;class ActiveState implements AccountStateInterface { protected User $user ; public function __construct (User $user ) { $this ->user = $user ; } public function activate ( ): void { } public function suspend ( ): void { $this ->user->update ([ 'status' => 'suspended' , 'suspended_at' => now (), ]); } public function ban ( ): void { $this ->user->update ([ 'status' => 'banned' , 'banned_at' => now (), ]); } public function close ( ): void { $this ->user->update ([ 'status' => 'closed' , 'closed_at' => now (), ]); } public function canLogin ( ): bool { return true ; } public function canPerformAction (string $action ): bool { return true ; } public function getStatus ( ): string { return 'active' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <?php namespace App \States \Account ;use App \Models \User ;use App \Contracts \States \AccountStateInterface ;class SuspendedState implements AccountStateInterface { protected User $user ; public function __construct (User $user ) { $this ->user = $user ; } public function activate ( ): void { $this ->user->update ([ 'status' => 'active' , 'suspended_at' => null , ]); } public function suspend ( ): void { } public function ban ( ): void { $this ->user->update ([ 'status' => 'banned' , 'banned_at' => now (), ]); } public function close ( ): void { $this ->user->update ([ 'status' => 'closed' , 'closed_at' => now (), ]); } public function canLogin ( ): bool { return false ; } public function canPerformAction (string $action ): bool { return false ; } public function getStatus ( ): string { return 'suspended' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?php namespace App \States \Account ;use App \Models \User ;use App \Contracts \States \AccountStateInterface ;class BannedState implements AccountStateInterface { protected User $user ; public function __construct (User $user ) { $this ->user = $user ; } public function activate ( ): void { throw new \Exception ('Cannot activate a banned account' ); } public function suspend ( ): void { throw new \Exception ('Cannot suspend a banned account' ); } public function ban ( ): void { } public function close ( ): void { $this ->user->update ([ 'status' => 'closed' , 'closed_at' => now (), ]); } public function canLogin ( ): bool { return false ; } public function canPerformAction (string $action ): bool { return false ; } public function getStatus ( ): string { return 'banned' ; } }
支付状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?php namespace App \States \Payment ;use App \Models \Payment ;use App \Contracts \States \PaymentStateInterface ;class PendingPaymentState implements PaymentStateInterface { protected Payment $payment ; public function __construct (Payment $payment ) { $this ->payment = $payment ; } public function complete ( ): void { $this ->payment->update ([ 'status' => 'completed' , 'completed_at' => now (), ]); } public function fail (string $reason = null ): void { $this ->payment->update ([ 'status' => 'failed' , 'failed_at' => now (), 'failure_reason' => $reason , ]); } public function cancel ( ): void { $this ->payment->update ([ 'status' => 'cancelled' , 'cancelled_at' => now (), ]); } public function refund ( ): void { throw new \Exception ('Cannot refund a pending payment' ); } public function getStatus ( ): string { return 'pending' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php namespace App \States \Payment ;use App \Models \Payment ;use App \Contracts \States \PaymentStateInterface ;class CompletedPaymentState implements PaymentStateInterface { protected Payment $payment ; public function __construct (Payment $payment ) { $this ->payment = $payment ; } public function complete ( ): void { throw new \Exception ('Payment is already completed' ); } public function fail (string $reason = null ): void { throw new \Exception ('Cannot fail a completed payment' ); } public function cancel ( ): void { throw new \Exception ('Cannot cancel a completed payment' ); } public function refund ( ): void { $this ->payment->update ([ 'status' => 'refunded' , 'refunded_at' => now (), ]); $this ->payment->processRefund (); } public function getStatus ( ): string { return 'completed' ; } }
测试状态模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php namespace Tests \Unit \States ;use Tests \TestCase ;use App \Models \Order ;use App \States \Order \{PendingState , ProcessingState };use App \Factories \OrderStateFactory ;class StateTest extends TestCase { public function test_order_pending_state ( ): void { $order = Order ::factory ()->create (['status' => 'pending' ]); $state = OrderStateFactory ::create ($order ); $this ->assertInstanceOf (PendingState ::class , $state ); $this ->assertEquals ('pending' , $state ->getStatusName ()); $this ->assertEquals (['processing' , 'cancelled' ], $state ->getAvailableTransitions ()); } public function test_order_state_transition ( ): void { $order = Order ::factory ()->create (['status' => 'pending' ]); $order ->process (); $this ->assertEquals ('processing' , $order ->fresh ()->status); $this ->assertInstanceOf (ProcessingState ::class , $order ->getState ()); } public function test_invalid_state_transition ( ): void { $order = Order ::factory ()->create (['status' => 'pending' ]); $this ->expectException (\InvalidArgumentException ::class ); $order ->ship (); } }
最佳实践 1. 状态类应该无状态 1 2 3 4 5 6 7 8 <?php class StatelessState implements StateInterface { public function handle ( ): void { } }
2. 使用枚举定义状态 1 2 3 4 5 6 7 8 9 10 <?php enum OrderStatus : string { case PENDING = 'pending' ; case PROCESSING = 'processing' ; case SHIPPED = 'shipped' ; case DELIVERED = 'delivered' ; case CANCELLED = 'cancelled' ; }
3. 状态转换验证 1 2 3 4 5 6 7 8 9 10 11 <?php trait ValidatesTransitions { protected array $allowedTransitions = []; public function canTransitionTo (string $state ): bool { return in_array ($state , $this ->allowedTransitions); } }
总结 Laravel 13 的状态模式提供了一种优雅的方式来管理对象的不同状态。通过合理使用状态模式,可以消除大量的条件判断,使代码更加清晰、可维护和可扩展。