codewiser / workflow
模型工作流Laravel包
Requires
- php: ^7.4|^8.0
- ext-json: *
- laravel/framework: >=8.0
Requires (Dev)
- phpunit/phpunit: ^9.0
- dev-master
- v4.5.3
- v4.5.2
- v4.5.1
- v4.5.0
- v4.4.5
- v4.4.4
- v4.4.3
- v4.4.2
- v4.4.1
- v4.4.0
- v4.3.2
- v4.3.1
- v4.3.0
- v4.2.10
- v4.2.9
- v4.2.8
- v4.2.7
- v4.2.6
- v4.2.5
- v4.2.4
- v4.2.3
- v4.2.2
- v4.2.1
- v4.2.0
- v4.1.4
- v4.1.3
- v4.1.2
- v4.1.1
- v4.1.0
- v4.0.1
- v4.0.0
- v3.1.1
- v3.1.0
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.0.16
- 2.0.15
- 2.0.14
- 2.0.13
- 2.0.12
- 2.0.11
- 2.0.10
- 2.0.9
- 2.0.8
- 2.0.7
- 2.0.6
- 2.0.5
- 1.5.8
- 1.5.4
- 1.5.3
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.8
- 1.3.7
- 1.3.6
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.0
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.1
- 1.1.0
- 1.0.2
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2024-09-11 09:42:12 UTC
README
包为Eloquent模型提供工作流功能。
工作流是一系列状态,文档通过这些状态演变。状态之间的转换影响演变路径。
设置
首先,用可用的状态和转换描述工作流程蓝图
class ArticleWorkflow extends \Codewiser\Workflow\WorkflowBlueprint { public function states(): array { return [ 'new', 'review', 'published', 'correction', ]; } public function transitions(): array { return [ ['new', 'review'], ['review', 'published'], ['review', 'correction'], ['correction', 'review'] ]; } }
您可以使用
Enum
而不是标量值。
class ArticleWorkflow extends \Codewiser\Workflow\WorkflowBlueprint { public function states(): array { return Enum::cases(); } public function transitions(): array { return [ [Enum::new, Enum::review], [Enum::review, Enum::published], [Enum::review, Enum::correction], [Enum::correction, Enum::review] ]; } }
接下来,包括特性和创建方法以将蓝图绑定到模型的属性。
use \Codewiser\Workflow\Example\Enum; use \Codewiser\Workflow\Example\ArticleWorkflow; use \Codewiser\Workflow\StateMachineEngine; class Article extends Model { use \Codewiser\Workflow\Traits\HasWorkflow; public function state(): StateMachineEngine { return $this->workflow(ArticleWorkflow::class, 'state'); } }
就是这样。
一致性
工作流观察模型并保持状态机的一致性。
use \Codewiser\Workflow\Example\Enum; // creating: will set proper initial state $article = new \Codewiser\Workflow\Example\Article(); $article->save(); assert($article->state == Enum::new); // updating: will examine state machine consistency $article->state = Enum::review; $article->save(); // No exceptions thrown assert($article->state == Enum::review);
状态和转换对象
在上面的例子中,我们用标量值描述了蓝图,但实际上它们将被转换成对象。这些对象为状态和转换提供了额外的功能,例如标题翻译、过渡授权、路由规则、前转换和后转换回调等...
use \Codewiser\Workflow\State; use \Codewiser\Workflow\Transition; class ArticleWorkflow extends \Codewiser\Workflow\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
来定义转换授权规则。
使用Policy
提供能力名称。包将检查给定的能力是否与相关模型关联。
use \Codewiser\Workflow\Transition; Transition::make('new', 'review') ->authorizedBy('transit'); class ArticlePolicy { public function transit(User $user, Article $article, Transition $transition) { // } }
使用闭包
授权闭包可以返回true
或false
,或者抛出AuthorizationException
。
use \Codewiser\Workflow\Transition; use \Illuminate\Support\Facades\Gate; Transition::make('new', 'review') ->authorizedBy(fn(Article $article, Transition $transition) => Gate::authorize('transit', [$article, $transition]));
授权转换
要获取当前用户有权限的转换,请使用TransitionCollection
的authorized
方法
$article = new \Codewiser\Workflow\Example\Article(); $transitions = $article->state() // Get transitions from model's current state. ->transitions() // Filter only authorized transitions. ->onlyAuthorized();
授权转换
在处理用户请求时,不要忘记授权工作流状态更改。
public function update(Request $request, 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(); }
可收费转换
只有当累积了一些费用时,可收费的转换才会触发。例如,我们可能只想在至少三位编辑接受后发布文章。
use \Codewiser\Workflow\Charge; use \Codewiser\Workflow\Transition; Transition::make('review', 'publish') ->chargeable(Charge::make( progress: function(Article $article) { return $article->accepts / 3; }, callback: function (Article $article) { $article->accepts++; $article->save(); } ));
Charge
类有更多选项,可以提供投票统计或防止重复投票。
业务逻辑
禁用转换
转换可能对模型有一些先决条件。如果模型符合这些条件,则转换是可能的。
先决条件是一个具有Model
参数的callable
。它可以抛出异常。
要临时禁用转换,先决条件应该抛出TransitionRecoverableException
。在异常消息中留下帮助说明。
以下是一些用户可能解决的问题的示例。
use \Codewiser\Workflow\Transition; use \Codewiser\Workflow\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 \Codewiser\Workflow\Transition; use \Codewiser\Workflow\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 \Codewiser\Workflow\Transition; Transition::make('review', 'reject') ->rules([ 'reason' => 'required|string|min:100' ]);
接下来,在控制器中设置上下文。
当创建模型时
use Illuminate\Http\Request; public function store(Request $request) { $this->authorize('create', \Codewiser\Workflow\Example\Article::class); $article = \Codewiser\Workflow\Example\Article::query()->make( $request->all() ); $article->state() // Init workflow, passing additional context ->init($request->all()) // Now save model ->save(); }
当转换模型时
use Illuminate\Http\Request; public function update(Request $request, \Codewiser\Workflow\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
对象。然后,使用枚举实现 \Codewiser\Workflow\Contracts\StateEnum
到 enum
。
没有标题的 Transition
将继承其目标 State
的标题。
use \Codewiser\Workflow\State; use \Codewiser\Workflow\Transition; use \Codewiser\Workflow\WorkflowBlueprint; class ArticleWorkflow extends WorkflowBlueprint { protected function states(): array { return [ State::make('new')->as(__('Draft')), State::make('published')->as(fn(Article $model) => __('Published')) ]; } protected function transitions(): array { return [ Transition::make('new', 'published')->as(__('Publish')) ]; } }
附加属性
有时我们需要向工作流程状态和转换添加一些附加属性。例如,我们可以按级别对状态进行分组,并使用此信息在用户界面中着色状态和转换。然后,使用枚举实现 \Codewiser\Workflow\Contracts\StateEnum
到 enum
。
Transition
继承其目标 State
的属性。
use \Codewiser\Workflow\State; use \Codewiser\Workflow\Transition; use \Codewiser\Workflow\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(\Codewiser\Workflow\Example\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
和可选 Transition
参数的 callable
。
use \Codewiser\Workflow\Context; use \Codewiser\Workflow\State; use \Codewiser\Workflow\Transition; State::make('correcting') ->rules(['reason' => 'required|string|min:100']) ->after(function(Article $article, Context $context) { $article->author->notify( new ArticleHasProblemNotification( $article, $context->data()->get('reason') ) ); });
转换回调
您可以定义转换回调,在转换成功执行后调用。
这与状态回调完全相同。
use \Codewiser\Workflow\Context; use \Codewiser\Workflow\Transition; Transition::make('review', 'correcting') ->rules(['reason' => 'required|string|min:100']) ->after(function(Article $article, Context $context) { $article->author->notify( new ArticleHasProblemNotification( $article, $context->data()->get('reason') ) ); });
您可以为单个转换定义多个回调。
事件监听器
转换生成 ModelTransited
事件。您可以为它定义 EventListener
。
use \Codewiser\Workflow\Events\ModelTransited; class ModelTransitedListener { public function handle(ModelTransited $event) { if ($event->model instanceof Article) { $article = $event->model; if ($event->context->target()->is('correction')) { // Article was send to correction, the reason described in context $article->author->notify( new ArticleHasProblemNotification( $article, $event->context->data()->get('reason') ) ); } } } }
转换历史记录
该包可以将转换记录到数据库表中。
在 config/app.php
的 providers
部分中注册 \Codewiser\Workflow\WorkflowServiceProvider
。
将 workflow.history
添加到 config/services.php
'workflow' => [ 'history' => true ]
发布并运行迁移
php artisan vendor:publish --tag=workflow-migrations
php artisan migrate
完成了。
要获取历史记录,将 \Codewiser\Workflow\Traits\HasTransitionHistory
添加到具有工作流程的 Model
中。它带来了 transitions
关系。
历史记录由 \Codewiser\Workflow\Models\TransitionHistory
模型表示,该模型包含有关转换执行者、源和目标状态以及上下文(如果提供)的信息。
蓝图验证
该包可以验证您定义的工作流程蓝图。
在 config/app.php
的 providers
部分中注册 \Codewiser\Workflow\WorkflowServiceProvider
。
使用蓝图类名运行控制台命令
php artisan workflow:blueprint --class=App/Workflow/ArticleWorkflow