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 Slide

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

    View Slide

  3. LET’S GET TO IT!

    View Slide

  4. INTRODUCTION

    View 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 Slide

  6. THEORY: SOME VOCABULARY

    View 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 Slide

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

    View 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 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 Slide

  11. IMPLEMENTATION

    View Slide

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

    View 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 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 Slide

  15. WORKFLOW COMPONENT
    IS HERE TO HELP YOU!

    View 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 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 Slide

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

    View 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 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 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 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 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 Slide

  24. You need to install Graphviz ;)

    View 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 Slide

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

    View 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 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 Slide

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

    View Slide

  30. Let’s see what’s inside!

    View 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 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 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 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 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 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 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 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 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 Slide

  40. MORE FEATURES!

    View 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 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 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 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 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 Slide

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

    View 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 Slide

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

    View Slide

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

    View 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 Slide

  51. Merci !
    @catlannister

    View Slide