跳到内容

工作流

编辑此页

在 Symfony 应用程序中使用 Workflow 组件首先需要了解关于工作流和状态机的一些基本理论和概念。 阅读这篇文章 以获得快速概览。

安装

在使用 Symfony Flex 的应用程序中,运行此命令以在开始使用工作流功能之前安装它

1
$ composer require symfony/workflow

配置

要查看所有配置选项,如果您在 Symfony 项目中使用该组件,请运行此命令

1
$ php bin/console config:dump-reference framework workflows

创建一个工作流

工作流是你的对象经历的过程或生命周期。过程中的每个步骤或阶段都称为 *place*(状态)。您还需要定义 *transitions*(转换),用于描述从一个状态到另一个状态所需的操作。

An example state diagram for a workflow, showing transitions and places.

一组状态和转换创建了一个 **definition**(定义)。一个工作流需要一个 Definition(定义)和一个将状态写入对象的方法(即 MarkingStoreInterface 的实例)。

考虑以下博客文章的示例。一篇文章可以有以下状态:draft(草稿)、reviewed(已审核)、rejected(已拒绝)、published(已发布)。您可以如下定义工作流

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
# config/packages/workflow.yaml
framework:
    workflows:
        blog_publishing:
            type: 'workflow' # or 'state_machine'
            audit_trail:
                enabled: true
            marking_store:
                type: 'method'
                property: 'currentPlace'
            supports:
                - App\Entity\BlogPost
            initial_marking: draft
            places:          # defining places manually is optional
                - draft
                - reviewed
                - rejected
                - published
            transitions:
                to_review:
                    from: draft
                    to:   reviewed
                publish:
                    from: reviewed
                    to:   published
                reject:
                    from: reviewed
                    to:   rejected

提示

如果您正在创建您的第一个工作流,请考虑使用 workflow:dump 命令来 调试工作流内容

提示

您可以在 YAML 文件中使用 PHP 常量,通过 !php/const 符号。例如,您可以使用 !php/const App\Entity\BlogPost::STATE_DRAFT 代替 'draft',或者使用 !php/const App\Entity\BlogPost::TRANSITION_TO_REVIEW 代替 'to_review'

提示

如果您的转换定义了工作流中使用的所有状态,则可以省略 places 选项。Symfony 将自动从转换中提取状态。

7.1

省略 places 选项的支持在 Symfony 7.1 中引入。

配置的属性将通过标记存储实现的 getter/setter 方法来使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Entity/BlogPost.php
namespace App\Entity;

class BlogPost
{
    // the configured marking store property must be declared
    private string $currentPlace;
    private string $title;
    private string $content;

    // getter/setter methods must exist for property access by the marking store
    public function getCurrentPlace(): string
    {
        return $this->currentPlace;
    }

    public function setCurrentPlace(string $currentPlace, array $context = []): void
    {
        $this->currentPlace = $currentPlace;
    }

    // you don't need to set the initial marking in the constructor or any other method;
    // this is configured in the workflow with the 'initial_marking' option
}

也可以为标记存储使用公共属性。上面的类将变成以下这样

1
2
3
4
5
6
7
8
9
10
// src/Entity/BlogPost.php
namespace App\Entity;

class BlogPost
{
    // the configured marking store property must be declared
    public string $currentPlace;
    public string $title;
    public string $content;
}

当使用公共属性时,不支持上下文。为了支持上下文,您必须声明一个 setter 来写入您的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Entity/BlogPost.php
namespace App\Entity;

class BlogPost
{
    public string $currentPlace;
    // ...

    public function setCurrentPlace(string $currentPlace, array $context = []): void
    {
        // assign the property and do something with the context
    }
}

注意

标记存储类型可以是 "multiple_state"(多状态)或 "single_state"(单状态)。单状态标记存储不支持一个模型同时处于多个状态。这意味着 "workflow"(工作流)必须使用 "multiple_state"(多状态)标记存储,而 "state_machine"(状态机)必须使用 "single_state"(单状态)标记存储。Symfony 默认根据 "type"(类型)配置标记存储,因此最好不要配置它。

