如何使用工作流

3.4 版本
维护中的版本

工作流组件提供了一个面向对象的方式,来定义你的对象所经历的进程或生命周期。进程中的每一步,或每一个阶段,被称之为一个 place(位置)。你还必须定义 transitions(过渡)来描述从一个位置到另一个位置时的action(动作)。

一组位置及其过渡,创建了一个 definition(定义)。一个工作流,需要的是一个 Definition 以及把状态(state)写入对象实例 (如,一个 MarkingStoreInterface 实例) 的方式。

思考以下博客主题的例程。每篇主题可以有一个预定义状态(predefined status,比如草稿、审稿、拒发、已发)的编号。你可以像这样定义这个工作流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
framework:
    workflows:
        blog_publishing:
            type: 'workflow' # or 'state_machine'
            marking_store:
                type: 'multiple_state' # or 'single_state'
                arguments:
                    - 'currentPlace'
            supports:
                - AppBundle\Entity\BlogPost
            places:
                - draft
                - review
                - rejected
                - published
            transitions:
                to_review:
                    from: draft
                    to:   review
                publish:
                    from: review
                    to:   published
                reject:
                    from: review
                    to:   rejected
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="utf-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
>
 
    <framework:config>
        <framework:workflow name="blog_publishing" type="workflow">
            <framework:marking-store type="single_state">
              <framework:arguments>currentPlace</framework:arguments>
            </framework:marking-store>
 
            <framework:support>AppBundle\Entity\BlogPost</framework:support>
 
            <framework:place>draft</framework:place>
            <framework:place>review</framework:place>
            <framework:place>rejected</framework:place>
            <framework:place>published</framework:place>
 
            <framework:transition name="to_review">
                <framework:from>draft</framework:from>
 
                <framework:to>review</framework:to>
            </framework:transition>
 
            <framework:transition name="publish">
                <framework:from>review</framework:from>
 
                <framework:to>published</framework:to>
            </framework:transition>
 
            <framework:transition name="reject">
                <framework:from>review</framework:from>
 
                <framework:to>rejected</framework:to>
            </framework:transition>
 
        </framework:workflow>
 
    </framework:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// app/config/config.php
 
        $container->loadFromExtension('framework', array(
            // ...
            'workflows' => array(
                'blog_publishing' => array(
                  'type' => 'workflow', // or 'state_machine'
                  'marking_store' => array(
                    'type' => 'multiple_state', // or 'single_state'
                    'arguments' => array('currentPlace')
                  ),
                  'supports' => array('AppBundle\Entity\BlogPost'),
                  'places' => array(
                    'draft',
                    'review',
                    'rejected',
                    'published',
                  ),
                  'transitions' => array(
                    'to_review'=> array(
                      'form' => 'draft',
                      'to' => 'review',
                    ),
                    'publish'=> array(
                      'form' => 'review',
                      'to' => 'published',
                    ),
                    'reject'=> array(
                      'form' => 'review',
                      'to' => 'rejected',
                    ),
                  ),
                ),
            ),
        ));
1
2
3
4
5
6
7
8
class BlogPost
{
    // This property is used by the marking store
    // 此属性被marking stroe所用
    public $currentPlace;
    public $title;
    public $content
}

marking store的类型可以是 "multiple_state" 或 "single_state"。一个单一的state marking store不支持模型在同一时间出现在多个place(位置)。

使用这个名为 blog_publishing 的工作流,你可以得到 “决定对博客主题进行何种操作” 的帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$post = new \AppBundle\Entity\BlogPost();
 
$workflow = $this->container->get('workflow.blog_publishing');
$workflow->can($post, 'publish'); // False
$workflow->can($post, 'to_review'); // True
 
// Update the currentState on the post
// 更新主题的当前状态
try {
    $workflow->apply($post, 'to_review');
} catch (LogicException $e) {
    // ...
}
 
// See all the available transition for the post in the current state
// 查看主题在当前状态下全部可用的过渡
$transitions = $workflow->getEnabledTransitions($post);

使用事件 

要让你的工作流更加强大,你应该在构建 Workflow 对象时使用一个 EventDispatcher。你现在可以创建监听来阻止过渡(如,根据博客主题中的数据)。以下事件会被派遣:

  • workflow.guard
  • workflow.[workflow name].guard
  • workflow.[workflow name].guard.[transition name]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
class BlogPostReviewListener implements EventSubscriberInterface
{
    public function guardReview(GuardEvent $event)
    {
        /** @var Acme\BlogPost $post */
        $post = $event->getSubject();
        $title = $post->title;
 
        if (empty($title)) {
            // Posts with no title should not be allowed
            $event->setBlocked(true);
        }
    }
 
    public static function getSubscribedEvents()
    {
        return array(
            'workflow.blogpost.guard.to_review' => array('guardReview'),
        );
    }
}

由于有 EventDispatcher and the AuditTrailListener 的帮助,你可以很容易地开启日志:

1
2
3
4
5
use Symfony\Component\Workflow\EventListener\AuditTrailListener;
 
$logger = new AnyPsr3Logger();
$subscriber = new AuditTrailListener($logger);
$dispatcher->addSubscriber($subscriber);

Twig中的用法 

在Twig模板中使用工作流可以减少视图层中的域逻辑(domain logic)。考虑以下“博客控制台”的例程。下面的链接只在action被允许时才会显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h3>Actions</h3>
{% if workflow_can(post, 'publish') %}
    <a href="...">Publish article</a>
{% endif %}
{% if workflow_can(post, 'to_review') %}
    <a href="...">Submit to review</a>
{% endif %}
{% if workflow_can(post, 'reject') %}
    <a href="...">Reject article</a>
{% endif %}
 
{# Or loop through the enabled transistions #}
{# 或者遍历已开启的过渡 #}
{% for transition in workflow_transitions(post) %}
    <a href="...">{{ transition.name }}</a>
{% else %}
    No actions available.
{% endfor %}

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)