From d0701f04d48af12e0ce485609eab84bb4646e9a2 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 12 Aug 2016 22:00:24 +0200 Subject: [PATCH 01/37] Added docs for Workflow component --- _images/workflow/blogpost.png | Bin 0 -> 26550 bytes _images/workflow/states_transactions.png | Bin 0 -> 20525 bytes components/workflow.rst | 105 +++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 _images/workflow/blogpost.png create mode 100644 _images/workflow/states_transactions.png diff --git a/_images/workflow/blogpost.png b/_images/workflow/blogpost.png new file mode 100644 index 0000000000000000000000000000000000000000..38e29250eb1d92a08cabefd6cb899824e936cfe9 GIT binary patch literal 26550 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'draft'); + + $marking = new ScalarMarkingStore('currentState'); + $workflow = new Workflow($definition, $marking); + +The ``Workflow`` can now help you do decide what actions that are allowed on a blog post. + + + $post = new \stdClass(); + $post->currentState = null; + $workflow->can($post, 'publish'); // False + $workflow->can($post, 'to_draft'); // True + + // Update the currentState on the post + try { + $workflow->apply($post, 'to_review'); + } catch (LogicException $e) { + // ... + } + + // See all the available transaction for the post in the current state + $transactions = $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 transactions ie depending on the data in the blog post. The following events +are dispatched: + +* workflow.guard +* workflow.[workflow name].guard +* workflow.[workflow name].guard.[transaction name] + +See example to make sure no blog post without title is moved to "review":: + + class BlogPostReviewListener implements EventSubscriberInterface + { + public function guardReview(GuardEvent $event) + { + $post = $event->getSubject(); + $title = $post->getTitle(); + + if (empty($title)) { + // Posts with no title should not be allowed + $event->setBlocked(true); + } + } + + public static function getSubscribedEvents() + { + return array( + 'workflow.blogpost.guad.to_review' => array('guardReview'), + ); + } + } + +With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you could easily enable logging:: + + $logger = new PSR3Logger() + $subscriber = new AuditTrailListener($logger); + $dispatcher->addSubscriber($subscriber); + +Dumper +------ + +To help you debug you could dump a representation of your workflow with 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 + + $ 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 + + .. _Packagist: https://packagist.org/packages/symfony/workflow From 91867c259ac6af5f360352b386d6a3989895e510 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 12 Aug 2016 22:14:31 +0200 Subject: [PATCH 02/37] Moved images --- _images/{ => components}/workflow/blogpost.png | Bin .../workflow/states_transactions.png | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename _images/{ => components}/workflow/blogpost.png (100%) rename _images/{ => components}/workflow/states_transactions.png (100%) diff --git a/_images/workflow/blogpost.png b/_images/components/workflow/blogpost.png similarity index 100% rename from _images/workflow/blogpost.png rename to _images/components/workflow/blogpost.png diff --git a/_images/workflow/states_transactions.png b/_images/components/workflow/states_transactions.png similarity index 100% rename from _images/workflow/states_transactions.png rename to _images/components/workflow/states_transactions.png From 69bca59445b6d186cb63c05ce362b638d92fc36d Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 12 Aug 2016 22:17:49 +0200 Subject: [PATCH 03/37] Fixes --- components/workflow.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/components/workflow.rst b/components/workflow.rst index b66dc50a36f..e308dc2c3f1 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -19,7 +19,7 @@ You can install the component in 2 different ways: * :doc:`Install it via Composer ` (``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 Usage ----- @@ -29,11 +29,11 @@ define *places* (or *states*) and *transactions*. A transaction describes the ac .. image:: /_images/components/workflow/states_transactions.png -A set of places and ``Transaction``s form a ``Definition``. A ``Workflow`` needs a ``Definition`` and a way to write -the states to the objects, ie a ``MarkingStoreInterface``. +A set of places and ``Transaction's`` creates a ``Definition``. A ``Workflow`` needs a ``Definition`` and a way to write +the states to the objects, ie an instance of a ``MarkingStoreInterface``. Consider the following example for a blog post. A post can have places: 'draft', 'review', 'rejected', 'published'. You -can define the workflow like this: +can define the workflow like this:: $states = ['draft', 'review', 'rejected', 'published']; $transactions[] = new Transition('to_review', ['draft', 'rejected'], 'review'); @@ -46,8 +46,7 @@ can define the workflow like this: $marking = new ScalarMarkingStore('currentState'); $workflow = new Workflow($definition, $marking); -The ``Workflow`` can now help you do decide what actions that are allowed on a blog post. - +The ``Workflow`` can now help you do decide what actions that are allowed on a blog post.: $post = new \stdClass(); $post->currentState = null; From f99fbb2b7edd1c12373d5d7289441a1aaf62fc0b Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 12 Aug 2016 22:23:56 +0200 Subject: [PATCH 04/37] Fixes --- components/workflow.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/workflow.rst b/components/workflow.rst index e308dc2c3f1..2940c7e32c0 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -46,8 +46,9 @@ can define the workflow like this:: $marking = new ScalarMarkingStore('currentState'); $workflow = new Workflow($definition, $marking); -The ``Workflow`` can now help you do decide what actions that are allowed on a blog post.: +The ``Workflow`` can now help you to decide what actions that are allowed on a blog post. +.. code-block:: php $post = new \stdClass(); $post->currentState = null; $workflow->can($post, 'publish'); // False @@ -78,6 +79,10 @@ are dispatched: See example to make sure no blog post without title is moved to "review":: + $marking = new ScalarMarkingStore('currentState'); + $workflow = new Workflow($definition, $marking, $dispatcher, 'blogpost'); + +.. code-block:: php class BlogPostReviewListener implements EventSubscriberInterface { public function guardReview(GuardEvent $event) @@ -101,7 +106,7 @@ See example to make sure no blog post without title is moved to "review":: With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you could easily enable logging:: - $logger = new PSR3Logger() + $logger = new PSR3Logger(); $subscriber = new AuditTrailListener($logger); $dispatcher->addSubscriber($subscriber); From e797f0c5080da71d4fa9b8fe1f2b072de9802446 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Fri, 12 Aug 2016 22:26:41 +0200 Subject: [PATCH 05/37] Syntax error --- components/workflow.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/workflow.rst b/components/workflow.rst index 2940c7e32c0..07fdd1f115c 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -49,6 +49,7 @@ can define the workflow like this:: The ``Workflow`` can now help you to decide what actions that are allowed on a blog post. .. code-block:: php + $post = new \stdClass(); $post->currentState = null; $workflow->can($post, 'publish'); // False @@ -83,6 +84,7 @@ See example to make sure no blog post without title is moved to "review":: $workflow = new Workflow($definition, $marking, $dispatcher, 'blogpost'); .. code-block:: php + class BlogPostReviewListener implements EventSubscriberInterface { public function guardReview(GuardEvent $event) From 5b2a029e36155a3bec41eee4ba87d05c319cd620 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 10:13:16 +0200 Subject: [PATCH 06/37] Cleanup and minor fixes --- ...ransactions.png => states_transitions.png} | Bin components/workflow.rst | 73 +++++++++++------- workflow.rst | 8 ++ 3 files changed, 52 insertions(+), 29 deletions(-) rename _images/components/workflow/{states_transactions.png => states_transitions.png} (100%) create mode 100644 workflow.rst diff --git a/_images/components/workflow/states_transactions.png b/_images/components/workflow/states_transitions.png similarity index 100% rename from _images/components/workflow/states_transactions.png rename to _images/components/workflow/states_transitions.png diff --git a/components/workflow.rst b/components/workflow.rst index 07fdd1f115c..f1b0f2ca6b6 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -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 + state machine. .. versionadded:: 3.2 The Workflow component was introduced in Symfony 3.2. @@ -24,32 +24,42 @@ You can install the component in 2 different ways: Usage ----- -The workflow component gives you an object oriented way to work with state machines. A state machine lets you -define *places* (or *states*) and *transactions*. A transaction describes the action to get from one place to another. +The workflow component gives you an object oriented way to work with state +machines. A state machine lets you define *places* and *transitions*. +A transition describes the action to get from one place to another. -.. image:: /_images/components/workflow/states_transactions.png +.. image:: /_images/components/workflow/states_transitions.png -A set of places and ``Transaction's`` creates a ``Definition``. A ``Workflow`` needs a ``Definition`` and a way to write -the states to the objects, ie an instance of a ``MarkingStoreInterface``. +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`. -Consider the following example for a blog post. A post can have places: 'draft', 'review', 'rejected', 'published'. You -can define the workflow like this:: +Consider the following example for a blog post. A post can have 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']; - $transactions[] = new Transition('to_review', ['draft', 'rejected'], 'review'); - $transactions[] = new Transition('publish', 'review', 'published'); - $transactions[] = new Transition('reject', 'review', 'rejected'); + $transitions[] = new Transition('to_review', ['draft', 'rejected'], 'review'); + $transitions[] = new Transition('publish', 'review', 'published'); + $transitions[] = new Transition('reject', 'review', 'rejected'); - $definition = new Definition($states, $transactions); + $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 on a blog post. +The ``Workflow`` can now help you to decide what actions that are allowed +on a blog post. .. code-block:: php + // ... $post = new \stdClass(); $post->currentState = null; $workflow->can($post, 'publish'); // False @@ -62,21 +72,20 @@ The ``Workflow`` can now help you to decide what actions that are allowed on a b // ... } - // See all the available transaction for the post in the current state - $transactions = $workflow->getEnabledTransitions($post); - + // See all the available transition for the post in the current state + $transitions = $workflow->getEnabledTransitions($post); - -Using events +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 transactions ie depending on the data in the blog post. The following events -are dispatched: +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 +events are dispatched: -* workflow.guard -* workflow.[workflow name].guard -* workflow.[workflow name].guard.[transaction name] +* ``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":: @@ -101,12 +110,13 @@ See example to make sure no blog post without title is moved to "review":: public static function getSubscribedEvents() { return array( - 'workflow.blogpost.guad.to_review' => array('guardReview'), + 'workflow.blogpost.guard.to_review' => array('guardReview'), ); } } -With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you could easily enable logging:: +With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you +could easily enable logging:: $logger = new PSR3Logger(); $subscriber = new AuditTrailListener($logger); @@ -115,8 +125,9 @@ With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you could Dumper ------ -To help you debug you could dump a representation of your workflow with the use of a ``DumperInterface``. Use the -``GraphvizDumper`` to create a PNG image of the workflow defined above:: +To help you debug you could dump a representation of your workflow with +the use of a ``DumperInterface``. Use the ``GraphvizDumper`` to create a +PNG image of the workflow defined above:: // dump-graph.php $dumper = new GraphvizDumper(); @@ -131,5 +142,9 @@ 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 + more about it on http://www.graphviz.org. .. _Packagist: https://packagist.org/packages/symfony/workflow diff --git a/workflow.rst b/workflow.rst new file mode 100644 index 00000000000..38ee435407a --- /dev/null +++ b/workflow.rst @@ -0,0 +1,8 @@ +Workflow +-------- + +.. toctree:: + :maxdepth: 1 + :glob: + + workflow/* From 196baf9089ea9af03a29a9b4828669b3ad254664 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 10:36:48 +0200 Subject: [PATCH 07/37] Separated docs for component --- components/workflow.rst | 108 ++++----------------------------- workflow/dumping-workflows.rst | 24 ++++++++ workflow/usage.rst | 104 +++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 97 deletions(-) create mode 100644 workflow/dumping-workflows.rst create mode 100644 workflow/usage.rst diff --git a/components/workflow.rst b/components/workflow.rst index f1b0f2ca6b6..8a237166d25 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -21,12 +21,13 @@ You can install the component in 2 different ways: .. include:: /components/require_autoload.rst.inc -Usage ------ +Creating a Workflow +------------------- -The workflow component gives you an object oriented way to work with state -machines. A state machine lets you define *places* and *transitions*. -A transition describes the action to get from one place to another. +The workflow component gives you an object oriented way to define a process +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 +the action to get from one place to another. .. image:: /_images/components/workflow/states_transitions.png @@ -44,7 +45,9 @@ like this:: use Symfony\Component\Workflow\MarkingStore\ScalarMarkingStore; $states = ['draft', 'review', 'rejected', 'published']; - $transitions[] = new Transition('to_review', ['draft', 'rejected'], 'review'); + + // Define a transaction with a name, where to go from and where to go to + $transitions[] = new Transition('to_review', 'draft', 'review'); $transitions[] = new Transition('publish', 'review', 'published'); $transitions[] = new Transition('reject', 'review', 'rejected'); @@ -55,96 +58,7 @@ like this:: $workflow = new Workflow($definition, $marking); The ``Workflow`` can now help you to decide what actions that are allowed -on a blog post. - -.. code-block:: php - - // ... - $post = new \stdClass(); - $post->currentState = null; - $workflow->can($post, 'publish'); // False - $workflow->can($post, 'to_draft'); // 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 -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":: - - $marking = new ScalarMarkingStore('currentState'); - $workflow = new Workflow($definition, $marking, $dispatcher, 'blogpost'); - -.. code-block:: php - - class BlogPostReviewListener implements EventSubscriberInterface - { - public function guardReview(GuardEvent $event) - { - $post = $event->getSubject(); - $title = $post->getTitle(); - - 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'), - ); - } - } - -With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you -could easily enable logging:: - - $logger = new PSR3Logger(); - $subscriber = new AuditTrailListener($logger); - $dispatcher->addSubscriber($subscriber); - -Dumper ------- - -To help you debug you could dump a representation of your workflow with -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 - - $ 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 - more about it on http://www.graphviz.org. +on a blog post depending on what *place* it is in. This will keep your domain +logic in one place and not spread all over your application. .. _Packagist: https://packagist.org/packages/symfony/workflow diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst new file mode 100644 index 00000000000..84f4489f907 --- /dev/null +++ b/workflow/dumping-workflows.rst @@ -0,0 +1,24 @@ +How to Dump Workflows +===================== + +To help you debug you could dump a representation of your workflow with +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 + + $ 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 + more about it on http://www.graphviz.org. diff --git a/workflow/usage.rst b/workflow/usage.rst new file mode 100644 index 00000000000..ddf54b3ccb5 --- /dev/null +++ b/workflow/usage.rst @@ -0,0 +1,104 @@ +.. index:: + single: Workflow; Usage + +How to Use the Workflow +======================= + +The workflow component gives you an object oriented way to work with state +machines. A state machine lets you define *places* and *transitions*. +A transition 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`. + +Consider the following example for a blog post. A post can have 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']; + $transitions[] = new Transition('to_review', ['draft', 'rejected'], '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 +on a blog post. + +.. code-block:: php + + // ... + $post = new \stdClass(); + $post->currentState = null; + $workflow->can($post, 'publish'); // False + $workflow->can($post, 'to_draft'); // 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 +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":: + + $marking = new ScalarMarkingStore('currentState'); + $workflow = new Workflow($definition, $marking, $dispatcher, 'blogpost'); + +.. code-block:: php + + class BlogPostReviewListener implements EventSubscriberInterface + { + public function guardReview(GuardEvent $event) + { + $post = $event->getSubject(); + $title = $post->getTitle(); + + 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'), + ); + } + } + +With help from the ``EventDispatcher`` and the ``AuditTrailListener`` you +could easily enable logging:: + + $logger = new PSR3Logger(); + $subscriber = new AuditTrailListener($logger); + $dispatcher->addSubscriber($subscriber); + From 763950d37c1e1dade13e736f76fe2ee61f26abc9 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 11:25:20 +0200 Subject: [PATCH 08/37] Updating docs to using symfony services --- workflow/usage.rst | 83 +++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/workflow/usage.rst b/workflow/usage.rst index ddf54b3ccb5..a74e48470d3 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -4,9 +4,14 @@ How to Use the Workflow ======================= -The workflow component gives you an object oriented way to work with state -machines. A state machine lets you define *places* and *transitions*. -A transition describes the action to get from one place to another. +Using the workflow component will help you to keep your domain logic as +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 @@ -16,32 +21,53 @@ instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreIn Consider the following example for a blog post. A post can have 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']; - $transitions[] = new Transition('to_review', ['draft', 'rejected'], '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); +like this: + +.. code-block: yaml + + framework: + workflows: + blog_publishing: + marking_store: + type: scalar # or 'property_accessor' + 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 + +.. code-block: php + + class BlogPost + { + // This property is used by the marking store + public $currentPlace; + public $title; + public $content + } -The ``Workflow`` can now help you to decide what actions that are allowed -on a blog post. +With this workflow named ``blog_publishing`` you can get help to decide +what actions that are allowed on a blog post. .. code-block:: php - // ... - $post = new \stdClass(); - $post->currentState = null; + $post = new BlogPost(); + + $workflow = $this->get('workflow.blog_publishing'); $workflow->can($post, 'publish'); // False $workflow->can($post, 'to_draft'); // True @@ -69,17 +95,12 @@ events are dispatched: See example to make sure no blog post without title is moved to "review":: - $marking = new ScalarMarkingStore('currentState'); - $workflow = new Workflow($definition, $marking, $dispatcher, 'blogpost'); - -.. code-block:: php - class BlogPostReviewListener implements EventSubscriberInterface { public function guardReview(GuardEvent $event) { $post = $event->getSubject(); - $title = $post->getTitle(); + $title = $post->title; if (empty($title)) { // Posts with no title should not be allowed From cead2f7a63071278e1382b35a52bb193b7b3676e Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 11:38:08 +0200 Subject: [PATCH 09/37] Added docs about registry --- components/workflow.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/components/workflow.rst b/components/workflow.rst index 8a237166d25..df950a122df 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -61,4 +61,24 @@ The ``Workflow`` can now help you to decide what actions that are allowed on a blog post depending on what *place* it is in. This will keep your domain logic in one place and not spread all over your application. +When you start defining multiple workflows you should consider putting them +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(); + $workflow = $registry->get($post); + + .. _Packagist: https://packagist.org/packages/symfony/workflow From e0089c56345e2ea45a310e0bf9624949c4ef9b82 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 11:38:14 +0200 Subject: [PATCH 10/37] Added placeholders --- workflow/usage.rst | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/workflow/usage.rst b/workflow/usage.rst index a74e48470d3..56e6ed87ab7 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -65,9 +65,9 @@ what actions that are allowed on a blog post. .. code-block:: php - $post = new BlogPost(); + $post = new \BlogPost(); - $workflow = $this->get('workflow.blog_publishing'); + $workflow = $this->container->get('workflow.blog_publishing'); $workflow->can($post, 'publish'); // False $workflow->can($post, 'to_draft'); // True @@ -123,3 +123,16 @@ could easily enable logging:: $subscriber = new AuditTrailListener($logger); $dispatcher->addSubscriber($subscriber); +Usage in Twig +------------- + +[Show example of twig usage] + +[Maybe add a controll panel with buttons and if statements for publish, draft and reject] + + +Workflows as State Machines +--------------------------- + +Discuss how you can (or can't) use the workflow component as a state machine. + From 48de43d7e6e9af7123e8d463297893985117fc44 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 14:31:32 +0200 Subject: [PATCH 11/37] Added note about state machines --- components/workflow.rst | 8 +++++ workflow/dumping-workflows.rst | 7 +++- workflow/state-machines.rst | 59 ++++++++++++++++++++++++++++++++++ workflow/usage.rst | 16 ++++----- 4 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 workflow/state-machines.rst diff --git a/components/workflow.rst b/components/workflow.rst index df950a122df..e6a12ca9f1a 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -80,5 +80,13 @@ supports the object you are trying to use it with:: $post = new BlogPost(); $workflow = $registry->get($post); +Learn more +---------- + +.. toctree:: + :maxdepth: 1 + :glob: + + /workflow .. _Packagist: https://packagist.org/packages/symfony/workflow diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index 84f4489f907..7795f907bd4 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -1,3 +1,6 @@ +.. index:: + single: Workflow; Dumping Workflows + How to Dump Workflows ===================== @@ -21,4 +24,6 @@ The result will look like this: .. note:: The ``dot`` command is a part of Graphviz. You can download it and read - more about it on http://www.graphviz.org. + more about it on `Graphviz.org`_. + +.. Graphviz.org: http://www.graphviz.org diff --git a/workflow/state-machines.rst b/workflow/state-machines.rst new file mode 100644 index 00000000000..8b734d488f7 --- /dev/null +++ b/workflow/state-machines.rst @@ -0,0 +1,59 @@ +.. 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. + + +.. code-block: yaml + + framework: + workflows: + blog_publishing: + marking_store: + 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 + + $workflow = $this->container->get('workflow.blog_publishing'); + $post = new \BlogPost(); + + $post->state = 'draft'; + $workflow->can($post, 'to_review'); // True + + $post->state = 'rejected'; + $workflow->can($post, 'to_review'); // True + + + + + +.. Petri net: https://en.wikipedia.org/wiki/Petri_net diff --git a/workflow/usage.rst b/workflow/usage.rst index 56e6ed87ab7..0d94e1ae520 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -29,7 +29,7 @@ like this: workflows: blog_publishing: marking_store: - type: scalar # or 'property_accessor' + type: property_accessor # 'scalar' or 'state_machine' arguments: - 'currentPlace' supports: @@ -60,6 +60,11 @@ like this: public $content } +.. note:: + +The marking store type could be "property_accessor", "scalar" or "state_machine". +The latter makes your workflow behave as a state machine. + With this workflow named ``blog_publishing`` you can get help to decide what actions that are allowed on a blog post. @@ -69,7 +74,7 @@ what actions that are allowed on a blog post. $workflow = $this->container->get('workflow.blog_publishing'); $workflow->can($post, 'publish'); // False - $workflow->can($post, 'to_draft'); // True + $workflow->can($post, 'to_review'); // True // Update the currentState on the post try { @@ -129,10 +134,3 @@ Usage in Twig [Show example of twig usage] [Maybe add a controll panel with buttons and if statements for publish, draft and reject] - - -Workflows as State Machines ---------------------------- - -Discuss how you can (or can't) use the workflow component as a state machine. - From 805b2376952a62bc92c00135bf06061b13f3362f Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 14:36:45 +0200 Subject: [PATCH 12/37] Added example with twig --- workflow/usage.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/workflow/usage.rst b/workflow/usage.rst index 0d94e1ae520..c815f5773d0 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -131,6 +131,20 @@ could easily enable logging:: Usage in Twig ------------- -[Show example of twig usage] +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 +edit page. The links below will only be displayed when the action is allowed: + +.. code-block:: twig + +

