workup / state-machine
Winzou State Machine 服务提供商,适用于 Laravel
Requires
- php: ^8.1
- illuminate/support: ^8.0 | ^9.0
- winzou/state-machine: ^0.4.2
Requires (Dev)
- mockery/mockery: ^1.3.1
- orchestra/testbench: ^6.0 | ^7.0
- phpunit/phpunit: ^9.3
- symfony/process: ^5.0 | ^6.0
This package is auto-updated.
Last update: 2024-09-03 17:44:02 UTC
README
这是一个为 winzou/state-machine 提供的 Laravel 服务提供者。它提供了对 StateMachineFactory
的依赖注入。您还可以使用 Laravel 的服务容器来解析回调类的类方法。还提供了一个门面以方便使用。
安装
您可以通过 composer 安装此包。
然后使用命令行界面要求安装此包
composer require workup/state-machine
版本
如果您需要在较旧的 Laravel 安装中安装此包,请使用下表找到兼容的版本。
自 5.5 版本以来,Laravel 使用包自动发现,因此您不需要手动添加 ServiceProvider 和门面。如果您不使用自动发现或您正在使用较旧版本,请在 config/app.php 中添加服务提供者和门面。
<?php 'providers' => [ Workup\StateMachine\ServiceProvider::class, ], 'aliases' => [ 'StateMachine' => Workup\StateMachine\Facade::class, ],
配置
有两种方式可以配置状态机
通用配置文件
在 config/state-machine.php
中发布配置文件(如果您在模型中使用了提供的 trait,则不需要此步骤)。
php artisan vendor:publish --provider="Workup\StateMachine\ServiceProvider"
模型 trait
在您的模型中添加 HasStateMachines
trait 并实现 $stateMachines
数组来自动设置其状态机。
protected static array $stateMachines = [ 'state' => 'state-machine-config', // passing a string will load the corresponding config 'another-state' => [ // inline declaration 'states' => ['test'], 'transitions' => [], ], ];
选项
有关所有可用选项的文档,请参阅 StateMachineBundle 的文档。
使用时
当使用您模型中的提供的 trait 时,您不需要传递 graph
、property_name
和 class
参数。您还可以传递 initial_state
参数(默认情况下,初始状态将是状态数组中找到的第一个)。
<?php // Get the article $article = App\Article::find($id); // Get the state machine for this article, and graph called "simple" // Using the facade $stateMachine = StateMachine::get($article, 'simple'); // Or using the service container with dependency injection public function method(SM\Factory\FactoryInterface $factory) { $stateMachine = $factory->get($article, 'simple'); }
现在您可以使用 $stateMachine
来与 $article
的状态进行交互。
<?php // Get the actual state of the object $stateMachine->getState(); // Get all available transitions $stateMachine->getPossibleTransitions(); // Check if a transition can be applied: returns true or false $stateMachine->can('approve'); // Apply a transition: returns true or throws an SM\SMException $stateMachine->apply('publish'); // Apply a transition without throwing an exception: returns true or false $stateMachine->apply('publish', true);
回调
回调用于保护转换或在实际应用转换之前或之后执行某些代码。此包增加了使用 Laravel 的服务容器来解决回调的能力。
例如。
您想要调用 MyService
类的 handle
方法以确定状态机是否可以应用 submit_changes
转换。
<?php 'callbacks' => [ // will be called when testing a transition 'guard' => [ 'guard_on_submitting' => [ // call the callback on a specific transition 'on' => 'submit_changes', // will call the method of this class 'do' => ['MyService', 'handle'], // arguments for the callback 'args' => ['object'], ], ], ],
您可以使用数组格式或 @ 分隔的字符串格式指定回调,例如 ['Class', 'method']
或 Class@method
。
使用权限和策略
如果您不想使用自定义类和方法进行保护,则可以使用 Laravel 的授权权限或策略来决定是否可以应用转换。
您必须定义一个 can
键,而不是指定 do
键,以检查您想要检查的能力的名称。自 Laravel 5.5 以来,您还可以指定一组能力,并将逐一进行检查。
默认情况下,对象实例将被传递给权限作为参数。您还可以通过指定 args
键来覆盖这些参数。
使用权限的示例
在这个示例中,我们定义了一个将接受 $article
作为参数的权限。如果您不需要此参数,则不需要在您的权限中定义此参数。
<?php use App\User; use App\Article; Gate::define('approve', function (User $user, Article $article) { // });
<?php 'callbacks' => [ 'guard' => [ 'guard_on_approving' => [ // call the gate on a specific transition 'on' => 'approve', // will call Gate:allows('approve', $article) 'can' => 'approve', ], ], ],
使用策略的示例
假设您为您的 Article
模型创建了一个 ArticlePolicy
策略,其中包含一个 approve
方法。
您应该在 can
指数中定义 approve
。这将相当于调用 $user->can('approve', $article)
。
您还可以通过指定 args
数组来覆盖将传递给 approved
方法的参数。为了使策略类能够解析,您必须将 object
作为第一个参数。例如:'args' => ['object', '"final_approval"']
将相当于调用 $user->can('approve', [$article, 'final_approval'])
。
<?php namespace App\Policies; use App\User; use App\Article; class ArticlePolicy { public function approve(User $user, Article $article) { // } }
<?php 'callbacks' => [ 'guard' => [ 'guard_on_approving' => [ // call the policy on a specific transition 'on' => 'approve', // will call Gate:allows('approve', $article) 'can' => 'approve', ], ], ],
事件
在检查是否可以应用转换时,会触发 SM\Event\SMEvents::TEST_TRANSITION
事件。
在应用转换之前和之后,分别会触发 SM\Event\SMEvents::PRE_TRANSITION
和 SM\Event\SMEvents::POST_TRANSITION
事件。
所有事件都会接收到一个 Workup\SM\Event\TransitionEvent
实例。
如果您想使用相同的监听器监听所有事件,可以使用 winzou.state_machine.*
通配符参数。
您可以在应用程序的 EventServiceProvider
中定义自己的监听器。例如:
<?php use SM\Event\SMEvents; /** * The event listener mappings for the application. * * @var array */ protected $listen = [ SMEvents::TEST_TRANSITION => [ \App\Listeners\CheckTransition::class, ], SMEvents::PRE_TRANSITION => [ \App\Listeners\BeforeTransition::class, ], SMEvents::POST_TRANSITION => [ \App\Listeners\AfterTransition::class, ], 'winzou.state_machine.*' => [ \App\Listeners\Transition::class, ], ];
上下文
在检查或应用转换时,您还可以通过数组传递额外的数据。此数组将被传递到 Workup\SM\Event\TransitionEvent
。您可以在事件监听器或回调中使用 $event->getContext()
访问数组。
使用事件监听器的示例
<?php // Reject the transition of the approval date is past Event::listen(SMEvents::TEST_TRANSITION, function (TransitionEvent $event) { $context = $event->getContext(); if ($context['approved_at']->isPast()) { $event->setRejected(); } }); // Check if a approve transition can be applied on some date $stateMachine->can('approve', ['approved_at' => now()]);
使用回调的示例
<?php // Setup an callback after publishing [ 'callbacks' => [ 'after' => [ 'after_publishing' => [ 'on' => 'publish', 'do' => [App\Actions\PublishArticleAction::class, 'execute'], 'args' => ['object', 'event'], ], ], ], ]; // Save the publish date in your action class PublishArticleAction { public function execute(Article $article, TransitionEvent $event) { $context = $event->getContext(); $article->update(['published_at' => $context['published_at']]); } } // Apply a publish transition on some date $stateMachine->apply('publish', false, ['published_at' => now()]);
元数据
您可以选择在图、状态和转换中存储元数据。元数据存储在关联数组中,可以是任何您想要的内容。
<?php return [ 'graphA' => [ 'class' => App\Article::class, 'metadata' => [ 'title' => 'Article State Machine', ], 'states' => [ [ 'name' => 'pending_review', 'metadata' => ['title' => 'Pending Review'], ], ], 'transitions' => [ 'ask_for_changes' => [ 'from' => ['pending_review'], 'to' => 'accepted', 'metadata' => ['title' => 'Ask for changes'], ], ], ], ];
状态机对象提供了许多灵活的方法来获取元数据,无论是作为关联数组,还是通过键获取特定值。如果没有指定的键,您还可以传递默认值或闭包。
从图中获取元数据
<?php $stateMachine = StateMachine::get($article); // ['title' => 'Article State Machine'] $stateMachine->metadata('graph'); $stateMachine->metadata()->graph(); // 'Article State Machine' $stateMachine->metadata('title'); // 'Article State Machine' $stateMachine->metadata('graph', 'title'); $stateMachine->metadata()->graph('title'); // null $stateMachine->metadata('graph', 'invalid'); $stateMachine->metadata()->graph('invalid'); // 'default' $stateMachine->metadata('graph', 'invalid', 'default'); $stateMachine->metadata()->graph('invalid', 'default');
从状态中获取元数据
$stateMachine = StateMachine::get($article); // ['title' => 'Pending Review'] $stateMachine->metadata('state', 'pending_review'); $stateMachine->metadata()->state('pending_review'); // 'Pending Review' $stateMachine->metadata('state', 'pending_review', 'title'); $stateMachine->metadata()->state('pending_review', 'title'); // null $stateMachine->metadata('state', 'pending_review', 'invalid'); $stateMachine->metadata()->state('pending_review', 'invalid'); // 'default' $stateMachine->metadata('state', 'pending_review', 'invalid', 'default'); $stateMachine->metadata()->state('pending_review', 'invalid', 'default');
从当前状态中获取元数据
<?php $article->state = 'pending_review'; $stateMachine = StateMachine::get($article); // ['title' => 'Pending Review'] $stateMachine->metadata('state'); // 'Pending Review' $stateMachine->metadata('state', 'title'); // null $stateMachine->metadata('state', 'invalid'); // 'default' $stateMachine->metadata('state', 'invalid', 'default');
从转换中获取元数据
<?php $stateMachine = StateMachine::get($article); // ['title' => 'Ask for changes'] $stateMachine->metadata('transition', 'ask_for_changes'); $stateMachine->metadata()->transition('ask_for_changes'); // 'Ask for changes' $stateMachine->metadata('transition', 'ask_for_changes', 'title'); $stateMachine->metadata()->transition('ask_for_changes', 'title'); // null $stateMachine->metadata('transition', 'ask_for_changes', 'invalid'); $stateMachine->metadata()->transition('ask_for_changes', 'invalid'); // 'default' $stateMachine->metadata('transition', 'ask_for_changes', 'invalid', 'default'); $stateMachine->metadata()->transition('ask_for_changes', 'invalid', 'default');
调试命令
包含了一个用于调试图的 artisan 命令。它接受图的名称作为参数。如果没有传递参数,将会交互式地询问图名。
$ php artisan winzou:state-machine:debug simple
+--------------------+-----------------------+
| Configured States: | Metadata: |
+--------------------+-----------------------+
| new | |
| pending_review | title: Pending Review |
| awaiting_changes | |
| accepted | |
| published | |
| rejected | |
+--------------------+-----------------------+
+-----------------+------------------+------------------+
| Transition | From(s) | To |
+-----------------+------------------+------------------+
| create | new | pending_review |
+-----------------+------------------+------------------+
| ask_for_changes | pending_review | awaiting_changes |
| | accepted | |
+-----------------+------------------+------------------+
| cancel_changes | awaiting_changes | pending_review |
+-----------------+------------------+------------------+
| submit_changes | awaiting_changes | pending_review |
+-----------------+------------------+------------------+
| approve | pending_review | accepted |
| | rejected | |
+-----------------+------------------+------------------+
| publish | accepted | published |
+-----------------+------------------+------------------+
+---------------------+--------------------+------------------------+--------+
| Guard Callbacks | Satisfies | Do | Args |
+---------------------+--------------------+------------------------+--------+
| guard_on_submitting | On: submit_changes | MyClass::handle() | object |
| guard_on_approving | On: approve | Gate::check("approve") | |
+---------------------+--------------------+------------------------+--------+
+---------------------+-------------+-------------+-----------------------------+
| Before Callbacks | Satisfies | Do | Args |
+---------------------+-------------+-------------+-----------------------------+
| log_before_approval | On: approve | Log::info() | "approving article", object |
+---------------------+-------------+-------------+-----------------------------+
+----------------------+--------------+------------------------------+---------------+
| After Callbacks | Satisfies | Do | Args |
+----------------------+--------------+------------------------------+---------------+
| email_after_approval | To: accepted | SendApprovalMail::dispatch() | object, event |
+----------------------+--------------+------------------------------+---------------+
可视化命令
包含了一个用于生成给定图图像的 artisan 命令。它接受图的名称作为参数。它是从相应的 Symfony 包 https://github.com/MadMind/StateMachineVisualizationBundle 中获得的,所以所有的荣誉都归原作者。
如果您想运行此命令,您需要安装 dot - 图形viz 包的一部分(https://graphviz.cn/)。在您的 mac 上,这相当于运行了 brew install graphviz
。
php artisan winzou:state-machine:visualize {graph? : A state machine graph} {--output=./graph.jpg} {--format=jpg} {--direction=TB} {--shape=circle} {--dot-path=/usr/local/bin/dot}
Eloquent 模型的可状态特性
如果您想在模型中直接与状态机交互,可以通过 laravel-statable 包来实现,该包由 iben12 提供。
此包允许您从模型中获取图,检查/应用转换,以及在数据库中记录状态历史。
变更日志
有关最近更改的更多信息,请参阅 CHANGELOG。
测试
$ composer test
贡献
有关详细信息,请参阅 CONTRIBUTING。
安全性
如果您发现任何安全相关的问题,请通过电子邮件 info@sebdesign.eu 而不是使用问题跟踪器。
鸣谢
许可
MIT 许可证 (MIT)。有关更多信息,请参阅 许可文件。