$30 off During Our Annual Pro Sale. View Details »

[OpenClassrooms] Workflow Component

Sarah KHALIL
September 20, 2016

[OpenClassrooms] Workflow Component

Let's see what's is this about that awesome new component !

Sarah KHALIL

September 20, 2016
Tweet

More Decks by Sarah KHALIL

Other Decks in Programming

Transcript

  1. WHAT IS THIS ABOUT? Being able to validate each step

    modification of an object Quite different from a state machine Implementation of Workflow net
  2. FINITE STATE MACHINE State description of configuration waiting to execute

    a transition (noun) Transition action to execute if a condition is fulfilled (verb) WORKFLOW NET’ state 1 state 2 transition Place input/output of a transition contains x tokens Transition action to fire to go from a place to another (verb) Marking / Tokens representation of the position of all tokens over the network. place a transition1 transition 2 transition 3 place b place s place e place d transition4 place f transition1 place a
  3. TYPES OF WORKFLOW YOU SHOULD ENCOUNTER place 1 transition 1

    place 2 transition 2 place 3 1. Straight (state machine look alike) 1I. Round trip place 1 transition 1 place 2 transition 2 place 3 transition 2 1/2
  4. TYPES OF WORKFLOW YOU SHOULD ENCOUNTER III. OR IV. AND

    2/2 place 1 transition a place 2 transition c place 4 transition b place 3 transition d place 1 transition a place 2 transition c place 6 place 3 transition d place 4 transition e place 5
  5. A USE CASE Let’s say you are working on a

    e-commerce application with Symfony You are in charge of implementing the cart checkout You need to make sure that a product goes through a very precise process through the cart checkout
  6. PROCESS DESCRIBED BY THE MANAGEMENT 1. The product is in

    stock 2. The customer puts the product in a cart 3. Then 2 options • either the customer puts the product in a wishlist, that way, a friend can pay the cart • either the customer is ready to pay the product 4. Then customer pays and that’s it!
  7. HOW TO USE IT? Step 1: Take a pen and

    a paper and draw the workflow of your dreams Step 2: Declare your workflow Step 3: Dump it to check if everything’s ok Step 4: Write some code to deal with the process and display things
  8. STEP 2: DESCRIBE YOUR WORKFLOW Configuration Using PHP with the

    Symfony\Component\Workflow\Definition option 1 option 2
  9. option 1 $this->definition = new Definition(); $this->definition->addPlaces([ 'in_stock', 'in_cart', 'in_wish_list',

    'ready_to_purchase', 'paid' ]); $this->definition->addTransition([ 'put_in_cart', // name ['in_stock'], // input places ['in_cart'], // output places ); $this->definition->addTransition([ 'check_out' ['in_cart'], ['in_wish_list', 'ready_to_purchase'], ]); $this->definition->addTransition([ 'send_to_friend', ['in_wish_list'], ['ready_to_purchase'], ]); $this->definition->addTransition([ 'pay', ['ready_to_purchase'], ['paid'], ]); Describe your workflow
  10. option 1 $this->definition = new Definition(); $this->definition->addPlaces([ 'in_stock', 'in_cart', 'in_wish_list',

    'ready_to_purchase', 'paid' ]); $this->definition->addTransition([ 'put_in_cart', // name ['in_stock'], // input places ['in_cart'], // output places ); $this->definition->addTransition([ 'check_out' ['in_cart'], ['in_wish_list', 'ready_to_purchase'], ]); $this->definition->addTransition([ 'send_to_friend', ['in_wish_list'], ['ready_to_purchase'], ]); $this->definition->addTransition([ 'pay', ['ready_to_purchase'], ['paid'], ]); Describe your workflow
  11. option 1 $this->definition = new Definition(); $this->definition->addPlaces([ 'in_stock', 'in_cart', 'in_wish_list',

    'ready_to_purchase', 'paid' ]); $this->definition->addTransition([ 'put_in_cart', // name ['in_stock'], // input places ['in_cart'], // output places ); $this->definition->addTransition([ 'check_out' ['in_cart'], ['in_wish_list', 'ready_to_purchase'], ]); $this->definition->addTransition([ 'send_to_friend', ['in_wish_list'], ['ready_to_purchase'], ]); $this->definition->addTransition([ 'pay', ['ready_to_purchase'], ['paid'], ]); Describe your workflow
  12. option 2 framework: workflows: product: marking_store: type: property_accessor supports: -

    AppBundle\Entity\Product places: - in_stock - in_cart - in_wish_list - ready_to_purchase - paid transitions: put_in_cart: from: in_stock to: in_cart check_out: from: in_cart to: - in_wish_list - ready_to_purchase send_to_friend: from: in_wish_list to: ready_to_purchase pay: from: ready_to_purchase to: paid Only available in Symfony fullstack Describe your workflow
  13. STEP 3: CHECK IT bin/console workflow:dump product | dot -Tpng

    > workflow.png /!\ This command is only enabled if there is at least one workflow declared.
  14. STEP 3: CHECK IT $ php workflow.php | dot -Tpng

    > workflow.png <?php use Symfony\Component\Workflow\Dumper\GraphvizDumper; echo (new GraphvizDumper())->dump($definition);
  15. STEP 4: IN YOUR CODE What type of object needs

    to be handled? 1/4 The product entity
  16. use Doctrine\ORM\Mapping as ORM; /** * @ORM\Table(name="product") * @ORM\Entity() */

    class Product { /** * @ORM\Column(type="integer") * @ORM\Id() * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** @ORM\Column(type="json_array", nullable=true) */ private $marking; public function getId() { return $this->id; } public function getMarking() { return $this->marking; } public function setMarking($marking) { $this->marking = $marking; } }
  17. use Doctrine\ORM\Mapping as ORM; /** * @ORM\Table(name="product") * @ORM\Entity() */

    class Product { /** * @ORM\Column(type="integer") * @ORM\Id() * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** @ORM\Column(type="json_array", nullable=true) */ private $marking; public function getId() { return $this->id; } public function getMarking() { return $this->marking; } public function setMarking($marking) { $this->marking = $marking; } }
  18. A service is created for you when using the configuration

    workflow.[the_name_of_your_workflow] The generated name will be:
  19. SYMFONY\COMPONENT\WORKFLOW\WORKFLOW /** * Returns the object's Marking. * * @param

    object $subject A subject * * @return Marking The Marking * * @throws LogicException */ public function getMarking($subject) { // … } Also sets the initial marking.
  20. SYMFONY\COMPONENT\WORKFLOW\WORKFLOW /** * Returns true if the transition is enabled.

    * * @param object $subject A subject * @param string $transitionName A transition * * @return bool true if the transition is enabled * * @throws LogicException If the transition does not exist */ public function can($subject, $transitionName) { //… } /** * @Route("/product/{id}", name="show_product") */ public function showAction(Product $product) { dump($this->get('workflow.product')->can($product, 'put_in_cart')); //… }
  21. SYMFONY\COMPONENT\WORKFLOW\WORKFLOW /** * Returns true if the transition is enabled.

    * * @param object $subject A subject * @param string $transitionName A transition * * @return bool true if the transition is enabled * * @throws LogicException If the transition does not exist */ public function can($subject, $transitionName) { //… } /** * @Route("/product/{id}", name="show_product") */ public function showAction(Product $product) { dump($this->get('workflow.product')->can($product, 'put_in_cart')); //… } True False or according to the current marking
  22. SYMFONY\COMPONENT\WORKFLOW\WORKFLOW /** * Fires a transition. * * @param object

    $subject A subject * @param string $transitionName A transition * * @return Marking The new Marking * * @throws LogicException If the transition is not applicable * @throws LogicException If the transition does not exist */ public function apply($subject, $transitionName) { //… }
  23. SYMFONY\COMPONENT\WORKFLOW\WORKFLOW /** * Returns all enabled transitions. * * @param

    object $subject A subject * * @return Transition[] All enabled transitions */ public function getEnabledTransitions($subject) { $enabled = array(); $marking = $this->getMarking($subject); foreach ($this->definition->getTransitions() as $transition) { if ($this->doCan($subject, $marking, $transition)) { $enabled[$transition->getName()] = $transition; } } return $enabled; }
  24. STEP 4: IN YOUR CODE How can I display things?

    3/4 The current marking {{ product.marking|keys|join(', ')|default('[]')|raw }} Available transitions {% for transition in workflow_transitions(product) %} {{ dump(transition) }} {% else %} No more transition available for the product object. {% endfor %}
  25. STEP 4: IN YOUR CODE Go from a place to

    another 4/4 Apply a transition 1/2 /** * @Route("/apply-transition/{id}", name="article_apply_transition") */ public function applyTransitionAction(Request $request, Product $product) { try { $this->get('workflow.product') ->apply($product, $request->request->get('transition')); $this->get('doctrine')->getManager()->flush(); } catch (ExceptionInterface $e) { $this->get('session')->getFlashBag()->add('danger', $e->getMessage()); } return $this->redirect( $this->generateUrl('show_product', ['id' => $product->getId()]) ); }
  26. STEP 4: IN YOUR CODE Go from a place to

    another 4/4 Apply a transition 2/2 <form action="{{ url('article_apply_transition', {id: product.id}) }}" method="post"> <div class="btn-group-vertical" role="group"> {% for transition in workflow_transitions(product) %} <button type="submit" name="transition" value="{{ transition.name }}" {% if workflow_can(product, transition.name) -%} class="btn btn-primary" {%- else -%} class="btn btn-danger" disabled="disabled" {%- endif -%} > {{ transition.name }} </button> {% endfor %} </div> </form>
  27. STEP 4: IN YOUR CODE Go from a place to

    another 4/4 Apply a transition 2/2 <form action="{{ url('article_apply_transition', {id: product.id}) }}" method="post"> <div class="btn-group-vertical" role="group"> {% for transition in workflow_transitions(product) %} <button type="submit" name="transition" value="{{ transition.name }}" {% if workflow_can(product, transition.name) -%} class="btn btn-primary" {%- else -%} class="btn btn-danger" disabled="disabled" {%- endif -%} > {{ transition.name }} </button> {% endfor %} </div> </form>
  28. EVENTS 3 nice events workflow.enter workflow.leave workflow.transition Take a look

    at that event subscriber: Symfony\Component\Workflow\EventListener\AuditTrailListener
  29. EVENTS 3 nice events workflow.enter workflow.leave workflow.transition Take a look

    at that event subscriber: Symfony\Component\Workflow\EventListener\AuditTrailListener a place is reached
  30. EVENTS 3 nice events workflow.enter workflow.leave workflow.transition Take a look

    at that event subscriber: Symfony\Component\Workflow\EventListener\AuditTrailListener a place is left a place is reached
  31. EVENTS 3 nice events workflow.enter workflow.leave workflow.transition Take a look

    at that event subscriber: Symfony\Component\Workflow\EventListener\AuditTrailListener a place is left a place is reached a transition is fired
  32. GUARD EVENT namespace Symfony\Component\Workflow\Event; class GuardEvent extends Event { private

    $blocked = false; public function isBlocked() { return $this->blocked; } public function setBlocked($blocked) { $this->blocked = (bool) $blocked; } } namespace Symfony\Component\Workflow\Event; class Event extends BaseEvent { private $subject; private $marking; private $transition; /** * Event constructor. * * @param object $subject * @param Marking $marking * @param Transition $transition */ public function __construct($subject, Marking $marking, Transition $transition) { $this->subject = $subject; $this->marking = $marking; $this->transition = $transition; } public function getMarking() { return $this->marking; } public function getSubject() { return $this->subject; } public function getTransition() { return $this->transition; } }
  33. GUARD EVENT Make an event [listener|subscriber] on that event $event->setBlocked($bool)

    according to your logic https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Workflow/Workflow.php#L186