lexik / workflow-bundle
Requires
- php: >=5.3.3
- doctrine/orm: ~2.2
- symfony/framework-bundle: ~2.7
README
此包已弃用
此 Symfony2 包允许使用事件调度器来定义和管理简单的工怍流。
此包最初是基于 FreeAgentWorkflowBundle 分支的。实现上的区别在于我们使用事件调度器,并为每个模型对象存储步骤历史。
安装
使用 composer 安装
... "require": { ... "lexik/workflow-bundle": "dev-master", ... }, ...
接下来,请确保在您的 app/AppKernel.php
文件中启用此包
public function registerBundles() { return array( // ... new Lexik\Bundle\WorkflowBundle\LexikWorkflowBundle(), // ... ); }
工作原理
首先,什么是工怍流?根据维基百科的定义,“工怍流是一系列相连的步骤”。下面是此包使用的工怍流术语:
- 定义您的工怍流需要描述一些过程;
- 一个过程由一系列步骤组成,您将逐步通过这个过程;
- 一个步骤包含验证和动作,验证在尝试达到该步骤时执行,如果验证成功,则达到该步骤并执行动作。
该工怍流在“模型”对象上工作,模型是一个实现 Lexik\Bundle\WorkflowBundle\Model\ModelInterface
接口的类。每次模型尝试达到一个步骤时,我们将其记录在数据库中,以保留步骤历史。
工怍流示例
让我们定义一个围绕帖子从创建到发布的简单工怍流
- 首先,我们必须创建一个草稿,然后管理员必须验证此草稿,然后才能发布;
- 一旦帖子发布,任何用户都可以取消发布;
- 如果帖子未发布,管理员可以删除它;
- 如果发布步骤失败,我们将返回到草稿步骤。
# app/config/config.yml lexik_workflow: processes: post_publication: start: draft_created end: [ deleted ] steps: draft_created: label: "Draft created" roles: [ ROLE_USER ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_DRAFT ] next_states: validate: { type: step, target: validated_by_admin } # you can omit "type: step" as "step" is the default value of the "type" node. Soon, you'll be able to use "type: process". validated_by_admin: label: "Post validated" roles: [ ROLE_ADMIN ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_VALIDATED ] next_states: publish: { target: published } published: label: "Post published" roles: [ ROLE_USER ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_PUBLISHED ] on_invalid: draft_created # will try to reach the "draft_created" step in case validations to reach "published" fail. next_states: unpublish: { target: unpublished } unpublished: label: "Post unpublished" roles: [ ROLE_USER ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_UNPUBLISHED ] next_states: delete: { target: deleted } publish: { target: published } deleted: label: "Post deleted" roles: [ ROLE_ADMIN ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_DELETED ] next_states: ~
模型对象
该工怍流处理“模型”对象。一个“模型”对象基本上是 Lexik\Bundle\WorkflowBundle\Model\ModelInterface
接口的实例。此接口有 3 个方法需要实现
getWorkflowIdentifier()
返回用于在数据库中存储模型状态的唯一标识符;getWorkflowData()
返回一个要存储与模型状态一起的数据数组;getWorkflowObject()
返回最终对象,它将被默认的 ProcessHandler 通过安全上下文传递。
以下是一个我们可以用于 post_publication
过程的 PostModel
类示例:
<?php namespace Project\Bundle\SuperBundle\Workflow\Model; use Lexik\Bundle\WorkflowBundle\Model\ModelInterface; use Project\Bundle\SuperBundle\Entity\Post; /** * This class is used to wrap a Post entity. * It's not required to do it like that, we could also implement ModelInterface in the Post entity . */ class PostModel implements ModelInterface { private $post; public function __construct(Post $post) { $this->post = $post; } public function getPost() { return $this->post; } public function setStatus($status) { $this->post->setStatus($status); } public function getStatus() { return $this->post->getStatus(); } /** * Returns an unique identifier. * * @return mixed */ public function getWorkflowIdentifier() { return md5(get_class($this->post).'-'.$this->post->getId()); } /** * Returns data to store in the ModelState. * * @return array */ public function getWorkflowData() { return array( 'post_id' => $this->post->getId(), 'content' => $this->post->getContent(), // ... ); } /** * Returns the final object. * If your entity implements ModelInterface itself just return $this. * * @return object */ public function getWorkflowObject() { return $this->post; } }
步骤验证
正如您在套餐介绍中阅读的那样,我们使用事件调度器来执行操作和验证。为了验证是否可以到达某个步骤,您只需要监听 <process_name>.<step_name>.validate
事件。
监听器将接收一个 Lexik\Bundle\WorkflowBundle\Event\ValidateStepEvent
实例,这将允许您获取步骤、模型以及管理步骤违规的对象。您可以向其中添加违规项来阻止访问该步骤。
如果在验证错误的情况下无法到达该步骤,则将触发一个 <process_name>.<step_name>.validation_fail
事件。
让我们看一个简单的例子,这里我监听了来自 post_publication
流程的步骤 published
的 *.validate
和 *.validation_fail
事件。
<?php namespace Project\Bundle\SuperBundle\Workflow\Listener; use Lexik\Bundle\WorkflowBundle\Event\StepEvent; use Lexik\Bundle\WorkflowBundle\Event\ValidateStepEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class PostPublicationProcessSubscriber implements EventSubscriberInterface { /** * {@inheritDoc} */ public static function getSubscribedEvents() { return array( 'post_publication.published.validate' => array( 'handleAccessValidationPublished', ), 'post_publication.published.validation_fail' => array( 'handleValidationFail', ), ); } public function handleAccessValidationPublished(ValidateStepEvent $event) { if ( ! $event->getModel()->canBePublished()) { $event->addViolation('error message'); } } public function handleValidationFail(StepEvent $event) { // ... } }
<service id="project.workflow.listener.post_publication" class="Project\Bundle\SuperBundle\Workflow\Listener\PostPublicationProcessSubscriber"> <tag name="kernel.event_subscriber" /> </service>
步骤动作
如果您需要在成功到达步骤后执行一些逻辑,您可以监听 <process_name>.<step_name>.reached
事件。
监听器将接收一个 Lexik\Bundle\WorkflowBundle\Event\StepEvent
对象,您可以使用它来获取步骤、模型和最后一个模型状态。
让我们看一个简单的例子,这里我监听了来自 post_publication
流程的步骤 published
的 *.reached
事件。
<?php namespace Project\Bundle\SuperBundle\Workflow\Listener; use Lexik\Bundle\WorkflowBundle\Event\StepEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class PostPublicationProcessSubscriber implements EventSubscriberInterface { /** * {@inheritDoc} */ public static function getSubscribedEvents() { return array( 'post_publication.published.reached' => array( 'handleSuccessfullyPublished', ), ); } public function handleSuccessfullyPublished(StepEvent $event) { // ... } }
<service id="project.workflow.listener.post_publication" class="Project\Bundle\SuperBundle\Workflow\Listener\PostPublicationProcessSubscriber"> <tag name="kernel.event_subscriber" /> </service>
步骤预验证
除了步骤验证之外,您还可以处理一些预验证。预验证将在步骤验证之前执行,并且仅针对当前步骤。
例如:假设我们有一个帖子目前位于步骤 published
,我想到达步骤 unpublished
。
# app/config/config.yml lexik_workflow: processes: post_publication: start: draft_created end: [ deleted ] steps: # ... published: label: "Post published" roles: [ ROLE_USER ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_PUBLISHED ] on_invalid: draft_created # will try to reach the "draft_created" step in case validations to reach "published" fail. next_states: unpublish: { target: unpublished } unpublished: label: "Post unpublished" roles: [ ROLE_USER ] model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_UNPUBLISHED ] next_states: delete: { target: deleted } publish: { target: published } # ...
当您尝试到达 unpublished
步骤时,流程处理程序将触发一个名为
post_publication.published.unpublish.pre_validation
的事件。通过监听此事件,您可以在流程处理程序执行 unpublished
步骤上定义的默认验证之前执行一些验证。并且这些预验证仅在您尝试从 published
到 unpublished
时执行。
预验证事件模式
- 为了处理预验证:
<process_name>.<current_step_name>.<next_state_name>.pre_validation
。 - 为了处理预验证失败的情况:
<process_name>.<current_step_name>.<next_state_name>.pre_validation_fail
。
以下是一个预验证监听器的简单示例
namespace Project\Bundle\SuperBundle\Workflow\Listener; use Lexik\Bundle\WorkflowBundle\Event\StepEvent; use Lexik\Bundle\WorkflowBundle\Event\ValidateStepEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class PostPublicationProcessSubscriber implements EventSubscriberInterface { /** * {@inheritDoc} */ public static function getSubscribedEvents() { return array( 'post_publication.published.unpublish.pre_validation' => array( 'preValidate', ), 'post_publication.published.unpublish.pre_validation_fail' => array( 'preValidationFail', ), ); } public function preValidate(ValidateStepEvent $event) { // do your checks } public function preValidationFail(StepEvent $event) { // process code in case the pre validation fail } }
条件下一步(或)
为了定义一个条件性下个状态,您必须像往常一样定义 target
键,以及 type
键来通知流程处理程序该下个状态是条件性的。
以下是一个条件性下个状态的示例
lexik_workflow: processes: my_process: steps: my_step_xxx: label: "Step xxx" next_states: go_to_next_step: type: step_or target: my_step_A: "service_id:method_name" my_step_B: "service_id:other_method_name" my_step_C: ~ # default choice
假设我们有一个当前位于步骤 my_step_xxx
的模型状态。如果我们尝试通过调用 $processHandler->reachNextState($model, 'go_to_next_step')
来到达名为 go_to_next_step
的下个状态,工作流将调用为每个目标定义的每个方法。第一个返回 true 的方法将使工作流继续到相关的步骤。
因此,如果 service_id:method_name
返回 true
,则下个步骤将是 my_step_A
。如果 service_id:method_name
返回 false
并且 service_id:other_method_name
返回 true
,则下个步骤将是 my_step_B
。如果 service_id:method_name
和 service_id:other_method_name
都返回 false
,则下个步骤将是 my_step_C
。
模型状态更新
您可以使用 model_status
选项轻松地为您的模型分配状态。第一个参数是在步骤到达时将在模型上调用的方法。第二个参数是将传递给此方法的值。这允许您在流程的每个步骤自动更新状态。
steps: published: ... model_status: [ setStatus, Project\Bundle\SuperBundle\Entity\Post::STATUS_PUBLISHED ] ...
步骤用户角色
您可以定义当前用户必须拥有的角色才能到达一个步骤。角色在步骤验证之前进行检查。
steps: published: ... roles: [ ROLE_ADMIN ] ...
当用户没有相应角色时,会触发事件 *.bad_credentials
。
在您的ModelInterface上设置modelStates
如果您想检索为您的ModelInterface对象创建的所有modelStates,您需要实现ModelStateInterface。
<?php class FakeModel implements ModelInterface, ModelStateInterface { //... protected $states = array(); public function addState(ModelState $modelState) { $this->states[] = $modelState; } public function getStates() { return $this->states; }
这将允许您调用在lexik_workflow.model_storage
服务中定义的方法getStates($objects, $processes = array(), $onlySuccess = false)
。
<?php // get your object // Set states on your object $this->get('lexik_workflow.model_storage')->setStates($post); // Get all your objects and set your process and only state successful $this->get('lexik_workflow.model_storage')->setStates($posts, ['process'], true);
用法
以下是一个简单示例,说明如何使用工作流。
<?php // create a model object (see the PostModel class defined previously in the Model object section) $model = new PostModel($myPost); // get the process handler $processHandler = $container->get('lexik_workflow.handler.post_publication'); // start the process $modelState = $processHandler->start($model); // $model->getStatus() === Project\Bundle\SuperBundle\Entity\Post::STATUS_DRAFT // reach the next state $modelState = $processHandler->reachNextState($model, 'validate'); // here 'validate' is the key defined in the draft_created next states. // $model->getStatus() === Project\Bundle\SuperBundle\Entity\Post::STATUS_VALIDATED if ( ! $modelState->getSuccessful() ) { var_dump($modelState->getErrors()); }
请注意,start()
和reachNextState()
方法返回Lexik\Bundle\WorkflowBundle\Entity\ModelState
的实例。该实体表示给定模型和过程的状态。