lexik/workflow-bundle

此包已被废弃,不再维护。作者建议使用 symfony/workflow 包。

适用于 Symfony2 的简单工作流包

安装次数: 102,148

依赖者: 0

推荐者: 0

安全性: 0

星级: 127

关注者: 24

分支: 29

开放问题: 8

类型:symfony-bundle

v0.5.0 2015-08-03 09:41 UTC

This package is auto-updated.

Last update: 2022-02-01 12:25:04 UTC


README

Build Status Latest Stable Version SensioLabsInsight

此包已弃用

此 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 步骤上定义的默认验证之前执行一些验证。并且这些预验证仅在您尝试从 publishedunpublished 时执行。

预验证事件模式

  • 为了处理预验证:<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_nameservice_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的实例。该实体表示给定模型和过程的状态。