asantibanez / laravel-eloquent-state-machines
为您的 Laravel Eloquent 模型提供状态机
Requires
- php: ^7.3|^7.4|^8.0
- illuminate/support: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
- javoscript/laravel-macroable-models: ^1.0
Requires (Dev)
- laravel/legacy-factories: ^1.0.4
- orchestra/testbench: ^4.0|^5.0|^6.0|^7.0|^8.0
- phpunit/phpunit: ^8.0|^9.0
- dev-master
- v6.0.0
- v5.2.0
- v5.1.0
- v5.0.1
- v5.0.0
- v4.0.0
- v3.0.0
- v2.3.0
- v2.2.1
- v2.2.0
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.0
- v1.1.0
- v1.0.1
- v1.0.0
- dev-laravel-11-compatibility
- dev-feature/support-any-transitions
- dev-feature/state-machine-custom-method-name
- dev-feature/after-transition-hook-key-change
- dev-features/add-before-transition-hooks
- dev-features/query-builder-methods
- dev-features/pending-transitions
- dev-features/transition-responsible
- dev-issues/check-same-state-transition
- dev-issues/check-record-history-on-creating-model
This package is auto-updated.
Last update: 2024-09-22 21:32:07 UTC
README
简介
此包允许您通过在特定的状态机类中定义转换逻辑来简化 Eloquent 模型的状态转换。每个类都允许您注册验证、钩子和允许的转换和状态,使每个状态机类成为从一种状态转换到另一种状态的唯一真相来源。
Laravel Eloquent 状态机还允许您自动记录模型可能具有的所有状态的历史,并根据需要进行查询。
在核心上,此包旨在提供简单但功能强大的 API,让 Laravel 开发者感到宾至如归。
示例
具有两个状态字段的模型
$salesOrder->status; // 'pending', 'approved', 'declined' or 'processed' $salesOrder->fulfillment; // null, 'pending', 'completed'
从一个状态转换到另一个状态
$salesOrder->status()->transitionTo('approved'); $salesOrder->fulfillment()->transitionTo('completed'); //With custom properties $salesOrder->status()->transitionTo('approved', [ 'comments' => 'Customer has available credit', ]); //With responsible $salesOrder->status()->transitionTo('approved', [], $responsible); // auth()->user() by default
检查可用的转换
$salesOrder->status()->canBe('approved'); $salesOrder->status()->canBe('declined');
检查当前状态
$salesOrder->status()->is('approved'); $salesOrder->status()->responsible(); // User|null
检查转换历史
$salesOrder->status()->was('approved'); $salesOrder->status()->timesWas('approved'); $salesOrder->status()->whenWas('approved'); $salesOrder->fulfillment()->snapshotWhen('completed'); $salesOrder->status()->history()->get();
演示
您可以在此处查看演示和示例 这里
安装
您可以通过 composer 安装此包
composer require asantibanez/laravel-eloquent-state-machines
接下来,您必须导出包迁移
php artisan vendor:publish --provider="Asantibanez\LaravelEloquentStateMachines\LaravelEloquentStateMachinesServiceProvider" --tag="migrations"
最后,准备所需的数据库表
php artisan migrate
用法
定义我们的状态机
假设我们有一个 SalesOrder
模型,该模型有一个 status
字段,用于跟踪销售订单在系统中的不同阶段:REGISTERED
、APPROVED
、PROCESSED
或 DECLINED
。
我们可以在状态机类中集中管理和处理所有这些阶段和转换。要定义一个,我们可以使用 php artisan make:state-machine
命令。
例如,我们可以为我们的 SalesOrder 模型创建一个 StatusStateMachine
php artisan make:state-machine StatusStateMachine
运行命令后,我们将在 App\StateMachines
目录中创建一个新的状态机类。该类将包含以下代码。
use Asantibanez\LaravelEloquentStateMachines\StateMachines\StateMachine; class StatusStateMachine extends StateMachine { public function recordHistory(): bool { return false; } public function transitions(): array { return [ // ]; } public function defaultState(): ?string { return null; } }
在这个类中,我们可以定义我们的状态和允许的转换
public function transitions(): array { return [ 'pending' => ['approved', 'declined'], 'approved' => ['processed'], ]; }
允许使用通配符以允许任何状态更改
public function transitions(): array { return [ '*' => ['approved', 'declined'], // From any to 'approved' or 'declined' 'approved' => '*', // From 'approved' to any '*' => '*', // From any to any ]; }
我们还可以定义默认/起始状态
public function defaultState(): ?string { return 'pending'; // it can be null too }
状态机类允许自动为您记录每个转换。要启用此行为,我们必须将 recordHistory()
设置为返回 true
;
public function recordHistory(): bool { return true; }
注册我们的状态机
一旦我们定义了状态机,我们就可以将其注册到我们的 SalesOrder
模型中的 $stateMachine
属性中。在这里,我们设置绑定模型 field
和控制它的状态机类。
use Asantibanez\LaravelEloquentStateMachines\Traits\HasStateMachines; use App\StateMachines\StatusStateMachine; class SalesOrder extends Model { Use HasStateMachines; public $stateMachines = [ 'status' => StatusStateMachine::class ]; }
状态机方法
当在我们的模型中注册 $stateMachines
时,每个状态字段都将有自己的自定义方法来与状态机和转换方法交互。HasStateMachines
特性为 $stateMachines
中映射的每个字段定义了一个方法。例如:
对于
'status' => StatusStateMachine::class, 'fulfillment_status' => FulfillmentStatusStateMachine::class
我们将有一个相应的
status(); fulfillment_status(); // or fulfillmentStatus()
我们可以使用它来检查我们的当前状态、历史记录并应用转换。
注意:字段 "status" 将保持完整并与状态机同步
转换状态
要从一个状态转换到另一个状态,我们可以使用 transitionTo
方法。例如:
$salesOrder->status()->transitionTo($to = 'approved');
如果需要,也可以传递 $customProperties
$salesOrder->status()->transitionTo($to = 'approved', $customProperties = [ 'comments' => 'All ready to go' ]);
还可以指定 $responsible
。默认情况下,将使用 auth()->user()
$salesOrder->status()->transitionTo( $to = 'approved', $customProperties = [], $responsible = User::first() );
在应用转换时,状态机将验证状态转换是否允许,根据我们定义的 transitions()
状态。如果转换不允许,将抛出 TransitionNotAllowed
异常。
查询历史记录
如果在我们的状态机中将 recordHistory()
设置为 true
,则每个状态转换都将记录在 StateHistory
模型中,使用在安装包时导出的 state_histories
表。
启用 recordHistory()
后,我们可以查询我们的字段已转换到的状态历史。例如
$salesOrder->status()->was('approved'); // true or false $salesOrder->status()->timesWas('approved'); // int $salesOrder->status()->whenWas('approved'); // ?Carbon
如上图所示,我们可以检查我们的字段是否已转换到所查询的状态之一。
我们还可以获取给定状态的最新快照或所有快照
$salesOrder->status()->snapshotWhen('approved'); $salesOrder->status()->snapshotsWhen('approved');
转换状态的完整历史记录也是可用的
$salesOrder->status()->history()->get();
history()
方法返回一个 Eloquent 关系,可以与以下作用域一起链式调用以进一步筛选结果。
$salesOrder->status()->history() ->from('pending') ->to('approved') ->withCustomProperty('comments', 'like', '%good%') ->get();
使用查询构建器
HasStateMachines
特性在根据每个状态机的状态历史查询模型时引入了一个辅助方法。您可以使用 whereHas{FIELD_NAME}
(例如:whereHasStatus
、whereHasFulfillment
)根据状态转换、责任和自定义属性添加约束到模型查询。
whereHas{FIELD_NAME}
方法接受一个闭包,您可以在其中添加以下类型的约束
withTransition($from, $to)
transitionedFrom($to)
transitionedTo($to)
withResponsible($responsible|$id)
withCustomProperty($property, $operator, $value)
$from
和 $to
参数可以是字符串或状态名称的数组。
SalesOrder::with() ->whereHasStatus(function ($query) { $query ->withTransition('pending', 'approved') ->withResponsible(auth()->id()) ; }) ->whereHasFulfillment(function ($query) { $query ->transitionedTo('complete') ; }) ->get();
获取自定义属性
在应用带有自定义属性的转换时,我们可以使用 getCustomProperty($key)
方法获取我们的注册值。例如。
$salesOrder->status()->getCustomProperty('comments');
此方法将获取当前状态的自定义属性。您可以使用 snapshotWhen($state)
方法获取先前状态的自定义属性。
$salesOrder->status()->snapshotWhen('approved')->getCustomProperty('comments');
获取责任
与自定义属性类似,您可以检索应用状态转换的 $responsible
对象。
$salesOrder->status()->responsible();
此方法将获取当前状态的责任。您可以使用 snapshotWhen($state)
方法获取先前状态的责任。
$salesOrder->status()->snapshotWhen('approved')->responsible;
注意:
responsible
可以是null
,如果没有指定,并且在后台作业中发生转换时。这是因为没有可用的auth()->user()
。
高级用法
跟踪属性更改
当 recordHistory()
激活时,模型状态转换将记录在 state_histories
表中。每个转换记录都包含有关在状态转换期间更改的属性的信息。您可以通过 changedAttributesNames()
方法获取有关更改的信息。此方法将返回一个已更改的属性名称数组。使用这些属性名称,然后您可以使用 changedAttributeOldValue($attributeName)
和 changedAttributeNewValue($attributeName)
方法分别获取旧值和新值。
$salesOrder = SalesOrder::create([ 'total' => 100, ]); $salesOrder->total = 200; $salesOrder->status()->transitionTo('approved'); $salesOrder->changedAttributesNames(); // ['total'] $salesOrder->changedAttributeOldValue('total'); // 100 $salesOrder->changedAttributeNewValue('total'); // 200
添加验证
在转换到新状态之前,我们可以添加验证,允许/禁止转换。为此,我们可以在状态机类中重写 validatorForTransition($from, $to, $model)
方法。
此方法必须返回一个 Validator
,它将用于在应用转换之前检查转换。如果验证器 fails()
,则抛出 ValidationException
。例如。
use Illuminate\Support\Facades\Validator as ValidatorFacade; class StatusStateMachine extends StateMachine { public function validatorForTransition($from, $to, $model): ?Validator { if ($from === 'pending' && $to === 'approved') { return ValidatorFacade::make([ 'total' => $model->total, ], [ 'total' => 'gt:0', ]); } return parent::validatorForTransition($from, $to, $model); } }
在上面的示例中,我们在应用转换之前验证我们的销售订单模型总计是否大于 0。
添加钩子
我们还可以添加在应用转换之前/之后执行的自定义钩子/回调。为此,我们必须相应地重写状态机中的 beforeTransitionHooks()
和 afterTransitionHooks()
方法。
这两个转换钩子方法都必须返回一个键值数组,其中键是状态,值是要执行的回调/闭包数组。
注意:beforeTransitionHooks() 的键必须是
$from
状态。注意:afterTransitionHooks() 的键必须是$to
状态。
示例
class StatusStateMachine extends StateMachine { public function beforeTransitionHooks(): array { return [ 'approved' => [ function ($to, $model) { // Dispatch some job BEFORE "approved changes to $to" }, function ($to, $model) { // Send mail BEFORE "approved changes to $to" }, ], ]; } public function afterTransitionHooks(): array { return [ 'processed' => [ function ($from, $model) { // Dispatch some job AFTER "$from transitioned to processed" }, function ($from, $model) { // Send mail AFTER "$from transitioned to processed" }, ], ]; } }
延迟转换
您也可以使用 postponeTransitionTo
方法将转换延迟到其他状态。此方法接受与 transitionTo
相同的参数,以及一个 $when
Carbon 实例来指定何时运行转换。
postponeTransitionTo
不会立即应用转换。相反,它将其保存到 pending_transitions
表中,其中跟踪所有模型的待处理转换。
要启用稍后运行这些转换,您必须将 PendingTransitionsDispatcher
作业类安排到您的调度器中,每分钟、五分钟或十分钟运行一次。
$schedule->job(PendingTransitionsDispatcher::class)->everyMinute();
PendingTransitionsDispatcher
负责在指定的 $when
日期/时间应用延迟的转换。
您可以使用 hasPendingTransitions()
方法检查模型是否具有特定状态机的待处理转换。
$salesOrder->status()->hasPendingTransitions();
测试
composer test
变更日志
请参阅 CHANGELOG 了解最近更改的更多信息。
贡献
请参阅 CONTRIBUTING 了解详细信息。
安全
如果您发现任何安全相关的问题,请通过电子邮件 santibanez.andres@gmail.com 联系我们,而不是使用问题跟踪器。
鸣谢
许可证
MIT 许可证 (MIT)。请参阅 许可证文件 了解更多信息。