Workflow

Using the Workflow component inside a Symfony application requires knowing first some basic theory and concepts about workflows and state machines. Read this article for a quick overview.

Symfony アプリケーション内で Workflow コンポーネントを使用するには、最初にワークフローとステート マシンに関する基本的な理論と概念を理解する必要があります。簡単な概要については、この記事をお読みください。

Installation

In applications using Symfony Flex, run this command to install the workflow feature before using it:

Symfony Flex を使用するアプリケーションでは、次のコマンドを実行して、ワークフロー機能を使用する前にインストールします。
1
$ composer require symfony/workflow

Configuration

To see all configuration options, if you are using the component inside a Symfony project run this command:

aSymfony プロジェクト内でコンポーネントを使用している場合、すべての設定オプションを表示するには、次のコマンドを実行します。
1
$ php bin/console config:dump-reference framework workflows

Creating a Workflow

A workflow is a process or a lifecycle that your objects go through. Each step or stage in the process is called a place. You also define transitions, which describe the action needed to get from one place to another.

ワークフローは、オブジェクトが通過するプロセスまたはライフサイクルです。プロセスの各ステップまたは段階は場所と呼ばれます。また、ある場所から別の場所に移動するために必要なアクションを記述する遷移も定義します。

A set of places and transitions creates a definition. A workflow needs a Definition and a way to write the states to the objects (i.e. an instance of a MarkingStoreInterface.)

一連の場所と遷移によって定義が作成されます。ワークフローには、定義と、状態をオブジェクトに書き込む方法 (つまり、MarkingStoreInterface のインスタンス) が必要です。

Consider the following example for a blog post. A post can have these places: draft, reviewed, rejected, published. You could define the workflow as
follows:

ブログ投稿の次の例を考えてみましょう。投稿には、下書き、レビュー、却下、公開の場所を含めることができます。ワークフローは次のように定義できます。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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:
                - draft
                - reviewed
                - rejected
                - published
            transitions:
                to_review:
                    from: draft
                    to:   reviewed
                publish:
                    from: reviewed
                    to:   published
                reject:
                    from: reviewed
                    to:   rejected

Tip

ヒント

If you are creating your first workflows, consider using the workflow:dump command to debug the workflow contents.

初めてワークフローを作成する場合は、workflow:dump コマンドを使用してワークフローの内容をデバッグすることを検討してください。

The configured property will be used via its implemented getter/setter methods by the marking store:

構成されたプロパティは、マーキング ストアによって実装された getter/setter メソッドを介して使用されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Entity/BlogPost.php
namespace App\Entity;

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

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

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

Note

ノート

The marking store type could be "multiple_state" or "single_state". A single state marking store does not support a model being on multiple places at the same time. This means a "workflow" must use a "multiple_state" marking store and a "state_machine" must use a "single_state" marking store. Symfony configures the marking store according to the "type" by default, so it's preferable to not configure it.

マーキング ストア タイプは、"multiple_state" または "single_state" です。単一状態のマーキング ストアは、同時に複数の場所に存在するモデルをサポートしません。つまり、「ワークフロー」は「multiple_state」マーキング ストアを使用し、「state_machine」は「single_state」マーキング ストアを使用する必要があります。 symfony はデフォルトで「タイプ」に従ってマーキング ストアを設定するため、設定しない方が望ましいです。

A single state marking store uses a string to store the data. A multiple state marking store uses an array to store the data.

単一の状態マーキング ストアは、文字列を使用してデータを格納します。複数状態のマーキング ストアは、配列を使用してデータを格納します。

Tip

ヒント

The marking_store.type (the default value depends on the type value) and property (default value ['marking']) attributes of the marking_store option are optional. If omitted, their default values will be used. It's highly recommended to use the default value.

Marking_store オプションの Marking_store.type (デフォルト値は type 値によって異なります) および property (デフォルト値 ['marking']) 属性はオプションです。省略した場合、デフォルト値が使用されます。デフォルト値を使用することを強くお勧めします。

Tip

ヒント

Setting the audit_trail.enabled option to true makes the application generate detailed log messages for the workflow activity.

