norotaro / enumata
为 Eloquent 模型使用 Enum 实现状态机
Requires
- php: ^8.2
- illuminate/support: ^10.0|^11.0
- javoscript/laravel-macroable-models: ^1.0
Requires (Dev)
- laravel/pint: ^1.16
- orchestra/testbench: ^7.0|^8.0|^9.1
- pestphp/pest: ^2.34
- phpstan/phpstan: ^1.11
README
状态机 Eloquent 模型使用 Enum。
目录
描述
此包通过使用枚举文件表示所有可能的状态以及配置转换,以简单的方式实现 Eloquent 模型的状态机。
实时演示
您可以查看 norotaro/enumata-demo 仓库,或访问此 PHP 沙盒 中的演示的实时版本。
安装
composer require norotaro/enumata
基本用法
具有 status
字段和 4 个可能状态的模式
$order->status; // 'pending', 'approved', 'declined' or 'processed'
我们需要创建一个包含状态定义的 enum
文件,我们将其称为 OrderStatus
。我们可以使用 make:model-state
命令来完成此操作
php artisan make:model-state OrderStatus
状态定义文件 - 枚举文件
上述命令将创建一个默认文件,我们可以根据需要对其进行修改
namespace App\Models; use Norotaro\Enumata\Contracts\Nullable; use Norotaro\Enumata\Contracts\DefineStates; enum OrderStatus implements DefineStates { case Pending; case Approved; case Declined; case Processed; public function transitions(): array { return match ($this) { // when the order is Pending we can approve() or decline() it self::Pending => [ 'approve' => self::Approved, 'decline' => self::Delined, ], // when the order is Approved we can apply the processOrder() transition self::Approved => [ 'processOrder' => self::Processed, ], }; } public static function default(): self { return self::Pending; } }
transitions()
方法必须返回一个 key=>value
的数组,其中 key 是转换的名称,value 是在该转换中应用的状态。
请注意,默认情况下,将为每个转换在模型中创建方法。在示例中,将创建
approve()
、decline()
和processOrder()
方法。
配置模型
在模型中,我们必须注册 HasStateMachines
特性和然后在 $casts
属性中注册 enum
文件
use Norotaro\Enumata\Traits\HasStateMachines; class Order extends Model { use HasStateMachines; protected $casts = [ 'status' => OrderStatus::class, ]; }
就是这样!现在我们可以在状态之间转换。
访问当前状态
如果您访问属性,Eloquent 将返回具有当前状态的 enum
对象
$model = new Order; $model->save(); $model->status; // App\Model\OrderStatus{name: "Pending"} $model->fulfillment; // null
转换
默认情况下,此包将为 transitions()
返回的每个转换在模型中创建方法,因此,对于此示例,我们将具有以下方法可用
$model->approve(); // Change status to OrderStatus::Approved $model->decline(); // Change status to OrderStatus::Declined $model->processOrder(); // Change status to OrderStatus::Processed
禁用默认转换方法
您可以通过将模型的 $defaultTransitionMethods
属性设置为 false
来禁用转换方法的创建。
内部方法使用 StateMachine
类中可用的 transitionTo($state)
方法,因此您可以使用它来实现自定义转换方法。
class Order extends Model { use HasStateMachines; // disable the creation of transition methods public bool $defaultTransitionMethods = false; protected $casts = [ 'status' => OrderStatus::class, ]; // custom transition method public function myApproveTransition(): void { $this->status()->transitionTo(OrderStatus::Approved); //... } }
转换不允许异常
如果应用了转换,并且当前状态不允许它,将抛出 TransitionNotAllowedException
。
$model->status; // App\Model\OrderStatus{name: "Pending"} $model->processOrder(); // throws Norotaro\Enumata\Exceptions\TransitionNotAllowedException
强制转换
所有由特性和 transitionTo()
方法创建的转换方法都有 force
参数,当为 true 时,转换将应用而不检查定义的规则。
$model->status; // App\Model\OrderStatus{name: "Pending"} $model->processOrder(force: true); // this will apply the transition and will not throw the exception $model->status; // App\Model\OrderStatus{name: "Processed"} $model->status()->transitionTo(OrderStatus::Pending, force:true); // will apply the transition without errors
可空状态
如果模型具有可空状态,我们只需在状态定义文件中实现 Norotaro\Enumata\Contracts\Nullable
合同即可。
例如,我们将向订单模型添加 fulfillment
属性
$order->fulfillment; // null, 'pending', 'completed'
创建状态定义文件
我们可以使用带有 make:model-state
命令和 --nullable
选项的 make:model-state
命令创建枚举文件
php artisan make:model-state OrderFulfillment --nullable
编辑生成的文件后,我们可以得到类似以下内容
namespace App\Models; use Norotaro\Enumata\Contracts\Nullable; use Norotaro\Enumata\Contracts\DefineStates; enum OrderFulfillment implements DefineStates, Nullable { case Pending; case Completed; public function transitions(): array { return match ($this) { self::Pending => [ 'completeFulfillment' => self::Completed, ], }; } public static function default(): ?self { return null; } public static function initialTransitions(): array { return [ 'initFulfillment' => self::Pending, ]; } }
initialTransitions()
方法必须返回字段为 null 时可用的转换列表。
与
transitions()
类似,默认情况下,方法将以initialTransitions()
返回的键的名称创建。
注册状态定义文件
正如我们之前在 status
定义中做的那样,我们需要在 $casts
属性中注册该文件。
use Norotaro\Enumata\Traits\HasStateMachines; class Order extends Model { use HasStateMachines; protected $casts = [ 'status' => OrderStatus::class, 'fulfillment' => OrderFulfillment::class, ]; }
状态机
要访问状态机,我们只需要在属性名称后添加括号。
$model->status(); // Norotaro\Enumata\StateMachine
如果属性使用下划线,例如
my_attribute
,您可以使用my_attribute()
或myAttribute()
访问状态机。
使用状态机
转换
我们可以使用 transitionTo($state)
方法在状态之间进行转换。
$model->status()->transitionTo(OrderStatus::Approved);
检查可用转换
$model->status; // App\Model\OrderStatus{name: "Pending"} $model->status()->canBe(OrderStatus::Approved); // true $model->status()->canBe(OrderStatus::Processed); // false
事件
此包为默认由 Eloquent 分发的那些事件添加了两个新事件,并且可以以相同的方式使用。
有关 Eloquent 事件的更多信息,请参阅官方文档。
transitioning:{attribute}
:在保存到新状态之前,将分发此事件。transitioned:{attribute}
:在保存到新状态之后,将分发此事件。
在 transitioning
事件中,您可以按以下方式访问原始状态和新状态。
$from = $order->getOriginal('fulfillment'); // App\Model\OrderFulfillment{name: "Pending"} $to = $order->fulfillment; // App\Model\OrderFulfillment{name: "Complete"}
使用 $dispatchesEvents
监听事件
use App\Events\TransitionedOrderFulfillment; use App\Events\TransitioningOrderStatus; use Norotaro\Enumata\Traits\HasStateMachines; class Order extends Model { use HasStateMachines; protected $casts = [ 'status' => OrderStatus::class, 'fulfillment' => OrderFulfillment::class, ]; protected $dispatchesEvents = [ 'transitioning:status' => TransitioningOrderStatus::class, 'transitioned:fulfillment' => TransitionedOrderFulfillment::class, ]; }
使用闭包监听事件
transitioning($field, $callback)
和 transitioned($field, $callback)
方法有助于注册闭包。
请注意,第一个参数必须是我们要监听的字段的名称。
use Norotaro\Enumata\Traits\HasStateMachines; class Order extends Model { use HasStateMachines; protected $casts = [ 'status' => OrderStatus::class, 'fulfillment' => OrderFulfillment::class, ]; protected static function booted(): void { static::transitioning('fulfillment', function (Order $order) { $from = $order->getOriginal('fulfillment'); $to = $order->fulfillment; \Log::debug('Transitioning fulfillment field', [ 'from' => $from->name, 'to' => $to->name, ]); }); static::transitioned('status', function (Order $order) { \Log::debug('Order status transitioned to ' . $order->status->name); }); } }
测试
要运行测试套件
composer run test
灵感
此包受到 asantibanez/laravel-eloquent-state-machines 的启发。
许可协议
MIT 许可证 (MIT)。有关更多信息,请参阅许可文件。