In Symfony 4.3 we improved the Workflow component with lots of major and minor features. This blog post summarizes the most important ones.

Added a context to Workflow::apply()

Grégoire Pineau
Contributed by Grégoire Pineau in #29146

When applying a transition, now it's possible to pass a custom $context (e.g. the user who performed the transition or the current date):

1
2
3
$workflow->apply($article, $request->request->get('transition'), [
    'time' => date('y-m-d H:i:s'),
]);

Before using this feature, update your entity or any other object supported by the workflow as follows:

1
2
3
4
class Article
 {
-    public function setMarking($marking)
+    public function setMarking($marking, $context = [])

Then, update the configuration of the workflow to use the MethodMarkingStore:

1
2
3
4
5
6
7
framework:
     workflows:
         article:
             type: workflow
             marking_store:
-                 type: multiple_state
+                 type: method

Allow to modify the context in a listener

Grégoire Pineau
Contributed by Grégoire Pineau in #30902

Passing the context on every call to ->apply() could be annoying and will lead to duplicated code.

Now you can create a listener that does that for you:

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
class TransitionEventSubscriber implements EventSubscriberInterface
{
    private $tokenStorage;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->tokenStorage = $tokenStorage;
    }

    public function onWorkflowArticleTransition(TransitionEvent $event)
    {
        $context = $event->getContext();

        $token = $this->tokenStorage->getToken();
        if ($token instanceof TokenInterface) {
            $user = $token->getUser();
            if ($user instanceof UserInterface) {
                $context['user'] = $user->getUsername();
            }
        }

        $event->setContext($context);
    }

    public static function getSubscribedEvents()
    {
        return [
           TransitionEvent::class => 'onWorkflowArticleTransition',
        ];
    }
}

Added color to dumped workflow

Alexis Lefebvre
Contributed by Alexis Lefebvre in #29538

It's now possible to configure how a workflow will be rendered thanks to the dump_style metadata config option:

1
2
3
4
5
6
7
8
9
10
transitions:
    submit:
        from: start
        to: travis
        metadata:
            title: transition submit title
            dump_style:
                label: 'My custom label'
                arrow_color: '#0088FF'
                label_color: 'Red'

And this is how the custom style would look like:

Allow to configure many initial places

Grégoire Pineau
Contributed by Grégoire Pineau in #30468 and #30890

Unlike state machines, when using a workflow it's possible to have a subject in many places. That's why the component now allows to configure multiple initial places:

1
2
3
4
5
workflows:
    article:
        type: workflow
        initial_marking: [foo, bar]
        places: [foo, bar, a, b, c, d]

Simpler configuration

Grégoire Pineau
Contributed by Grégoire Pineau in #30551 and #30890

As mentioned above, subjects can only be in one place in state machines but they can be in one or more states when using a workflow. However, the initial design of the Workflow component allowed to use a workflow with a Single State Marking Store. This wasn't the best decision and it added some unnecessary complexity.

Starting from Symfony 4.3, if your subject can be only in one state, use a state machine. In that case, the property (called marking by default) will be a string. If the subject can be in many places, use a workflow. In that case, the property will be an array.

Thanks to this simplification, we've improved the DX (developer experience):

1
2
3
4
5
6
7
8
9
10
11
12
framework:
    workflows:
        article:
            type: workflow
            marking_store:
                type: method # This will be the default value in Symfony 5.0
                property: marking # This is the default value, it could be omitted
        task:
            type: state_machine
            marking_store:
                type: method # This will be the default value in Symfony 5.0
                property: state

Added workflow_transition_blockers() Twig function

Grégoire Pineau
Contributed by Grégoire Pineau in #30908

In Symfony 4.1 we added a feature to know why a transition is blocked.

In Symfony 4.3 we're adding a Twig function to build the blocker list:

1
2
3
4
5
6
7
8
9
10
11
12
<h2>Publication was blocked because:</h2>
<ul>
    {% for blocker in workflow_transition_blockers(article, 'publish') %}
        <li>
            {{ blocker.message }}
            {# Display the guard expression #}
            {% if blocker.parameters.expression is defined %}
                <code>{{ blocker.parameters.expression }}</code>
            {% endif %}
        </li>
    {% endfor %}
<ul>
Published in #Living on the edge