audit_trail.enabled オプションを true に設定すると、アプリケーションはワークフロー アクティビティの詳細なログ メッセージを生成します。

With this workflow named blog_publishing, you can get help to decide what actions are allowed on a blog post:

blog_publishing という名前のこのワークフローを使用すると、ブログ投稿で許可されているアクションを決定するのに役立ちます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use App\Entity\BlogPost;
use Symfony\Component\Workflow\Exception\LogicException;

$post = new 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 $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');

Accessing the Workflow in a Class

You can use the workflow inside a class by using service autowiring and using camelCased workflow name + Workflow as parameter name. If it is a state machine type, use camelCased workflow name + StateMachine:

クラス内でワークフローを使用するには、サービス オートワイヤリングを使用し、camelCased ワークフロー名 + ワークフローをパラメーター名として使用します。ステートマシン タイプの場合は、camelCased ワークフロー名 + StateMachine を使用します。
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 App\Entity\BlogPost;
use Symfony\Component\Workflow\WorkflowInterface;

class MyClass
{
    private $blogPublishingWorkflow;

    // Symfony will inject the 'blog_publishing' workflow configured before
    public function __construct(WorkflowInterface $blogPublishingWorkflow)
    {
        $this->blogPublishingWorkflow = $blogPublishingWorkflow;
    }

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

6.2

6.2

All workflows and state machines services are tagged since in Symfony 6.2.

Symfony 6.2 以降、すべてのワークフローとステート マシン サービスはタグ付けされています。

Tip

ヒント

If you want to retrieve all workflows, for documentation purposes for example, you can inject all services with the following tag:

たとえば、文書化の目的ですべてのワークフローを取得する場合は、次のタグを使用してすべてのサービスを挿入できます。
  • workflow: all workflows and all state machine;
    ワークフロー: すべてのワークフローとすべてのステート マシン。
  • workflow.workflow: all workflows;
    workflow.workflow: すべてのワークフロー。
  • workflow.state_machine: all state machines.
    workflow.state_machine: すべてのステート マシン。

Tip

ヒント

You can find the list of available workflow services with the php bin/console debug:autowiring workflow command.

php bin/console debug:autowiring workflow コマンドを使用して、利用可能なワークフロー サービスのリストを見つけることができます。

Using Events

To make your workflows more flexible, you can construct the Workflow object with an EventDispatcher. You can now create event listeners to block transitions (i.e. depending on the data in the blog post) and do additional actions when a workflow operation happened (e.g. sending announcements).

ワークフローをより柔軟にするために、EventDispatcher を使用して Workflow オブジェクトを作成できます。イベント リスナーを作成して遷移をブロックし (つまり、ブログ投稿のデータに応じて)、ワークフロー操作が発生したときに追加のアクションを実行できるようになりました (例: 通知の送信)。

Each step has three events that are fired in order:

各ステップには、順番に発生する 3 つのイベントがあります。
  • An event for every workflow;
    すべてのワークフローのイベント。
  • An event for the workflow concerned;
    関連するワークフローのイベント。
  • An event for the workflow concerned with the specific transition or place name.
    特定のトランジションまたは場所名に関連するワークフローのイベント。

When a state transition is initiated, the events are dispatched in the following order:

状態遷移が開始されると、イベントは次の順序でディスパッチされます。
workflow.guard

Validate whether the transition is blocked or not (see guard events and blocking transitions).

遷移がブロックされているかどうかを検証します (seeguard イベントとブロッキング遷移)。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.guard
    ワークフロー.ガード
  • workflow.[workflow name].guard
    workflow.[ワークフロー名].guard
  • workflow.[workflow name].guard.[transition name]
    workflow.[ワークフロー名].guard.[トランジション名]
workflow.leave

The subject is about to leave a place.

対象は場所を離れようとしています。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.leave
    ワークフロー.残す
  • workflow.[workflow name].leave
    workflow.[ワークフロー名].leave
  • workflow.[workflow name].leave.[place name]
    workflow.[ワークフロー名].leave.[場所名]
workflow.transition

The subject is going through this transition.

対象はこの移行を通過しています。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.transition
    ワークフロー.トランジション
  • workflow.[workflow name].transition
    workflow.[ワークフロー名].transition
  • workflow.[workflow name].transition.[transition name]
    workflow.[ワークフロー名].transition.[トランジション名]
workflow.enter

The subject is about to enter a new place. This event is triggered right before the subject places are updated, which means that the marking of the subject is not yet updated with the new places.

対象は新しい場所に入ろうとしています。このイベントは、サブジェクトの場所が更新される直前にトリガーされます。つまり、サブジェクトのマーキングは新しい場所でまだ更新されていません。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.enter
    ワークフロー入力
  • workflow.[workflow name].enter
    workflow.[ワークフロー名].enter
  • workflow.[workflow name].enter.[place name]
    workflow.[ワークフロー名].enter.[場所名]
workflow.entered

The subject has entered in the places and the marking is updated.

箇所に被写体が入り、マーキングが更新されています。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.entered
    ワークフロー.入力済み
  • workflow.[workflow name].entered
    workflow.[ワークフロー名].entered
  • workflow.[workflow name].entered.[place name]
    workflow.[ワークフロー名].entered.[場所名]
workflow.completed

The object has completed this transition.

オブジェクトはこの遷移を完了しました。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.completed
    ワークフロー.完了
  • workflow.[workflow name].completed
    ワークフロー.[ワークフロー名].完了
  • workflow.[workflow name].completed.[transition name]
    workflow.[ワークフロー名].completed.[トランジション名]
workflow.announce

Triggered for each transition that now is accessible for the subject.

サブジェクトがアクセスできるようになったトランジションごとにトリガーされます。

The three events being dispatched are:

ディスパッチされる 3 つのイベントは次のとおりです。
  • workflow.announce
    ワークフロー.アナウンス
  • workflow.[workflow name].announce
    workflow.[ワークフロー名].announce
  • workflow.[workflow name].announce.[transition name]
    workflow.[ワークフロー名].announce.[トランジション名]

After a transition is applied, the announce event tests for all available transitions. That will trigger all guard events once more, which could impact performance if they include intensive CPU or database workloads.

トランジションが適用された後、アナウンス イベントは使用可能なすべてのトランジションをテストします。これにより、すべてのガード イベントが再度トリガーされ、集中的な CPU またはデータベースのワークロードが含まれる場合、パフォーマンスに影響を与える可能性があります。

If you don't need the announce event, disable it using the context:

アナウンス イベントが必要ない場合は、コンテキストを使用して無効にします。
1
$workflow->apply($subject, $transitionName, [Workflow::DISABLE_ANNOUNCE_EVENT => true]);

The context is accessible in all events except for the workflow.guard events:

コンテキストは、workflow.guard イベントを除くすべてのイベントでアクセスできます。
1
2
3
4
5
6
// $context must be an array
$context = ['context_key' => 'context_value'];
$workflow->apply($subject, $transitionName, $context);

// in an event listener (workflow.guard events)
$context = $event->getContext(); // returns ['context']

Note

ノート

The leaving and entering events are triggered even for transitions that stay in the same place.

出入りイベントは、同じ場所にとどまるトランジションに対してもトリガーされます。

Note

ノート

If you initialize the marking by calling $workflow->getMarking($object);, then the workflow.[workflow_name].entered.[initial_place_name] event will be called with the default context (Workflow::DEFAULT_INITIAL_CONTEXT).

$workflow->getMarking($object); を呼び出してマーキングを初期化すると、workflow.[workflow_name].entered.[initial_place_name] イベントがデフォルトのコンテキスト (Workflow::DEFAULT_INITIAL_CONTEXT) で呼び出されます。

Here is an example of how to enable logging for every time a "blog_publishing" workflow leaves a place:

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

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

class WorkflowLoggerSubscriber implements EventSubscriberInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function onLeave(Event $event)
    {
        $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()
    {
        return [
            'workflow.blog_publishing.leave' => 'onLeave',
        ];
    }
}

If some listeners update the context during a transition, you can retrieve it via the marking:

一部のリスナーが遷移中にコンテキストを更新する場合、マーキングを介して取得できます。
1
2
3
4
$marking = $workflow->apply($post, 'to_review');

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

Guard Events

There are special types of events called "Guard events". Their event listeners are invoked every time a call to Workflow::can(), Workflow::apply() or Workflow::getEnabledTransitions() is executed. With the guard events you may add custom logic to decide which transitions should be blocked or not. Here is a list of the guard event names.

「ガードイベント」と呼ばれる特別な種類のイベントがあります。 Workflow::can()、Workflow::apply()、Workflow::getEnabledTransitions() への呼び出しが実行されるたびに、それらのイベント リスナーが呼び出されます。ガード イベントを使用すると、カスタム ロジックを追加して、どのトランジションをブロックするかどうかを決定できます。これはガード イベント名のリストです。
  • workflow.guard
    ワークフロー.ガード
  • workflow.[workflow name].guard
    workflow.[ワークフロー名].guard
  • workflow.[workflow name].guard.[transition name]
    workflow.[ワークフロー名].guard.[トランジション名]

This example stops any blog post being transitioned to "reviewed" if it is missing a title:

この例では、タイトルがない場合、ブログ投稿が「レビュー済み」に移行されるのを停止します。
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)
    {
        /** @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()
    {
        return [
            'workflow.blog_publishing.guard.to_review' => ['guardReview'],
        ];
    }
}

Choosing which Events to Dispatch

If you prefer to control which events are fired when performing each transition, use the events_to_dispatch configuration option. This option does not apply to Guard events, which are always fired:

各トランジションの実行時にどのイベントが発生するかを制御したい場合は、events_to_dispatch 構成オプションを使用します。このオプションは、常に発生する Guard イベントには適用されません。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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: []

            # ...

You can also disable a specific event from being fired when applying a transition:

トランジションの適用時に特定のイベントが発生しないようにすることもできます。
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) {
    // ...
}

Disabling an event for a specific transition will take precedence over any events specified in the workflow configuration. In the above example the workflow.leave event will not be fired, even if it has been specified as an event to be dispatched for all transitions in the workflow configuration.

特定のトランジションのイベントを無効にすることは、ワークフロー構成で指定されたすべてのイベントよりも優先されます。上記の例では、ワークフロー設定ですべての遷移に対してディスパッチされるイベントとして指定されていても、workflow.leave イベントは発生しません。

These are all the available constants:

これらはすべて利用可能な定数です:
  • Workflow::DISABLE_LEAVE_EVENT
    ワークフロー::DISABLE_LEAVE_EVENT
  • Workflow::DISABLE_TRANSITION_EVENT
    ワークフロー::DISABLE_TRANSITION_EVENT
  • Workflow::DISABLE_ENTER_EVENT
    ワークフロー::DISABLE_ENTER_EVENT
  • Workflow::DISABLE_ENTERED_EVENT
    ワークフロー::DISABLE_ENTERED_EVENT
  • Workflow::DISABLE_COMPLETED_EVENT
    ワークフロー::DISABLE_COMPLETED_EVENT

Event Methods

Each workflow event is an instance of Event. This means that each event has access to the following information:

各ワークフロー イベントは Event のインスタンスです。これは、各イベントが次の情報にアクセスできることを意味します。
getMarking()
Returns the Marking of the workflow.
ワークフローのマーキングを返します。
getSubject()
Returns the object that dispatches the event.
イベントを送出するオブジェクトを返します。
getTransition()
Returns the Transition that dispatches the event.
イベントを送出する Transition を返します。
getWorkflowName()
Returns a string with the name of the workflow that triggered the event.
イベントをトリガーしたワークフローの名前を含む文字列を返します。
getMetadata()
Returns a metadata.
メタデータを返します。

For Guard Events, there is an extended GuardEvent class. This class has these additional methods:

ガード イベントには、拡張された GuardEvent クラスがあります。このクラスには、次の追加メソッドがあります。
isBlocked()
Returns if transition is blocked.
遷移がブロックされている場合に返します。
setBlocked()
Sets the blocked value.
ブロックされた値を設定します。
getTransitionBlockerList()
Returns the event TransitionBlockerList. See blocking transitions.
イベント TransitionBlockerList を返します。ブロック遷移を参照してください。
addTransitionBlocker()
Add a TransitionBlocker instance.
TransitionBlocker インスタンスを追加します。

Blocking Transitions

The execution of the workflow can be controlled by calling custom logic to decide if the current transition is blocked or allowed before applying it. This feature is provided by "guards", which can be used in two ways.

ワークフローの実行は、カスタム ロジックを呼び出して、現在のトランジションを適用する前にブロックするか許可するかを決定することによって制御できます。この機能は「ガード」によって提供され、2 つの方法で使用できます。

First, you can listen to the guard events. Alternatively, you can define a guard configuration option for the transition. The value of this option is any valid expression created with the ExpressionLanguage component:

まず、ガード イベントをリッスンできます。または、遷移のガード構成オプションを定義できます。このオプションの値は、ExpressionLanguage コンポーネントで作成された有効な式です。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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

You can also use transition blockers to block and return a user-friendly error message when you stop a transition from happening. In the example we get this message from the Event's metadata, giving you a central place to manage the text.

また、トランジション ブロッカーを使用して、トランジションの発生を停止したときに、ユーザー フレンドリなエラー メッセージをブロックして返すこともできます。この例では、イベントのメタデータからこのメッセージを取得し、テキストを管理する中心的な場所を提供します。

This example has been simplified; in production you may prefer to use the Translation component to manage messages in one place:

この例は簡略化されています。本番環境では、Translation コンポーネントを使用してメッセージを 1 か所で管理したい場合があります。
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)
    {
        $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()
    {
        return [
            'workflow.blog_publishing.guard.publish' => ['guardPublish'],
        ];
    }
}

Usage in Twig

Symfony defines several Twig functions to manage workflows and reduce the need of domain logic in your templates:

symfony では、ワークフローを管理し、テンプレート内のドメイン ロジックの必要性を減らすために、いくつかの Twig 関数が定義されています。
workflow_can()
Returns true if the given object can make the given transition.
指定されたオブジェクトが指定された遷移を行うことができる場合、true を返します。
workflow_transitions()
Returns an array with all the transitions enabled for the given object.
指定されたオブジェクトで有効になっているすべてのトランジションを含む配列を返します。
workflow_transition()
Returns a specific transition enabled for the given object and transition name.
指定されたオブジェクトとトランジション名に対して有効になっている特定のトランジションを返します。
workflow_marked_places()
Returns an array with the place names of the given marking.
指定されたマーキングの地名の配列を返します。
workflow_has_marked_place()
Returns true if the marking of the given object has the given state.
指定されたオブジェクトのマーキングが指定された状態である場合、true を返します。
workflow_transition_blockers()
Returns TransitionBlockerList for the given transition.
指定されたトランジションの TransitionBlockerList を返します。

The following example shows these functions in action:

次の例は、これらの関数の動作を示しています。
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 %}

Storing Metadata

In case you need it, you can store arbitrary metadata in workflows, their places, and their transitions using the metadata option. This metadata can be only the title of the workflow or very complex objects:

必要に応じて、メタデータ オプションを使用して、任意のメタデータをワークフロー、その場所、およびそのトランジションに保存できます。このメタデータは、ワークフローのタイトルのみ、または非常に複雑なオブジェクトにすることができます:
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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.'

Then you can access this metadata in your controller as follows:

次に、次のようにコントローラーでこのメタデータにアクセスできます。
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)
{
    $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
    ;

    // ...
}

There is a getMetadata() method that works with all kinds of metadata:

すべての種類のメタデータを扱う 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);

In a flash message in your controller:

コントローラーのフラッシュ メッセージ:
1
2
3
4
5
// $transition = ...; (an instance of Transition)

// $workflow is a Workflow instance retrieved from the Registry or injected directly (see above)
$title = $workflow->getMetadataStore()->getMetadata('title', $transition);
$this->addFlash('info', "You have successfully applied the transition with title: '$title'");

Metadata can also be accessed in a Listener, from the Event object.

メタデータには、Event オブジェクトから Listener でアクセスすることもできます。

In Twig templates, metadata is available via the workflow_metadata() function:

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>

Learn more