We got the task to make an order API, from open order, to delivered, with payments in between and after. So there are naturally a lot of states, and a lot of transitions where we needed to calculate the prices correctly and handle credit card transfers. Keeping track of all of this, and when we need to do what, ensuring that an order is always up to date, and that it has the data it needs, and that we send good error messages when a user can not do an action, was a challenge for us until we discovered the workflow component.
This is a real happy use case story where I will show you how we did this, and how much more straightforward it was for us to build an otherwise complex system using the workflow component.
85. @michellesanver #phpfwdays
/**
* @return array Hashmap of Symfony events this handler cares about
*/
public static function getSubscribedEvents()
{
return [
// Guard events (Validate whether the transition is allowed at all)
'workflow.order.guard' => 'guardGeneralOrderWorkflow',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CHANGE_REGION) => 'guardChangeRegion',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CONFIRM_ORDER) => 'guardConfirmOrder',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_PICKER) => 'guardAssignPicker',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_CUSTOMER) => 'guardAssignCustomer',
// Enter events (The object is about to enter a new place)
sprintf('workflow.order.enter.%s', Order::STATE_CONFIRMED) => 'enterConfirmed',
sprintf('workflow.order.enter.%s', Order::STATE_DELIVERED) => 'enterDelivered',
// Entered events (The object entered a new place)
sprintf('workflow.order.entered.%s', Order::STATE_PICKED_UP) => 'enteredPickedUp',
// The object has completed this transition.
'workflow.order.completed' => 'onCompleted',
];
}
86. @michellesanver #phpfwdays
/**
* @return array Hashmap of Symfony events this handler cares about
*/
public static function getSubscribedEvents()
{
return [
// Guard events (Validate whether the transition is allowed at all)
'workflow.order.guard' => 'guardGeneralOrderWorkflow',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CHANGE_REGION) => 'guardChangeRegion',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CONFIRM_ORDER) => 'guardConfirmOrder',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_PICKER) => 'guardAssignPicker',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_CUSTOMER) => 'guardAssignCustomer',
// Enter events (The object is about to enter a new place)
sprintf('workflow.order.enter.%s', Order::STATE_CONFIRMED) => 'enterConfirmed',
sprintf('workflow.order.enter.%s', Order::STATE_DELIVERED) => 'enterDelivered',
// Entered events (The object entered a new place)
sprintf('workflow.order.entered.%s', Order::STATE_PICKED_UP) => 'enteredPickedUp',
// The object has completed this transition.
'workflow.order.completed' => 'onCompleted',
];
}
87. @michellesanver #phpfwdays
/**
* @return array Hashmap of Symfony events this handler cares about
*/
public static function getSubscribedEvents()
{
return [
// Guard events (Validate whether the transition is allowed at all)
'workflow.order.guard' => 'guardGeneralOrderWorkflow',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CHANGE_REGION) => 'guardChangeRegion',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CONFIRM_ORDER) => 'guardConfirmOrder',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_PICKER) => 'guardAssignPicker',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_CUSTOMER) => 'guardAssignCustomer',
// Enter events (The object is about to enter a new place)
sprintf('workflow.order.enter.%s', Order::STATE_CONFIRMED) => 'enterConfirmed',
sprintf('workflow.order.enter.%s', Order::STATE_DELIVERED) => 'enterDelivered',
// Entered events (The object entered a new place)
sprintf('workflow.order.entered.%s', Order::STATE_PICKED_UP) => 'enteredPickedUp',
// The object has completed this transition.
'workflow.order.completed' => 'onCompleted',
];
}
88. @michellesanver #phpfwdays
/**
* @return array Hashmap of Symfony events this handler cares about
*/
public static function getSubscribedEvents()
{
return [
// Guard events (Validate whether the transition is allowed at all)
'workflow.order.guard' => 'guardGeneralOrderWorkflow',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CHANGE_REGION) => 'guardChangeRegion',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CONFIRM_ORDER) => 'guardConfirmOrder',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_PICKER) => 'guardAssignPicker',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_CUSTOMER) => 'guardAssignCustomer',
// Enter events (The object is about to enter a new place)
sprintf('workflow.order.enter.%s', Order::STATE_CONFIRMED) => 'enterConfirmed',
sprintf('workflow.order.enter.%s', Order::STATE_DELIVERED) => 'enterDelivered',
// Entered events (The object entered a new place)
sprintf('workflow.order.entered.%s', Order::STATE_PICKED_UP) => 'enteredPickedUp',
// The object has completed this transition.
'workflow.order.completed' => 'onCompleted',
];
}
89. @michellesanver #phpfwdays
/**
* @return array Hashmap of Symfony events this handler cares about
*/
public static function getSubscribedEvents()
{
return [
// Guard events (Validate whether the transition is allowed at all)
'workflow.order.guard' => 'guardGeneralOrderWorkflow',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CHANGE_REGION) => 'guardChangeRegion',
sprintf('workflow.order.guard.%s', Order::TRANSITION_CONFIRM_ORDER) => 'guardConfirmOrder',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_PICKER) => 'guardAssignPicker',
sprintf('workflow.order.guard.%s', Order::TRANSITION_ASSIGN_CUSTOMER) => 'guardAssignCustomer',
// Enter events (The object is about to enter a new place)
sprintf('workflow.order.enter.%s', Order::STATE_CONFIRMED) => 'enterConfirmed',
sprintf('workflow.order.enter.%s', Order::STATE_DELIVERED) => 'enterDelivered',
// Entered events (The object entered a new place)
sprintf('workflow.order.entered.%s', Order::STATE_PICKED_UP) => 'enteredPickedUp',
// The object has completed this transition.
'workflow.order.completed' => 'onCompleted',
];
}
93. @michellesanver #phpfwdays
public function onCompleted(Event $event): void
{
$transition = $event->getTransition();
$fromState = $transition->getFroms()[0];
/** @var Order $order */
$order = $event->getSubject();
// If an order is already in delivered state, all calculations are done, and should stay as
they are.
if (Order::STATE_DELIVERED !== $fromState) {
$this->orderModifier->updateTotals($order);
}
$this->orderStateHistoryRepository->storeOrderStateTransition($order, $transition);
$this->orderEventService->eventHappened($transition->getName(), $order->getId(), $fromState);
}
94. @michellesanver #phpfwdays
public function onCompleted(Event $event): void
{
$transition = $event->getTransition();
$fromState = $transition->getFroms()[0];
/** @var Order $order */
$order = $event->getSubject();
// If an order is already in delivered state, all calculations are done, and should stay as
they are.
if (Order::STATE_DELIVERED !== $fromState) {
$this->orderModifier->updateTotals($order);
}
$this->orderStateHistoryRepository->storeOrderStateTransition($order, $transition);
$this->orderEventService->eventHappened($transition->getName(), $order->getId(), $fromState);
}
99. @michellesanver #phpfwdays
public function onCompleted(Event $event): void
{
$transition = $event->getTransition();
$fromState = $transition->getFroms()[0];
/** @var Order $order */
$order = $event->getSubject();
// If an order is already in delivered state, all calculations are done, and should stay as
they are.
if (Order::STATE_DELIVERED !== $fromState) {
$this->orderModifier->updateTotals($order);
}
$this->orderStateHistoryRepository->storeOrderStateTransition($order, $transition);
$this->orderEventService->eventHappened($transition->getName(), $order->getId(), $fromState);
}
100. @michellesanver #phpfwdays
public function onCompleted(Event $event): void
{
$transition = $event->getTransition();
$fromState = $transition->getFroms()[0];
/** @var Order $order */
$order = $event->getSubject();
// If an order is already in delivered state, all calculations are done, and should stay as
they are.
if (Order::STATE_DELIVERED !== $fromState) {
$this->orderModifier->updateTotals($order);
}
$this->orderStateHistoryRepository->storeOrderStateTransition($order, $transition);
$this->orderEventService->eventHappened($transition->getName(), $order->getId(), $fromState);
}