单状态标记存储使用 string(字符串)来存储数据。多状态标记存储使用 array(数组)来存储数据。如果未定义状态标记存储,则在这两种情况下都必须返回 null(例如,上面的示例应定义一个像 App\Entity\BlogPost::getCurrentPlace(): ?arrayApp\Entity\BlogPost::getCurrentPlace(): ?string 这样的返回类型)。

提示

marking_store 选项的 marking_store.type(默认值取决于 type 值)和 property(默认值 ['marking'])属性是可选的。如果省略,将使用它们的默认值。强烈建议使用默认值。

提示

audit_trail.enabled 选项设置为 true 会使应用程序为工作流活动生成详细的日志消息。

通过这个名为 blog_publishing 的工作流,您可以获得帮助来决定对博客文章允许执行哪些操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use App\Entity\BlogPost;
use Symfony\Component\Workflow\Exception\LogicException;

$post = new BlogPost();
// you don't need to set the initial marking with code; this is configured
// in the workflow with the 'initial_marking' option

$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 $exception) {
    // ...
}

// See all the available transitions for the post in the current state
$transitions = $workflow->getEnabledTransitions($post);
// See a specific available transition for the post in the current state
$transition = $workflow->getEnabledTransition($post, 'publish');

使用多状态标记存储

如果您正在创建一个 workflow(工作流),您的标记存储可能需要同时包含多个状态。这就是为什么,如果您正在使用 Doctrine,匹配的列定义应使用 json 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Entity/BlogPost.php
namespace App\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class BlogPost
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private int $id;

    #[ORM\Column(type: Types::JSON)]
    private array $currentPlaces;

    // ...
}

警告

您不应为您的标记存储使用 simple_array 类型。在多状态标记存储中,状态存储为键,值为 1,例如 ['draft' => 1]。如果标记存储仅包含一个状态,则此 Doctrine 类型将仅将其值存储为字符串,从而导致对象当前状态的丢失。

在类中访问工作流

您可以通过使用服务自动装配并在参数名称中使用驼峰式的工作流名称 + Workflow,在类中使用工作流。如果是状态机类型,则使用驼峰式的工作流名称 + StateMachine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use App\Entity\BlogPost;
use Symfony\Component\Workflow\WorkflowInterface;

class MyClass
{
    public function __construct(
        // Symfony will inject the 'blog_publishing' workflow configured before
        private WorkflowInterface $blogPublishingWorkflow,
    ) {
    }

    public function toReview(BlogPost $post): void
    {
        // Update the currentState on the post
        try {
            $this->blogPublishingWorkflow->apply($post, 'to_review');
        } catch (LogicException $exception) {
            // ...
        }
        // ...
    }
}

要获取工作流的已启用转换,您可以使用 getEnabledTransition() 方法。

7.1

getEnabledTransition() 方法在 Symfony 7.1 中引入。

工作流也可以通过它们的名称和 Target 属性进行注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use App\Entity\BlogPost;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\Workflow\WorkflowInterface;

class MyClass
{
    public function __construct(
        #[Target('blog_publishing')]
        private WorkflowInterface $workflow
    ) {
    }

    // ...
}

这允许您将参数名称与任何实现名称解耦。

提示

如果您想检索所有工作流,例如为了文档目的,您可以使用以下标签注入所有服务

  • workflow: 所有工作流和所有状态机;
  • workflow.workflow: 所有工作流;
  • workflow.state_machine: 所有状态机。

请注意,工作流元数据附加到标签的 metadata 键下,为您提供关于工作流的更多上下文和信息。了解更多关于 标签属性存储工作流元数据 的信息。

7.1

附加到标签的配置在 Symfony 7.1 中引入。

提示

您可以使用 php bin/console debug:autowiring workflow 命令找到可用工作流服务的列表。

使用事件

为了使您的工作流更灵活,您可以使用 EventDispatcher 构建 Workflow 对象。现在,您可以创建事件监听器来阻塞转换(即,取决于博客文章中的数据)并在工作流操作发生时执行其他操作(例如,发送通知)。

每个步骤都有三个按顺序触发的事件

  • 每个工作流的事件;
  • 相关工作流的事件;
  • 相关工作流的事件,包含特定的转换或状态名称。

