soap / eloquent-workflow
Laravel Eloquent 简单状态和转换工作流
Requires
- php: ^8.0|^8.1
- illuminate/contracts: ^8.0|^9.0
- spatie/laravel-package-tools: ^1.6.2
Requires (Dev)
- nunomaduro/collision: ^5.0|^6.0
- nunomaduro/larastan: ^1.0|^2.0
- orchestra/testbench: ^7.0
- pestphp/pest: ^1.21
- pestphp/pest-plugin-laravel: ^1.1
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^9.5
- spatie/laravel-ray: ^1.26
This package is auto-updated.
Last update: 2024-09-23 03:23:07 UTC
README
此包为 eloquent 模型添加状态转换工作流。用户可以选择从可用的转换中进行操作,使 eloquent 模型从一个状态转换到另一个状态。它基于 codewiser/workflow 的大量工作 codewiser/workflow
支持我们
安装
您可以通过 composer 安装此包
composer require soap/eloquent-workflow
您可以使用以下命令发布并运行迁移
php artisan vendor:publish --tag="eloquent-workflow-migrations"
php artisan migrate
您可以使用以下命令发布配置文件
php artisan vendor:publish --tag="eloquent-workflow-config"
这是已发布配置文件的内容
return [
];
可选地,您可以使用以下命令发布视图
php artisan vendor:publish --tag="eloquent-workflow-views"
用法
包为 Eloquent 模型提供工作流功能。
工作流是一系列状态,文档通过这些状态演变。状态之间的转换施加演变路径。
设置
首先,用可用的状态和转换描述工作流程蓝图
class ArticleWorkflow extends \Soap\EloquentWorkflow\WorkflowBlueprint { public function states(): array { return [ 'new', 'review', 'published', 'correction', ]; } public function transitions(): array { return [ ['new', 'review'], ['review', 'published'], ['review', 'correction'], ['correction', 'review'] ]; } }
您可以使用
Enum
代替标量值
接下来,包含特性并创建方法以将蓝图绑定到模型的属性。
use \Soap\EloquentWorkflow\Example\Enum; use \Soap\EloquentWorkflow\Example\ArticleWorkflow; use \Soap\EloquentWorkflow\StateMachineEngine; class Article extends Model { use \Soap\EloquentWorkflow\Traits\HasWorkflow; public function state(): StateMachineEngine { return $this->workflow(ArticleWorkflow::class, 'state'); } }
就这样。
一致性
工作流观察模型并保持状态机一致性。
use \Soap\EloquentWorkflow\Example\Enum; // creating: will set proper initial state $article = new \Soap\EloquentWorkflow\Example\Article(); $article->save(); assert($article->state == 'new'); // updating: will examine state machine consistency $article->state = 'review'; $article->save(); // No exceptions thrown assert($article->state == 'review');
状态和转换对象
在上面的示例中,我们使用标量值描述了蓝图,但实际上它们将被转换为对象。这些对象为状态和转换带来了额外的功能,例如标题翻译、转换授权、路由规则等...
use \Soap\EloquentWorkflow\State; use \Soap\EloquentWorkflow\Transition; class ArticleWorkflow extends \Soap\EloquentWorkflow\WorkflowBlueprint { public function states(): array { return [ State::make('new'), State::make('review'), State::make('published'), State::make('correction'), ]; } public function transitions(): array { return [ Transition::make('new', 'review'), Transition::make('review', 'published'), Transition::make('review', 'correction'), Transition::make('correction', 'review'), ]; } }
授权
由于模型的操作不允许任何用户进行,因为不允许任何用户更改状态,因此您可能需要使用 Policy
或使用 callable
定义转换授权规则。
使用策略
提供能力名称
use \Soap\EloquentWorkflow\Transition; Transition::make('new', 'review') ->authorizedBy('transit');
使用闭包
use \Soap\EloquentWorkflow\Transition; use \Illuminate\Support\Facades\Gate; Transition::make('new', 'review') ->authorizedBy(fn(Article $article) => Gate::allows('transit', $article));
授权转换
要获取当前用户授权的唯一转换,请使用 TransitionCollection
的 authorized
方法
$article = new \Soap\EloquentWorkflow\Example\Article(); $transitions = $article->state() // Get transitions from model's current state. ->transitions() // Filter only authorized transitions. ->onlyAuthorized();
授权转换
在处理用户请求时,请记住授权工作流程状态更改。
public function update(Request $request, \Soap\EloquentWorkflow\Example\Article $article) { $this->authorize('update', $article); if ($state = $request->input('state')) { // Check if user allowed to make this transition $article->state()->authorize($state); } $article->fill($request->validated()); $article->save(); }
业务逻辑
禁用转换
转换可能有一些先决条件。如果模型符合这些条件,则转换是可能的。
先决条件是一个带有 Model
参数的 callable
。它可能抛出异常。
要禁用转换,先决条件应抛出 TransitionRecoverableException
。在异常消息中留下帮助说明。
以下是一个用户可能解决的问题的示例。
use \Soap\EloquentWorkflow\Transition; use \Soap\EloquentWorkflow\Exceptions\TransitionRecoverableException; Transition::make('new', 'review') ->before(function(Article $model) { if (strlen($model->body) < 1000) { throw new TransitionRecoverableException( 'Your article should contain at least 1000 symbols. Then you may send it to review.' ); } }) ->before(function(Article $model) { if ($model->images->count() == 0) { throw new TransitionRecoverableException( 'Your article should contain at least 1 image. Then you may send it to review.' ); } });
用户将在可用转换列表中看到有问题的转换。用户按照说明解决问题后,可以再次尝试执行转换。
删除转换
在某些情况下,工作流程路由可能分为分支。路线由业务逻辑强制执行,而不是由用户。用户甚至不应该知道其他路线。
要完全从列表中删除转换,先决条件应抛出 TransitionFatalException
。
use \Soap\EloquentWorkflow\Transition; use \Soap\EloquentWorkflow\Exceptions\TransitionFatalException; Transition::make('new', 'to-local-manager') ->before(function(Order $model) { if ($model->amount >= 1000000) { throw new TransitionFatalException("Order amount is too big for this transition."); } }); Transition::make('new', 'to-region-manager') ->before(function(Order $model) { if ($model->amount < 1000000) { throw new TransitionFatalException("Order amount is too small for this transition."); } });
用户将根据订单数量值看到唯一可能的转换。
附加上下文
有时应用程序需要额外的上下文来执行转换。例如,这可能是因为文章被审稿人拒绝的原因。
首先,在转换定义中声明验证规则
use \Soap\EloquentWorkflow\Transition; Transition::make('review', 'reject') ->rules([ 'reason' => 'required|string|min:100' ]);
接下来,在控制器中设置转换上下文
use Illuminate\Http\Request; public function update(Request $request, \Soap\EloquentWorkflow\Example\Article $article) { $this->authorize('update', $article); if ($state = $request->input('state')) { $article->state() // Authorize transition ->authorize($state) // Transit to the new state, passing additional context ->transit($state, $request->all()) // Now save model ->save(); } }
在保存时将验证上下文,您可能会捕获一个 ValidationException
。
最终,您可以在 事件 中处理这个上下文。
翻译
您可以定义带有可翻译标题的 State
和 Transition
对象。
use \Soap\EloquentWorkflow\State; use \Soap\EloquentWorkflow\Transition; use \Soap\EloquentWorkflow\WorkflowBlueprint; class ArticleWorkflow extends WorkflowBlueprint { protected function states(): array { return [ State::make('new')->as(__('Draft')), State::make('published')->as(__('Published')) ]; } protected function transitions(): array { return [ Transition::make('new', 'published')->as(__('Publish')) ]; } }
附加属性
有时我们需要向工作流状态和转换添加一些附加属性。例如,我们可以按级别分组状态,并使用这些信息在用户界面中为状态和转换着色。
use \Soap\EloquentWorkflow\State; use \Soap\EloquentWorkflow\Transition; use \Soap\EloquentWorkflow\WorkflowBlueprint; class ArticleWorkflow extends WorkflowBlueprint { protected function states(): array { return [ State::make('new'), State::make('review') ->set('level', 'warning'), State::make('published') ->set('level', 'success'), State::make('correction') ->set('level', 'danger') ]; } protected function transitions(): array { return [ Transition::make('new', 'review') ->set('level', 'warning'), Transition::make('review', 'published') ->set('level', 'success'), Transition::make('review', 'correction') ->set('level', 'danger'), Transition::make('correction', 'review') ->set('level', 'warning') ]; } }
JSON 序列化
为了使用户能够与模型的工作流交互,我们应该将数据传递给应用程序的前端
use Illuminate\Http\Request; public function state(\App\Models\Article $article) { return $article->state()->toArray(); }
有效载荷将如下所示
{ "value": "review", "name": "Review", "transitions": [ { "source": "review", "target": "publish", "name": "Publish", "issues": [ "Publisher should provide a foreword." ], "level": "success" }, { "source": "review", "target": "correction", "name": "Send to Correction", "rules": { "reason": ["required", "string", "min:100"] }, "level": "danger" } ] }
事件
状态回调
您可以为达到状态时定义状态回调。
回调是一个具有 Model
和 context
参数的 callable
。
use \Soap\EloquentWorkflow\State; State::make('correcting') ->after(function(Article $article, ?State $previous, array $context) { $article->author->notify( new ArticleHasProblemNotification( $article, $context['reason'] ) ); });
转换回调
您可以为成功执行转换后定义转换回调。
回调是一个具有 Model
和 context
参数的 callable
。
use \Soap\EloquentWorkflow\State; use \Soap\EloquentWorkflow\Transition; Transition::make('review', 'correcting') ->rules([ 'reason' => 'required|string|min:100' ]) ->after(function(Article $article, ?State $previous, array $context) { $article->author->notify( new ArticleHasProblemNotification( $article, $context['reason'] ) ); });
您可以为单个转换定义多个回调。
事件监听器
转换生成 ModelTransited
事件。您可以为它定义 EventListener
。
use \Soap\EloquentWorkflow\Events\ModelTransited; class ModelTransitedListener { public function handle(ModelTransited $event) { if ($event->model instanceof Article) { $article = $event->model; if ($event->transition->target()->is('correction')) { // Article was send to correction, the reason described in context $article->author->notify( new ArticleHasProblemNotification( $article, $event->transition->context('reason') ) ); } } } }
转换日志
包可以将转换记录到数据库表中。
在 config/app.php
的 providers
部分中注册 \Soap\EloquentWorkflow\EloquentWorkflowServiceProvider
。
运行迁移
php artisan migrate
将 workflow.logging
编辑到 config/eloquent-workflow.php
'workflow' => [ 'logging' => true ]
完成了。
要获取日志记录,请将 \Soap\EloquentWorkflow\Traits\HasTransitionLog
添加到具有工作流的 Model
。它带来了 transitions
关系。
历史记录由 \Soap\EloquentWorkflow\Models\TransitionLog
模型表示,该模型包含有关转换执行者、源状态和目标状态以及上下文(如果提供)的信息。
蓝图验证
包可以验证您定义的工作流蓝图。
在 config/app.php
的 providers
部分中注册 \Soap\EloquentWorkflow\WorkflowServiceProvider
。
使用蓝图类名运行控制台命令
php artisan workflow:blueprint --class=App/Workflow/ArticleWorkflow
测试
composer test
变更日志
有关最近更改的更多信息,请参阅 变更日志。
贡献
有关详细信息,请参阅 贡献指南。
安全漏洞
有关如何报告安全漏洞的详细信息,请参阅 我们的安全策略。
鸣谢
许可证
MIT 许可证(MIT)。有关更多信息,请参阅 许可证文件。