Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[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. WORKFLOW COMPONENT
    Introduction to the brand new Symfony component

    View full-size slide

  2. WHO AM I
    Sarah Khalil
    Author for OpenClassrooms (PHP / Symfony courses)
    @saro0h

    View full-size slide

  3. LET’S GET TO IT!

    View full-size slide

  4. INTRODUCTION

    View full-size slide

  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

    View full-size slide

  6. THEORY: SOME VOCABULARY

    View full-size slide

  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

    View full-size slide

  8. FEATURES
    Multiple transitions at the same time
    Non deterministic
    Concurrent behavior / Distributed system

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  11. IMPLEMENTATION

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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!

    View full-size slide

  15. WORKFLOW COMPONENT
    IS HERE TO HELP YOU!

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  18. STEP 2: DESCRIBE YOUR WORKFLOW
    Configuration
    Using PHP with the Symfony\Component\Workflow\Definition
    option 1
    option 2

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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.

    View full-size slide

  24. You need to install Graphviz ;)

    View full-size slide

  25. STEP 3: CHECK IT
    $ php workflow.php | dot -Tpng > workflow.png
    use Symfony\Component\Workflow\Dumper\GraphvizDumper;
    echo (new GraphvizDumper())->dump($definition);

    View full-size slide

  26. STEP 4: IN YOUR CODE
    What type of object needs to be handled? 1/4
    The product entity

    View full-size slide

  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;
    }
    }

    View full-size slide

  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;
    }
    }

    View full-size slide

  29. A service is created for you when using the configuration
    workflow.[the_name_of_your_workflow]
    The generated name will be:

    View full-size slide

  30. Let’s see what’s inside!

    View full-size slide

  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.

    View full-size slide

  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'));
    //…
    }

    View full-size slide

  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

    View full-size slide

  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)
    {
    //…
    }

    View full-size slide

  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;
    }

    View full-size slide

  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 %}

    View full-size slide

  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()])
    );
    }

    View full-size slide

  38. STEP 4: IN YOUR CODE
    Go from a place to another 4/4
    Apply a transition 2/2


    {% for transition in workflow_transitions(product) %}
    {% if workflow_can(product, transition.name) -%}
    class="btn btn-primary"
    {%- else -%}
    class="btn btn-danger" disabled="disabled"
    {%- endif -%}
    >
    {{ transition.name }}

    {% endfor %}


    View full-size slide

  39. STEP 4: IN YOUR CODE
    Go from a place to another 4/4
    Apply a transition 2/2


    {% for transition in workflow_transitions(product) %}
    {% if workflow_can(product, transition.name) -%}
    class="btn btn-primary"
    {%- else -%}
    class="btn btn-danger" disabled="disabled"
    {%- endif -%}
    >
    {{ transition.name }}

    {% endfor %}


    View full-size slide

  40. MORE FEATURES!

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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;
    }
    }

    View full-size slide

  46. GUARD EVENT
    Workflow::can()
    Workflow::doCan()
    Workflow::guardTransition()
    $dispatcher->dispatch()

    View full-size slide

  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

    View full-size slide

  48. LET’S SEE AN DEMO APP TO REVIEW ALL OF
    THAT

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  51. Merci !
    @catlannister

    View full-size slide