Actions

+ {% if workflow_can(post, 'publish') %} + Publish article + {% endif %} + {% if workflow_can(post, 'to_review') %} + Submit to review + {% endif %} + {% if workflow_can(post, 'reject') %} + Reject article + {% endif %} -[Maybe add a controll panel with buttons and if statements for publish, draft and reject] From f57ec14f9e75e22f00ab846c64d98c82b005db63 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 14:38:34 +0200 Subject: [PATCH 13/37] Show Twig function workflow_transitions --- workflow/usage.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/workflow/usage.rst b/workflow/usage.rst index c815f5773d0..57249760ac8 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -148,3 +148,9 @@ edit page. The links below will only be displayed when the action is allowed: Reject article {% endif %} + {# Or loop through the enabled transistions #} + {% for transition in workflow_transitions(article) %} + {{ transition.name }} + {% else %} + No actions available. + {% endfor %} From 03925ff17e4c65ab2ed8a23f352746c4cd01e8c8 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 16 Aug 2016 14:44:13 +0200 Subject: [PATCH 14/37] syntax fix --- workflow/dumping-workflows.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index 7795f907bd4..edc1103ae7d 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -24,6 +24,6 @@ The result will look like this: .. note:: The ``dot`` command is a part of Graphviz. You can download it and read - more about it on `Graphviz.org`_. + more about it on `Graphviz.org`_:. .. Graphviz.org: http://www.graphviz.org From e6bdee6f0b86343b078f117695d4e1a58ba3c0fb Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 00:07:26 +0100 Subject: [PATCH 15/37] Some syntax fixes and a better "why do we need this" --- workflow.rst | 9 +++++ workflow/dumping-workflows.rst | 5 +-- workflow/state-machines.rst | 60 ++++++++++++++++++---------------- workflow/usage.rst | 60 ++++++++++++++++++---------------- 4 files changed, 75 insertions(+), 59 deletions(-) diff --git a/workflow.rst b/workflow.rst index 38ee435407a..6c86e6df743 100644 --- a/workflow.rst +++ b/workflow.rst @@ -1,6 +1,15 @@ 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 +a user submitts a series of different forms to complete a task. Such process 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. + .. toctree:: :maxdepth: 1 :glob: diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index edc1103ae7d..0a42934a25a 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -24,6 +24,7 @@ The result will look like this: .. note:: The ``dot`` command is a part of Graphviz. You can download it and read - more about it on `Graphviz.org`_:. + more about it on `Graphviz.org`_. -.. Graphviz.org: http://www.graphviz.org + +.. _Graphviz.org: http://www.graphviz.org diff --git a/workflow/state-machines.rst b/workflow/state-machines.rst index 8b734d488f7..28417b3ec59 100644 --- a/workflow/state-machines.rst +++ b/workflow/state-machines.rst @@ -8,33 +8,35 @@ 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. - - -.. code-block: yaml - - framework: - workflows: - blog_publishing: - marking_store: - 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 +commonly have cyclic path in the definition graph but it is common for a state +machine. + +.. configuration-block:: + + .. code-block:: yaml + + framework: + 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** @@ -43,7 +45,7 @@ type ``scalar`` the object had to be in **both** places. .. code-block:: php - $workflow = $this->container->get('workflow.blog_publishing'); + $workflow = $this->container->get('state_machine.blog_publishing'); $post = new \BlogPost(); $post->state = 'draft'; @@ -56,4 +58,4 @@ type ``scalar`` the object had to be in **both** places. -.. Petri net: https://en.wikipedia.org/wiki/Petri_net +.. _Petri net: https://en.wikipedia.org/wiki/Petri_net diff --git a/workflow/usage.rst b/workflow/usage.rst index 57249760ac8..7b2a2d1f8f7 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -23,32 +23,35 @@ Consider the following example for a blog post. A post can have places: 'draft', 'review', 'rejected', 'published'. You can define the workflow like this: -.. code-block: yaml - - framework: - workflows: - blog_publishing: - marking_store: - type: property_accessor # 'scalar' or 'state_machine' - 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 +.. configuration-block:: + + .. code-block: yaml + + framework: + workflows: + blog_publishing: + type: 'workflow' # or 'state_machine' + marking_store: + type: 'property_accessor' # or 'scalar' or 'state_machine' + 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 .. code-block: php @@ -62,8 +65,8 @@ like this: .. note:: -The marking store type could be "property_accessor", "scalar" or "state_machine". -The latter makes your workflow behave as a state machine. + 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 what actions that are allowed on a blog post. @@ -104,6 +107,7 @@ See example to make sure no blog post without title is moved to "review":: { public function guardReview(GuardEvent $event) { + /** @var Acme\BlogPost $post */ $post = $event->getSubject(); $title = $post->title; From c7464c7fd2a83a670eb51de8de4377d3ac53ec0b Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 00:30:21 +0100 Subject: [PATCH 16/37] typos --- components/workflow.rst | 2 +- workflow/usage.rst | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/workflow.rst b/components/workflow.rst index e6a12ca9f1a..3c102575797 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -87,6 +87,6 @@ Learn more :maxdepth: 1 :glob: - /workflow + /workflow/* .. _Packagist: https://packagist.org/packages/symfony/workflow diff --git a/workflow/usage.rst b/workflow/usage.rst index 7b2a2d1f8f7..55e3aea7210 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -25,7 +25,7 @@ like this: .. configuration-block:: - .. code-block: yaml + .. code-block:: yaml framework: workflows: @@ -53,6 +53,7 @@ like this: from: review to: rejected + .. code-block: php class BlogPost From 83d26c1e729540b21bad05e67bd0e02e17ec7d61 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 00:34:06 +0100 Subject: [PATCH 17/37] toctree fix --- components/workflow.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/components/workflow.rst b/components/workflow.rst index 3c102575797..9058cf47654 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -87,6 +87,7 @@ Learn more :maxdepth: 1 :glob: + /workflow /workflow/* .. _Packagist: https://packagist.org/packages/symfony/workflow From 6e7a35f65353a7252d957b400051283d4a7503ec Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 17:30:27 +0100 Subject: [PATCH 18/37] Fixed typos and comments --- components/workflow.rst | 23 ++++++++++++----------- workflow.rst | 6 +++--- workflow/dumping-workflows.rst | 2 +- workflow/state-machines.rst | 8 +------- workflow/usage.rst | 4 ++-- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/components/workflow.rst b/components/workflow.rst index 9058cf47654..3e83218dd52 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -26,18 +26,18 @@ Creating a Workflow The workflow component gives you an object oriented way to define a process 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 +process is called a *place*. You do also define *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`. +a ``Definition`` and a way to write the states to the objects (i.e. an +instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`). -Consider the following example for a blog post. A post can have places: -'draft', 'review', 'rejected', 'published'. You can define the workflow -like this:: +Consider the following example for a blog post. A post can have one of a number +of predefined statuses (`draft`, `review`, `rejected`, `published`). In a workflow, +these statuses are called **places**. You can define the workflow like this:: use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Transition; @@ -46,7 +46,7 @@ like this:: $states = ['draft', 'review', 'rejected', 'published']; - // Define a transaction with a name, where to go from and where to go to + // 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'); @@ -57,13 +57,14 @@ like this:: $marking = new ScalarMarkingStore('currentState'); $workflow = new Workflow($definition, $marking); -The ``Workflow`` can now help you to decide what actions that are allowed +The ``Workflow`` can now help you to decide what actions are allowed on a blog post depending on what *place* it is in. This will keep your domain logic in one place and not spread all over your application. -When you start defining multiple workflows you should consider putting them -in a ``Registry``. A registry will also help you to decide if a workflow -supports the object you are trying to use it with:: +When you define multiple workflows you should consider using a ``Registry``, +which is an object that stores and provides access to different workflows. +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; diff --git a/workflow.rst b/workflow.rst index 6c86e6df743..e7c90aef947 100644 --- a/workflow.rst +++ b/workflow.rst @@ -2,13 +2,13 @@ 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 -a user submitts a series of different forms to complete a task. Such process are +of how a blog post goes from draft, review and publish. Another example is when +a user submitts a series of different forms to complete a task. 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. +are allowed on the model at each state. .. toctree:: :maxdepth: 1 diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index 0a42934a25a..57a4396e4fd 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -4,7 +4,7 @@ How to Dump Workflows ===================== -To help you debug you could dump a representation of your workflow with +To help you debug your workflows, you can dump a representation of your workflow with the use of a ``DumperInterface``. Use the ``GraphvizDumper`` to create a PNG image of the workflow defined above:: diff --git a/workflow/state-machines.rst b/workflow/state-machines.rst index 28417b3ec59..4d79b8f8fc1 100644 --- a/workflow/state-machines.rst +++ b/workflow/state-machines.rst @@ -41,9 +41,7 @@ machine. 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 +type ``scalar`` the object had to be in **both** places. :: $workflow = $this->container->get('state_machine.blog_publishing'); $post = new \BlogPost(); @@ -54,8 +52,4 @@ type ``scalar`` the object had to be in **both** places. $post->state = 'rejected'; $workflow->can($post, 'to_review'); // True - - - - .. _Petri net: https://en.wikipedia.org/wiki/Petri_net diff --git a/workflow/usage.rst b/workflow/usage.rst index 55e3aea7210..9f389042adc 100644 --- a/workflow/usage.rst +++ b/workflow/usage.rst @@ -16,8 +16,8 @@ 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`. +a ``Definition`` and a way to write the states to the objects (i.e. an +instance of a :class:`Symfony\\Component\\Workflow\\MarkingStore\\MarkingStoreInterface`.) Consider the following example for a blog post. A post can have places: 'draft', 'review', 'rejected', 'published'. You can define the workflow From 44154663f791c6ce6e071c4048b152eafba9dd6d Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 18:01:22 +0100 Subject: [PATCH 19/37] Added usage example on the component --- components/workflow.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/workflow.rst b/components/workflow.rst index 3e83218dd52..3bea32cee59 100644 --- a/components/workflow.rst +++ b/components/workflow.rst @@ -77,10 +77,22 @@ are trying to use it with:: $registry->add($blogWorkflow, BlogPost::class); $registry->add($newsletterWorkflow, Newsletter::class); - // ... +Usage +----- + +When you have configured a ``Registry`` with your workflows you may use it as follows:: + $post = new BlogPost(); $workflow = $registry->get($post); + $workflow->can($post, 'publish'); // False + $workflow->can($post, 'to_review'); // True + + $workflow->apply($post, 'to_review'); + $workflow->can($post, 'publish'); // True + $workflow->getEnabledTransitions($post); // ['publish', 'reject'] + + Learn more ---------- From 866b25aff0809dfc919026cdab9a6852ee90b6b3 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 18:07:44 +0100 Subject: [PATCH 20/37] Added example how to dump with Symfony --- workflow/dumping-workflows.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/workflow/dumping-workflows.rst b/workflow/dumping-workflows.rst index 57a4396e4fd..f4e1245a148 100644 --- a/workflow/dumping-workflows.rst +++ b/workflow/dumping-workflows.rst @@ -14,13 +14,21 @@ PNG image of the workflow defined above:: .. code-block:: bash - $ php dump-graph-php > out.dot + $ 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 +If you have configured your workflow with the Symfony framwork you may dump the dot file +with the ``WorkflowDumpCommand``. + +.. code-block:: bash + + $ php bin/console workflow:dump name > out.dot + $ dot -Tpng out.dot -o graph.png + .. note:: The ``dot`` command is a part of Graphviz. You can download it and read From dceebec28cfd2737432028dd0052bf69d073651a Mon Sep 17 00:00:00 2001 From: Tobias Nyholm Date: Tue, 8 Nov 2016 19:00:16 +0100 Subject: [PATCH 21/37] Added examples of workflows --- .../components/workflow/job_application.png | Bin 0 -> 66896 bytes _images/components/workflow/simple.png | Bin 0 -> 9145 bytes workflow.rst | 44 ++++++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 _images/components/workflow/job_application.png create mode 100644 _images/components/workflow/simple.png diff --git a/_images/components/workflow/job_application.png b/_images/components/workflow/job_application.png new file mode 100644 index 0000000000000000000000000000000000000000..d4810fad741710499f0ecc66220c408d1d1d05d2 GIT binary patch literal 66896 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