Skip to content

Added docs for Workflow component #6871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d0701f0
Added docs for Workflow component
Nyholm Aug 12, 2016
91867c2
Moved images
Nyholm Aug 12, 2016
69bca59
Fixes
Nyholm Aug 12, 2016
f99fbb2
Fixes
Nyholm Aug 12, 2016
e797f0c
Syntax error
Nyholm Aug 12, 2016
5b2a029
Cleanup and minor fixes
Nyholm Aug 16, 2016
196baf9
Separated docs for component
Nyholm Aug 16, 2016
763950d
Updating docs to using symfony services
Nyholm Aug 16, 2016
cead2f7
Added docs about registry
Nyholm Aug 16, 2016
e0089c5
Added placeholders
Nyholm Aug 16, 2016
48de43d
Added note about state machines
Nyholm Aug 16, 2016
805b237
Added example with twig
Nyholm Aug 16, 2016
f57ec14
Show Twig function workflow_transitions
Nyholm Aug 16, 2016
03925ff
syntax fix
Nyholm Aug 16, 2016
e6bdee6
Some syntax fixes and a better "why do we need this"
Nyholm Nov 7, 2016
c7464c7
typos
Nyholm Nov 7, 2016
83d26c1
toctree fix
Nyholm Nov 7, 2016
6e7a35f
Fixed typos and comments
Nyholm Nov 8, 2016
4415466
Added usage example on the component
Nyholm Nov 8, 2016
866b25a
Added example how to dump with Symfony
Nyholm Nov 8, 2016
dceebec
Added examples of workflows
Nyholm Nov 8, 2016
b959f8a
Updated state machine with an example
Nyholm Nov 8, 2016
b45edf2
simplify job_application
Nyholm Nov 8, 2016
fefdb5f
syntax
Nyholm Nov 9, 2016
7f0f5b0
Added comment about the service name
Nyholm Nov 9, 2016
c681283
Updated accoding to feedback
Nyholm Nov 9, 2016
86ecf0a
Added PHP config
Nyholm Nov 9, 2016
d002a8b
Added xml and PHP config
Nyholm Nov 9, 2016
b0a8855
Added workflow under guides
Nyholm Nov 9, 2016
4e7cf11
Removed the *why workflows* from the usage page.
Nyholm Nov 9, 2016
3aa433d
Documented support for DefinitionBuilder
Nyholm Nov 9, 2016
4f277dc
fixed typo
Nyholm Nov 9, 2016
2511c21
Merge pull request #1 from Nyholm/workflow-definition-builder
Nyholm Nov 9, 2016
c9b1656
Updated to support changes in https://github.com/symfony/symfony/pull…
Nyholm Nov 9, 2016
2cc2934
Updated twig example
Nyholm Nov 9, 2016
47dc11d
show how to configure the DI extension config
Nyholm Nov 11, 2016
3250621
Fixed typos
Nyholm Nov 11, 2016
c0bd6da
Use imperative instead of past tense.
Nyholm Nov 11, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added _images/components/workflow/blogpost.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 72 additions & 3 deletions components/workflow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
The Workflow Component
======================

The Workflow component provides tools for managing a workflow or finite state
machine.
The Workflow component provides tools for managing a workflow or finite
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't a Petri net instead ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickaelandrieu a workflow is a subset of a petri net (and a state machine a subset of workflow). The component does not support all features necessary to implement a petri net

Copy link
Contributor

@mickaelandrieu mickaelandrieu Dec 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's unclear to me right now, but it's not a problem in the docs: I just need to learn a little bit more about all of theses implementations ^^

Thank you @stof

state machine.

