Slide 1

Slide 1 text

WORKFLOW COMPONENT Introduction to the brand new Symfony component

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

LET’S GET TO IT!

Slide 4

Slide 4 text

INTRODUCTION

Slide 5

Slide 5 text

WHAT IS THIS ABOUT? Being able to validate each step modification of an object Quite different from a state machine Implementation of Workflow net

Slide 6

Slide 6 text

THEORY: SOME VOCABULARY

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

IMPLEMENTATION

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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!

Slide 15

Slide 15 text

WORKFLOW COMPONENT IS HERE TO HELP YOU!

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

STEP 1: DRAW IT! in_stock put_in_cart in_cart in_wish_list check_out send_to_friend ready_to_purchase pay paid

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

You need to install Graphviz ;)

Slide 25

Slide 25 text

STEP 3: CHECK IT $ php workflow.php | dot -Tpng > workflow.png dump($definition);

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Let’s see what’s inside!

Slide 31

Slide 31 text

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.

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

STEP 4: IN YOUR CODE Go from a place to another 4/4 Apply a transition 2/2
{% for transition in workflow_transitions(product) %} {{ transition.name }} {% endfor %}

Slide 39

Slide 39 text

STEP 4: IN YOUR CODE Go from a place to another 4/4 Apply a transition 2/2
{% for transition in workflow_transitions(product) %} {{ transition.name }} {% endfor %}

Slide 40

Slide 40 text

MORE FEATURES!

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Merci ! @catlannister