当状态转换被启动时,事件按以下顺序派发

workflow.guard

验证转换是否被阻塞(参见 守卫事件阻塞转换)。

正在派发的三个事件是

  • workflow.guard
  • workflow.[工作流名称].guard
  • workflow.[工作流名称].guard.[转换名称]
workflow.leave

主题即将离开一个状态。

正在派发的三个事件是

  • workflow.leave
  • workflow.[工作流名称].leave
  • workflow.[工作流名称].leave.[状态名称]
workflow.transition

主题正在经历此转换。

正在派发的三个事件是

  • workflow.transition
  • workflow.[工作流名称].transition
  • workflow.[工作流名称].transition.[转换名称]
workflow.enter

主题即将进入一个新状态。此事件在主题状态更新之前触发,这意味着主题的标记尚未更新为新状态。

正在派发的三个事件是

  • workflow.enter
  • workflow.[工作流名称].enter
  • workflow.[工作流名称].enter.[状态名称]
workflow.entered

主题已进入状态,并且标记已更新。

正在派发的三个事件是

  • workflow.entered
  • workflow.[工作流名称].entered
  • workflow.[工作流名称].entered.[状态名称]
workflow.completed

对象已完成此转换。

正在派发的三个事件是

  • workflow.completed
  • workflow.[工作流名称].completed
  • workflow.[工作流名称].completed.[转换名称]
workflow.announce

为每个现在可供主题访问的转换触发。

正在派发的三个事件是

  • workflow.announce
  • workflow.[工作流名称].announce
  • workflow.[工作流名称].announce.[转换名称]

应用转换后,announce 事件会测试所有可用的转换。这将再次触发所有守卫事件,如果它们包含密集的 CPU 或数据库工作负载,则可能会影响性能。

如果您不需要 announce 事件,请使用上下文禁用它

1
$workflow->apply($subject, $transitionName, [Workflow::DISABLE_ANNOUNCE_EVENT => true]);

注意

即使对于停留在同一状态的转换,也会触发 leaving 和 entering 事件。

注意

如果您通过调用 $workflow->getMarking($object); 初始化标记,则 workflow.[工作流名称].entered.[初始状态名称] 事件将使用默认上下文 (Workflow::DEFAULT_INITIAL_CONTEXT) 调用。

这是一个如何为每次 "blog_publishing" 工作流离开一个状态启用日志记录的示例

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
// src/App/EventSubscriber/WorkflowLoggerSubscriber.php
namespace App\EventSubscriber;

use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\Event;
use Symfony\Component\Workflow\Event\LeaveEvent;

class WorkflowLoggerSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }

    public function onLeave(Event $event): void
    {
        $this->logger->alert(sprintf(
            'Blog post (id: "%s") performed transition "%s" from "%s" to "%s"',
            $event->getSubject()->getId(),
            $event->getTransition()->getName(),
            implode(', ', array_keys($event->getMarking()->getPlaces())),
            implode(', ', $event->getTransition()->getTos())
        ));
    }

    public static function getSubscribedEvents(): array
    {
        return [
            LeaveEvent::getName('blog_publishing') => 'onLeave',
            // if you prefer, you can write the event name manually like this:
            // 'workflow.blog_publishing.leave' => 'onLeave',
        ];
    }
}

提示

所有内置的工作流事件都定义了 getName(?string $workflowName, ?string $transitionOrPlaceName) 方法,用于构建完整的事件名称,而无需处理字符串。您也可以在自定义事件中通过 EventNameTrait 使用此方法。

7.1

getName() 方法在 Symfony 7.1 中引入。

如果某些监听器在转换期间更新了上下文,您可以通过标记检索它

1
2
3
4
$marking = $workflow->apply($post, 'to_review');

// contains the new value
$marking->getContext();

也可以通过使用以下属性声明事件监听器来监听这些事件

这些属性的工作方式与 AsEventListener 属性类似

1
2
3
4
5
6
7
8
9
10
class ArticleWorkflowEventListener
{
    #[AsTransitionListener(workflow: 'my-workflow', transition: 'published')]
    public function onPublishedTransition(TransitionEvent $event): void
    {
        // ...
    }

