[OpenClassrooms] Workflow Component

34ade09dd3d11004ca8ee4174fd3d6a2?s=47 Sarah KHALIL
September 20, 2016

[OpenClassrooms] Workflow Component

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

34ade09dd3d11004ca8ee4174fd3d6a2?s=128

Sarah KHALIL

September 20, 2016
Tweet

Transcript

  1. WORKFLOW COMPONENT Introduction to the brand new Symfony component

  2. WHO AM I Sarah Khalil Author for OpenClassrooms (PHP /

    Symfony courses) @saro0h
  3. LET’S GET TO IT!

  4. INTRODUCTION

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

    modification of an object Quite different from a state machine Implementation of Workflow net
  6. THEORY: SOME VOCABULARY

  7. 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
  8. FEATURES Multiple transitions at the same time Non deterministic Concurrent

    behavior / Distributed system
  9. 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
  10. 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
  11. IMPLEMENTATION

  12. The component does not implement all the specifications of the

    Workflow Net
  13. 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
  14. 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!
  15. WORKFLOW COMPONENT IS HERE TO HELP YOU!

  16. 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
  17. STEP 1: DRAW IT! in_stock put_in_cart in_cart in_wish_list check_out send_to_friend

    ready_to_purchase pay paid
  18. STEP 2: DESCRIBE YOUR WORKFLOW Configuration Using PHP with the

    Symfony\Component\Workflow\Definition option 1 option 2
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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.
  24. You need to install Graphviz ;)

  25. STEP 3: CHECK IT $ php workflow.php | dot -Tpng

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

    to be handled? 1/4 The product entity
  27. 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; } }
  28. 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; } }
  29. A service is created for you when using the configuration

    workflow.[the_name_of_your_workflow] The generated name will be:
  30. Let’s see what’s inside!

  31. 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.
  32. 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')); //… }
  33. 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
  34. 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) { //… }
  35. 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; }
  36. 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 %}
  37. 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()]) ); }
  38. 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>
  39. 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>
  40. MORE FEATURES!

  41. EVENTS 3 nice events workflow.enter workflow.leave workflow.transition Take a look

    at that event subscriber: Symfony\Component\Workflow\EventListener\AuditTrailListener
  42. 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
  43. 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
  44. 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
  45. 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; } }
  46. GUARD EVENT Workflow::can() Workflow::doCan() Workflow::guardTransition() $dispatcher->dispatch()

  47. 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
  48. LET’S SEE AN DEMO APP TO REVIEW ALL OF THAT

  49. https://github.com/saro0h/workflow-app-example

  50. FOLLOW US ON THE INTER TUBES @openclassrooms www.facebook.com/openclassrooms www.instagram.com/openclassrooms plus.google.com/+OpenClassrooms

    www.youtube.com/user/TheOpenClassrooms
  51. Merci ! @catlannister