Techblog: Symfony Workflow component (1/3)

Techblog: Symfony Workflow component (1/3)

Techblog: Symfony Workflow component (1/3)

With the release of Symfony 3.2 a new workflow component was added that implemented a workflow net or a state machine. This component helps to separate the business workflow logic from other application logic and can help to visualize the workflow and mark the current state it is in.

This blog post is part 1 of a small series on the workflow component:

Figure 1: Creditcard application workflow

  1. Introduction to the workflow component (this blog post)
  2. Scheduled transitions using a job scheduler, for example to send reminders.
  3. Improve the configuration, testability and visualize the workflow

For this introduction we’ll use a creditcard application usecase as example. The applicant will need to confirm the applications emailaddress, the application needs to be verified and a new creditcard needs to be created.

For the workflow component to work we need to configure a workflow definition, a marking store and a subject. A workflow definition consists of places (states), transitions between the places and guards that conditionally block transitions.

framework:
workflows:
creditcard_application_flow:
type: 'state_machine' # 'workflow' or 'state_machine', defaults to 'workflow'
marking_store:
type: 'single_state' # 'single_state or 'multiple_state'
supports: # the Subject entity. No longer required in Symfony 3.3+
- AppBundle\Entity\CreditcardApplication
places:
- start
- email_confirmed
- application_validated
- card_created
- card_activated
transitions:
confirm_email:
from: start
to: email_confirmed
validate_application:
from: email_confirmed
to: application_validated
create_card:
from: application_validated
to: card_created

 

The marking store

The marking store keeps track of the place where the subject (in this case the creditcard application) is inside the workflow. By default the marking store will manipulate the ‘marking’ property on your subject. If we have a creditcard application $application in the place start and apply the transition confirm_email the marking store would set the $application->markingproperty to email_confirmed.

By default the marking store will manipulate the ‘marking’ property on your subject, but you can change this by supplying an arguments property to the marking_store configuration. In the following example we change the property name to ‘state’.

framework:
workflows:
creditcard_application_flow:
......
marking_store:
type: 'single_state'
arguments: 'state'
.....

The marking store will only manipulate the property, but will not take care of any persistence. If we implemented a service persisting_marking_store with our own implementation of the MarkingStoreInterface we could configure it using the service configuration property.

framework:
workflows:
creditcard_application_flow:
......
marking_store:
service: 'persisting_marking_store'
.....

For an example of a persisting marking store see the OrmPersistentMarkingStore in the GTT workflow-extension-bundle.

 

Multiple-state workflow

When the workflow supports parallel processes the subject can be in multiple places at once and we’ll need to configure the MultipleStateMarkingStore.

framework:
workflows:
creditcard_application_flow:
......
marking_store:
type: 'multiple_state'
.....

Figure 2: Multiple-state workflow

But in our example we’ll use a state machine which always has a single place/state.

The type names for marking stores were changed:
– property_accessor -> multiple_state
– scalar -> single_state
You might still see examples with the old configuration names.

When your workflow type is state_machine multiple_state marking store is valid but doesn’t add any extra functionality as a state machine has only a single state.

 

Initiate workflow

To get a hold of a specific workflow you can use the following service name syntax: $container->get('workflow.' . $workflowName), or when you use a state machine: $container->get('state_machine' . $workflowName). Keep in mind that while the service name differs between a normal workflow and a statemachine the prefix workflow for event names in both cases.

 

Apply workflow transition

You can apply a transition using $workflow->apply('confirm_email', $subject). For our example application we implement a controller that would confirm the emailaddress using a link containing a unique hash.

<?php
.....
class ConfirmEmailController extends Controller {
....
public function confirmEmailAction($id, $hash) {
$form = $this->applicationRepository->findByHash($id, $hash);
try {
$this->workflow->apply($form, 'confirm_application');
} catch(LogicException $e) {
// Cannot apply transition, probably already confirmed
}
....
}
}

 

Transition events

Applying a transition will dispatch the following sequence of events:

  1. Guard the transition
    workflow[.<workflow_name>].guard[.<transition_name>]
    The guard event is used the apply conditions to a transition and can block the transition.
  2. Leave the from place
    workflow[.<workflow_name>].leave[.<from_place_name>]
  3. Transition
    workflow[.<workflow_name>].transition[.<transition_name>]
    This event can be used to apply an action on this transition such as sending an email.
  4. Enter the to place
    workflow[.<workflow_name>].enter[.<to_place_name>]
  5. Entered the to place (Only ≥ Symfony 3.3)
    workflow[.<workflow_name>].entered[.<to_place_name>]
    This is the same as the enter event but now the marking store is updated.
  6. Announce new applicable transitions
    workflow[.<workflow_name>].transition[.<transition_name>]
    Listener to this event if you want to automatically apply a transition.

 

Listen for a transition

Using the generated workflow events we could listen for the confirm_emailtransition event and send an email when the user confirms his creditcard application.

<?php
class ConfirmWorkflowListener implements EventSubscriberInterface {
....
public function onConfirmEmailTransition(Event $event) {
$application = $event->getSubject();
$this->confirmMailService->mailEmailConfirmedNotification($application);
}
public static function getSubscribedEvents() {
return array(
'workflow.creditcard_application_flow.transition.confirm_email' => array('onConfirmEmailTransition'),
);
}
}

 

Guard a workflow transition

To conditionally guard a workflow transition the application can listen for guard events. If any of the guard listeners set $guardEvent->setBlocked(true)the transition will not be available.

In the creditcard example we can require an additional approved credit limit when the user requests a creditcard with a limit above $ 200. We implement a Guard listener that listens for the workflow.creditcard_application_flow.guard.create_card event:

<?php
class CreateCardGuardListener implements EventSubscriberInterface
{
public function guardCreateCard(GuardEvent $event) {
/** @var CardApplication $cardApplication */
$cardApplication = $event->getSubject();
// By default block this transition.
$event->setBlocked(true);
// Only allow this transition if the requested limit is less then $100 or we have an approved credit limit.
if ($cardApplication->getRequestedCreditLimit() < 200 || $cardApplication->getRequestedCreditLimit() <= $cardApplication->getAppprovedCreditLimit()) {
$event->setBlocked(false);
}
}
/**
* Returns the events to which this class has subscribed.
*
* @return array
*/
public static function getSubscribedEvents() {
return [
'workflow.creditcard_application_flow.guard.create_card' => array('guardCreateCard'),
];
}
}

In Symfony 3.3+ you will also be able to define the guards using an expression in the workflow definition:

framework:
workflows:
creditcard_application_flow:
....
transitions:
....
create_card:
guard: "subject->getRequestedCreditLimit() < 200 or subject->getRequestedCreditLimit() <= subject->getAppprovedCreditLimit()"
from: application_validated
to: card_created
.....

 

What’s next?

In part 2 of this serie we’ll have a look at how you can schedule transitions and hook transitions to application events. We’ll build a reminder transtion that ‘ll be applied if the user hasn’t confirmed its creditcard application in 3 days.

Laat een reactie achter