.. versionadded:: 3.2
The Workflow component was introduced in Symfony 3.2.
Expand All @@ -19,6 +19,75 @@ You can install the component in 2 different ways:
* :doc:`Install it via Composer </components/using_components>` (``symfony/workflow`` on `Packagist`_);
* Use the official Git repository (https://github.com/symfony/workflow).

For more information, see the code in the Git Repository.
.. include:: /components/require_autoload.rst.inc

Creating a Workflow
-------------------

The workflow component gives you an object oriented way to define a process
Copy link
Contributor

@mickaelandrieu mickaelandrieu Nov 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I read this, the more I think we should rename - at least in docs - Workflow component to Workflow Engine component. Am I crazy ? /c @Nyholm

or a life cycle that your object goes through. Each step or stage in the
process is called a *place*. You do also define *transitions* to that describes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transitions to that describes ... -> transitions that describe ... ?

the action to get from one place to another.

.. image:: /_images/components/workflow/states_transitions.png

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 :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing parenthesis at the end of this line.


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also the initial place with something like: If a definition has no explicit initial place, it uses the first place defined.

Consider the following example for a blog post. A post can have places:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This -> A post can have places looks confusing to me because it uses workflow terminology without a more gentle introduction:

A post can have one of a number of predefined statuses (`draft`, `review`, `rejected`, `published`).
In a workflow, these statuses are called **places**.

'draft', 'review', 'rejected', 'published'. You can define the workflow
like this::

use Symfony\Component\Workflow\Definition;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\MarkingStore\ScalarMarkingStore;

$states = ['draft', 'review', 'rejected', 'published'];

// Define a transaction with a name, where to go from and where to go to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'd use workflow terminology:

// Transitions are defined with a unique name, an origin place and a destination place

$transitions[] = new Transition('to_review', 'draft', 'review');
$transitions[] = new Transition('publish', 'review', 'published');
$transitions[] = new Transition('reject', 'review', 'rejected');

$definition = new Definition($states, $transitions);
$definition->setInitialPlace('draft');

$marking = new ScalarMarkingStore('currentState');
$workflow = new Workflow($definition, $marking);

The ``Workflow`` can now help you to decide what actions that are allowed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what actions that are allowed -> what actions are allowed

on a blog post depending on what *place* it is in. This will keep your domain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about domain logic, it's more about Workflow management logic, isn'it ?

I'd say instead "This will keep your Workflow logic in one place and not spread all over your applications.", what do you think ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I would always consider this the domain logic because the "workflow management logic" is a part of your domain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The theory behind "Workflow management" is to put the Workflow management outside of your domain, if I've well understood. BlogPost entity shouldn't be modified every time the rules of publishing evolve, for instance.

I'm pretty noob of this field, so correct me if I'm wrong :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BlogPost entity shouldn't be modified every time the rules of publishing evolve, for instance.

Correct, you just edit the workflow when the rules of publishing changes.

The theory behind "Workflow management" is to put the Workflow management outside of your domain, if I've well understood.

Outside the domain or outside your model? The way I see it there is only three places you can put code/config:

  • In the framework
  • In your domain
  • The glue between your domain and the framework.

Can @lyrixx give some input?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand the question / debate here.
Basically, the the data are stored in the model, and you can put the places / transitions everywhere you want. I don't have a rule of thumb for that. IMHO, Simple things are always better than perfect code.

Copy link
Contributor

@unkind unkind Aug 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you write an issue with a summary of your thoughts with pros and cons, I'll be happy to submit a PR for that issue.

I can't do it, because the primary purpose of this Workflow component is not obvious for me:

It would be nice to describe the purpose of the component more.

The component is designed as very generic thing, so in theory it probably may have some interesting applications. However, provided examples don't impress. Compare it with rich domain model:

final class Article
{
    private $id;
    private $title;
    private $approvedByJournalist;
    private $approvedBySpellchecker;
    private $published;

    public static function postDraft(int $articleId, ArticleTitle $title): Article
    {
        $draft = new self();
        $draft->apply(new DraftArticleWasPosted($articleId, $title));
        $draft->apply(new ArticleWasSentForApproval($this->id));

        return $draft;
    }

    private function __construct() {}

    public function approveByJournalist()
    {
        if ($this->approvedByJournalist) {
            return;
        }

        $this->apply(new ArticleWasApprovedByJournalist($this->id));
        $this->publishIfItIsReady();
    }

    public function approveBySpellchecker() 
    {
        if ($this->approvedBySpellchecker) {
            return;
        }

        $this->apply(new ArticleWasApprovedBySpellchecker($this->id));
        $this->publishIfItIsReady();
    }

    public function changeTitle(ArticleTitle $newTitle)
    {
        if ($this->title->equals($newTitle)) {
            return;
        }

        $this->apply(new ArticleTitleWasChanged($this->id, $newTitle));
        $this->apply(new ArticleWasSentForApproval($this->id));
    }

    private function publishIfItIsReady()
    {
        if ($this->approvedByJournalist && $this->approvedBySpellchecker) {
            $this->apply(new ArticleWasPublished($this->id));
        }
    }

    // ...

    private function onArticleWasApprovedByJournalist(ArticleWasApprovedByJournalist $event)
    {
        $this->approvedByJournalist = true;
    }

    private function onArticleWasSentForApproval(ArticleWasSentForApproval $event)
    {
        $this->approvedByJournalist = false;
        $this->approvedBySpellchecker = false;
        $this->published = false;
    }

    private function onArticleTitleWasChanged(ArticleTitleWasChanged $event)
    {
        $this->title = $event->getNewTitle();
    }
}

We see use-cases (postDraft, approveBySpellchecker, approveBySpellchecker, changeTitle), events (ArticleWasApprovedByJournalist, ..., ArticleTitleWasChanged) and it's not hard to understand lifecycle of the Article from this code. We lose this expressiveness.

Further, that sample project checks permissions with GuardEvent hook:

public function onTransitionJournalist(GuardEvent $event)
    {
        if (!$this->checker->isGranted('ROLE_JOURNALIST')) {
            $event->setBlocked(true);
        }
    }

... and how you can solve it with commands:

final class ApproveArticleByJournalistHandler
{
    /**
     * @acl_role ROLE_JOURNALIST
     */
    public function handle(ApproveArticleByJournalist $command)
    {
        $blogPost = $this->blogPostRepository->find($command->getArticleId());
        $blogPost->approveByJournalist();
        $this->blogPostRepository->save($blogPost);
    }
}

And again we lose expressiveness by generic GuardEvent hook. It's harder to find it in the project, your have to search for string "workflow.article.guard.journalist_approval" instead of typing class name "ApproveArticleByJournalis...".

I would like to know if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, all this debate is totally out of the scope of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is it possible to write docs for the component without clear applications, examples?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not said that. But you you are telling us the Workflow component is useless and it's better to write code like your example. It's really a matter of taste (I have a strong opinion on that ; but I let people making their own choices). So I'm juste saying: The component is here, and it's useless to discuss if it's better to use CQRS / ES over Workflow Component.

For the record, the discussion is about the following sentence:

The workflow will keep your domain logic in one place and not spread all over your application

IMHO, it's more important to document how the component works.

I like the @Nyholm's work ; so let's focus on what matter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unkind: I hear you. Your arguments are valid.
@mickaelandrieu: Your arguments are also valid.

This is, as Grégoire says, out of scope for this PR. @unkind, I've asked you to create an issue on the symfony/symfony repository. I am happy to make a PR to try to improve the Workflow component regarding your input. Let's work together on this, but not in this PR.

logic in one place and not spread all over your application.

When you start defining multiple workflows you should consider putting them
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you start defining multiple workflows you should consider putting them
in a ``Registry``.

-->

When you define multiple workflows you should consider using a ``Registry``,
which is an object that stores and provides access to different workflows.

in a ``Registry``. A registry will also help you to decide if a workflow
supports the object you are trying to use it with::

use Symfony\Component\Workflow\Registry;
use Acme\Entity\BlogPost;
use Acme\Entity\Newsletter;

$blogWorkflow = ...
$newsletterWorkflow = ...

$registry = new Registry();
$registry->add($blogWorkflow, BlogPost::class);
$registry->add($newsletterWorkflow, Newsletter::class);

// ...
$post = new BlogPost();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please prefix the example with // ... to indicate things are left out (but shown above).

// ...
$post = new BlogPost();

$workflow = $registry->get($post);

Learn more
----------

.. toctree::
:maxdepth: 1
:glob:

/workflow
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this one. It kinda makes sense, but it'll ruin Sphinx's global toctree afaics.

/workflow/*

.. _Packagist: https://packagist.org/packages/symfony/workflow
17 changes: 17 additions & 0 deletions workflow.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Workflow
--------

A workflow is a model of a process in your application. It may be the process
of how a blog post goes from draft, review and publish. An other example is when
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An other -> Another

a user submitts a series of different forms to complete a task. Such process are
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such process are -> Such processes are

best kept away from your models and should be defined in configuration.

A state machine is a subset of a workflow and its purpose is to hold a state of
your model. Both the workflow and state machine defines what actions (transitions)
that are allowed on the model at each state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what actions (transitions) that are allowed -> what actions (transitions) are allowed


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two blank lines

.. toctree::
:maxdepth: 1
:glob:

workflow/*
30 changes: 30 additions & 0 deletions workflow/dumping-workflows.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. index::
single: Workflow; Dumping Workflows

How to Dump Workflows
=====================

To help you debug you could dump a representation of your workflow with
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To help you debug you could dump a ... -> To help you debug your workflows, you can dump a ...

the use of a ``DumperInterface``. Use the ``GraphvizDumper`` to create a
PNG image of the workflow defined above::

// dump-graph.php
$dumper = new GraphvizDumper();
echo $dumper->dump($definition);

.. code-block:: bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We recently switched to "terminal" for terminal examples.


$ php dump-graph-php > out.dot
$ dot -Tpng out.dot -o graph.png

The result will look like this:

.. image:: /_images/components/workflow/blogpost.png

.. note::

The ``dot`` command is a part of Graphviz. You can download it and read
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is part of

more about it on `Graphviz.org`_.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove the double empty line

.. _Graphviz.org: http://www.graphviz.org
61 changes: 61 additions & 0 deletions workflow/state-machines.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.. index::
single: Workflow; Workflows as State Machines

Workflows as State Machines
===========================

The workflow component is modelled after a *Workflow net* which is a subclass
of a `Petri net`_. By adding further restrictions you can get a state machine.
The most important one being that a state machine cannot be in more than
one place simultaneously. It is also worth noting that a workflow does not
commonly have cyclic path in the definition graph but it is common for a state
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"[...] definition graph, but it [...]"

machine.

.. configuration-block::

.. code-block:: yaml

framework:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add # app/config/config.yml

workflows:
blog_publishing:
type:
type: 'state_machine'
supports:
- AppBundle\Entity\BlogPost
places:
- draft
- review
- rejected
- published
transitions:
to_review:
from: [draft, rejected]
to: review
publish:
from: review
to: published
reject:
from: review
to: rejected


With the configuration above we allow an object in place ``draft`` **or**
``rejected`` to be moved to ``review``. If the marking store had been of
type ``scalar`` the object had to be in **both** places.

.. code-block:: php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this directive -> .. code-block:: php in Symfony Docs we prefer to remove it and add two colons (::) at the end of the line immediately above the PHP code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the previous paragraph didn't end with a colon (which is the case here).

Although reSt allows us to do the object ahd to be in **both** places. ::, we're always using the long .. code-block:: php in those cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wouterj another reason to stop using this ugly :: hack. I wish we could get rid of it 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think :: is ugly. It's just very smart. And we can get rid of it, but it would make things pretty verbose (e.g. replacing :: with : + .. code-block:: php)


$workflow = $this->container->get('state_machine.blog_publishing');
$post = new \BlogPost();

$post->state = 'draft';
$workflow->can($post, 'to_review'); // True

$post->state = 'rejected';
$workflow->can($post, 'to_review'); // True


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These extra blank lines can be removed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multiple blank lines should always be just one




.. _Petri net: https://en.wikipedia.org/wiki/Petri_net
161 changes: 161 additions & 0 deletions workflow/usage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
.. index::
single: Workflow; Usage

How to Use the Workflow
=======================

Using the workflow component will help you to keep your domain logic as
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workflow component

configuration. Having domain logic in one place gives you a better overview
and it is easier to maintain whenever the domain requirement changes since
you do not have to edit all your controllers, twig templates and services.

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 do also define *transitions*
to that describes the action to get from one place to another.

.. image:: /_images/components/workflow/states_transitions.png

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 :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing parenthesis at the end of this line.


Consider the following example for a blog post. A post can have places:
'draft', 'review', 'rejected', 'published'. You can define the workflow
like this:

.. configuration-block::

.. code-block:: yaml

framework:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add the file comment + XML and PHP here as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thank you

workflows:
blog_publishing:
type: 'workflow' # or 'state_machine'
marking_store:
type: 'property_accessor' # or 'scalar' or 'state_machine'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have the configuration references as well. Is the initial state automatically guessed from the first place in the list ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, the type of marking store can not be state_machine (see here)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed now.

arguments:
- 'currentPlace'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about

arguments:
    - 'status' # The name of property that holds the marking in the object

supports:
- AppBundle\Entity\BlogPost
places:
- draft
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- drafted # By default the initial state is the first place
- to_review

while renaming the transition 'submit' ?

- review
- rejected
- published
transitions:
to_review:
from: draft
to: review
publish:
from: review
to: published
reject:
from: review
to: rejected


.. code-block: php
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: should be a double colon


class BlogPost
{
// This property is used by the marking store
public $currentPlace;
public $title;
public $content
}

.. note::

The marking store type could be "property_accessor" or "scalar".
A scalar marking type does not support a model being on multiple places.

With this workflow named ``blog_publishing`` you can get help to decide
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"[...] named blog_publishing, you can [...]"

what actions that are allowed on a blog post.

.. code-block:: php

$post = new \BlogPost();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be \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);

Using Events
------------

To make your workflows even more powerful you could construct the ``Workflow``
object with an ``EventDispatcher``. You can now create event listeners to
block transitions ie depending on the data in the blog post. The following
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"block transitions (i.e. depending on the data in the blog post)."

events are dispatched:

* ``workflow.guard``
* ``workflow.[workflow name].guard``
* ``workflow.[workflow name].guard.[transition name]``

See example to make sure no blog post without title is moved to "review"::

class BlogPostReviewListener implements EventSubscriberInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing use statements (for the GuardEvent, 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'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method does not need to be in an array.

);
}
}

With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you
could easily enable logging::

$logger = new PSR3Logger();
$subscriber = new AuditTrailListener($logger);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing use statements

$dispatcher->addSubscriber($subscriber);

Usage in Twig
-------------

Using your workflow in your Twig templates reduces the need of domain logic
in the view layer. Consider this example of the control panel for our blog's
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"[...] for our blog's edit page" -> "[...] of the blog." ? (again, to remove first person)

edit page. The links below will only be displayed when the action is allowed:

.. code-block:: twig

<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(article) %}
Copy link
Member

@yceruto yceruto Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_transitions(post) instead of article to be consistent with the whole document ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I've updated the PR

<a href="...">{{ transition.name }}</a>
{% else %}
No actions available.
{% endfor %}