    // ...
}

您可以参考关于 使用 PHP 属性定义事件监听器 的文档以了解更多用法。

守卫事件

有一种特殊类型的事件称为 "Guard events"(守卫事件)。每次调用 Workflow::can()Workflow::apply()Workflow::getEnabledTransitions() 时,都会调用它们的事件监听器。通过守卫事件,您可以添加自定义逻辑来决定哪些转换应该被阻塞。以下是守卫事件名称的列表。

  • workflow.guard
  • workflow.[工作流名称].guard
  • workflow.[工作流名称].guard.[转换名称]

此示例阻止任何缺少标题的博客文章转换为 "reviewed"(已审核)状态

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
// src/App/EventSubscriber/BlogPostReviewSubscriber.php
namespace App\EventSubscriber;

use App\Entity\BlogPost;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\GuardEvent;

class BlogPostReviewSubscriber implements EventSubscriberInterface
{
    public function guardReview(GuardEvent $event): void
    {
        /** @var BlogPost $post */
        $post = $event->getSubject();
        $title = $post->title;

        if (empty($title)) {
            $event->setBlocked(true, 'This blog post cannot be marked as reviewed because it has no title.');
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [
            'workflow.blog_publishing.guard.to_review' => ['guardReview'],
        ];
    }
}

选择要派发的事件

如果您希望控制在执行每个转换时触发哪些事件,请使用 events_to_dispatch 配置选项。此选项不适用于始终触发的 守卫事件

1
2
3
4
5
6
7
8
9
10
11
# config/packages/workflow.yaml
framework:
    workflows:
        blog_publishing:
            # you can pass one or more event names
            events_to_dispatch: ['workflow.leave', 'workflow.completed']

            # pass an empty array to not dispatch any event
            events_to_dispatch: []

            # ...

您也可以在应用转换时禁用特定事件的触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use App\Entity\BlogPost;
use Symfony\Component\Workflow\Exception\LogicException;

$post = new BlogPost();

$workflow = $this->container->get('workflow.blog_publishing');

try {
    $workflow->apply($post, 'to_review', [
        Workflow::DISABLE_ANNOUNCE_EVENT => true,
        Workflow::DISABLE_LEAVE_EVENT => true,
    ]);
} catch (LogicException $exception) {
    // ...
}

禁用特定转换的事件将优先于工作流配置中指定的任何事件。在上面的示例中,workflow.leave 事件将不会被触发,即使它已被指定为在工作流配置中为所有转换派发的事件。

这些是所有可用的常量

  • Workflow::DISABLE_LEAVE_EVENT
  • Workflow::DISABLE_TRANSITION_EVENT
  • Workflow::DISABLE_ENTER_EVENT
  • Workflow::DISABLE_ENTERED_EVENT
  • Workflow::DISABLE_COMPLETED_EVENT

事件方法

每个工作流事件都是 Event 的实例。这意味着每个事件都可以访问以下信息

getMarking()
返回工作流的 Marking(标记)。
getSubject()
返回派发事件的对象。
getTransition()
返回派发事件的 Transition(转换)。
getWorkflowName()
返回一个字符串,其中包含触发事件的工作流的名称。
getMetadata()
返回元数据。

对于守卫事件,有一个扩展的 GuardEvent 类。此类具有以下附加方法

isBlocked()
返回转换是否被阻塞。
setBlocked()
设置阻塞值。
getTransitionBlockerList()
返回事件 TransitionBlockerList(转换阻塞器列表)。参见 阻塞转换
addTransitionBlocker()
添加一个 TransitionBlocker 实例。

阻塞转换

工作流的执行可以通过调用自定义逻辑来控制,以决定在应用当前转换之前是否阻塞或允许它。此功能由 "guards"(守卫)提供,可以通过两种方式使用。

首先,您可以监听 守卫事件。或者,您可以为转换定义 guard 配置选项。此选项的值是使用 ExpressionLanguage 组件 创建的任何有效表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# config/packages/workflow.yaml
framework:
    workflows:
        blog_publishing:
            # previous configuration
            transitions:
                to_review:
                    # the transition is allowed only if the current user has the ROLE_REVIEWER role.
                    guard: "is_granted('ROLE_REVIEWER')"
                    from: draft
                    to:   reviewed
                publish:
                    # or "is_anonymous", "is_remember_me", "is_fully_authenticated", "is_granted", "is_valid"
                    guard: "is_authenticated"
                    from: reviewed
                    to:   published
                reject:
                    # or any valid expression language with "subject" referring to the supported object
                    guard: "is_granted('ROLE_ADMIN') and subject.isRejectable()"
                    from: reviewed
                    to:   rejected

您还可以使用转换阻塞器来阻塞并在您阻止转换发生时返回用户友好的错误消息。在示例中,我们从 Event 的元数据中获取此消息,为您提供一个集中管理文本的位置。

此示例已简化;在生产环境中,您可能更喜欢使用 Translation 组件 在一个地方管理消息

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
// src/App/EventSubscriber/BlogPostPublishSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\TransitionBlocker;

class BlogPostPublishSubscriber implements EventSubscriberInterface
{
    public function guardPublish(GuardEvent $event): void
    {
        $eventTransition = $event->getTransition();
        $hourLimit = $event->getMetadata('hour_limit', $eventTransition);

        if (date('H') <= $hourLimit) {
            return;
        }

        // Block the transition "publish" if it is more than 8 PM
        // with the message for end user
        $explanation = $event->getMetadata('explanation', $eventTransition);
        $event->addTransitionBlocker(new TransitionBlocker($explanation , '0'));
    }

    public static function getSubscribedEvents(): array
    {
        return [
            'workflow.blog_publishing.guard.publish' => ['guardPublish'],
        ];
    }
}

创建你自己的标记存储

您可能需要实现自己的存储来在标记更新时执行一些额外的逻辑。例如,您可能有一些特定的需求来存储某些工作流上的标记。为此,您需要实现 MarkingStoreInterface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace App\Workflow\MarkingStore;

use Symfony\Component\Workflow\Marking;
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;

final class BlogPostMarkingStore implements MarkingStoreInterface
{
    /**
     * @param BlogPost $subject
     */
    public function getMarking(object $subject): Marking
    {
        return new Marking([$subject->getCurrentPlace() => 1]);
    }

    /**
     * @param BlogPost $subject
     */
    public function setMarking(object $subject, Marking $marking, array $context = []): void
    {
        $marking = key($marking->getPlaces());
        $subject->setCurrentPlace($marking);
    }
}

一旦您的标记存储实现,您可以配置您的工作流以使用它

1
2
3
4
5
6
7
# config/packages/workflow.yaml
framework:
    workflows:
        blog_publishing:
            # ...
            marking_store:
                service: 'App\Workflow\MarkingStore\BlogPostMarkingStore'

在 Twig 中的用法

Symfony 定义了几个 Twig 函数来管理工作流并减少在模板中对领域逻辑的需求

workflow_can()
如果给定对象可以进行给定的转换,则返回 true
workflow_transitions()
返回一个数组,其中包含给定对象的所有已启用转换。
workflow_transition()
返回为给定对象和转换名称启用的特定转换。
workflow_marked_places()
返回一个数组,其中包含给定标记的状态名称。
workflow_has_marked_place()
如果给定对象的标记具有给定的状态,则返回 true
workflow_transition_blockers()
返回给定转换的 TransitionBlockerList(转换阻塞器列表)。

以下示例展示了这些函数的作用

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
<h3>Actions on Blog Post</h3>
{% if workflow_can(post, 'publish') %}
    <a href="...">Publish</a>
{% endif %}
{% if workflow_can(post, 'to_review') %}
    <a href="...">Submit to review</a>
{% endif %}
{% if workflow_can(post, 'reject') %}
    <a href="...">Reject</a>
{% endif %}

{# Or loop through the enabled transitions #}
{% for transition in workflow_transitions(post) %}
    <a href="...">{{ transition.name }}</a>
{% else %}
    No actions available.
{% endfor %}

{# Check if the object is in some specific place #}
{% if workflow_has_marked_place(post, 'reviewed') %}
    <p>This post is ready for review.</p>
{% endif %}

{# Check if some place has been marked on the object #}
{% if 'reviewed' in workflow_marked_places(post) %}
    <span class="label">Reviewed</span>
{% endif %}

{# Loop through the transition blockers #}
{% for blocker in workflow_transition_blockers(post, 'publish') %}
    <span class="error">{{ blocker.message }}</span>
{% endfor %}

存储元数据

如果您需要,您可以使用 metadata 选项在工作流、它们的状态和它们的转换中存储任意元数据。此元数据可以仅是工作流的标题或非常复杂的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# config/packages/workflow.yaml
framework:
    workflows:
        blog_publishing:
            metadata:
                title: 'Blog Publishing Workflow'
            # ...
            places:
                draft:
                    metadata:
                        max_num_of_words: 500
                # ...
            transitions:
                to_review:
                    from: draft
                    to:   review
                    metadata:
                        priority: 0.5
                publish:
                    from: reviewed
                    to:   published
                    metadata:
                        hour_limit: 20
                        explanation: 'You can not publish after 8 PM.'

然后您可以按如下方式在控制器中访问此元数据

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
// src/App/Controller/BlogPostController.php
use App\Entity\BlogPost;
use Symfony\Component\Workflow\WorkflowInterface;
// ...

public function myAction(WorkflowInterface $blogPublishingWorkflow, BlogPost $post): Response
{
    $title = $blogPublishingWorkflow
        ->getMetadataStore()
        ->getWorkflowMetadata()['title'] ?? 'Default title'
    ;

    $maxNumOfWords = $blogPublishingWorkflow
        ->getMetadataStore()
        ->getPlaceMetadata('draft')['max_num_of_words'] ?? 500
    ;

    $aTransition = $blogPublishingWorkflow->getDefinition()->getTransitions()[0];
    $priority = $blogPublishingWorkflow
        ->getMetadataStore()
        ->getTransitionMetadata($aTransition)['priority'] ?? 0
    ;

    // ...
}

有一个 getMetadata() 方法,适用于所有类型的元数据

1
2
3
4
5
6
7
8
// get "workflow metadata" passing the metadata key as argument
$title = $workflow->getMetadataStore()->getMetadata('title');

// get "place metadata" passing the metadata key as the first argument and the place name as the second argument
$maxNumOfWords = $workflow->getMetadataStore()->getMetadata('max_num_of_words', 'draft');

// get "transition metadata" passing the metadata key as the first argument and a Transition object as the second argument
$priority = $workflow->getMetadataStore()->getMetadata('priority', $aTransition);

在控制器的 flash 消息中

1
2
3
4
5
// $transition = ...; (an instance of Transition)

// $workflow is an injected Workflow instance
$title = $workflow->getMetadataStore()->getMetadata('title', $transition);
$this->addFlash('info', "You have successfully applied the transition with title: '$title'");

元数据也可以在监听器中访问,从 Event 对象中访问。

在 Twig 模板中,元数据可通过 workflow_metadata() 函数获得

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
<h2>Metadata of Blog Post</h2>
<p>
    <strong>Workflow</strong>:<br>
    <code>{{ workflow_metadata(blog_post, 'title') }}</code>
</p>
<p>
    <strong>Current place(s)</strong>
    <ul>
        {% for place in workflow_marked_places(blog_post) %}
            <li>
                {{ place }}:
                <code>{{ workflow_metadata(blog_post, 'max_num_of_words', place) ?: 'Unlimited'}}</code>
            </li>
        {% endfor %}
    </ul>
</p>
<p>
    <strong>Enabled transition(s)</strong>
    <ul>
        {% for transition in workflow_transitions(blog_post) %}
            <li>
                {{ transition.name }}:
                <code>{{ workflow_metadata(blog_post, 'priority', transition) ?: 0 }}</code>
            </li>
        {% endfor %}
    </ul>
</p>
<p>
    <strong>to_review Priority</strong>
    <ul>
        <li>
            to_review:
            <code>{{ workflow_metadata(blog_post, 'priority', workflow_transition(blog_post, 'to_review')) }}</code>
        </li>
    </ul>
</p>
本作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本