Entities lifecycle is usually something more than create and delete. Models with which we are working on day to day basis change their state under some business circumstances. State machine patterns can be a powerful allay when solving this kind of problem. How does it look like? What are the pros and cons of state machine usage? What are the things that we should be beware of? I will answer these questions during my talk, together with practical differences between the most popular implementation.
16. (Q, Σ, δ, q0, F)
Q = a
fi
nite set of states
Σ = a
fi
nite, nonempty input alphabet
δ = a series of transition functions
q0 = the starting state
F = the set of accepting states
21. Callback’s execution in con
fi
g
fi
le
Used heavily in Sylius
Con
fi
gurable arguments of service
Services have to be public in order to integrate them with state machine
Without stable release, yet battle-tested and robust
29. Maintained by Symfony
Flex support
Work
fl
ow & state machine support
Execution of external services with events
XML / YAML / PHP support out-of-the-box
Best documentation hands down
36. Oldest and most popular implementation
(in terms of stars)
Least used implementation
(in terms of daily installs according to packagist)
Extendability with events & callbacks de
fi
nition
Perhaps reached its EOL
(https://github.com/yohang/Finite/issues/161)
It is said to be little bit heavier implementation compared to Winzou
40. final class Payment
{
public const NEW = 'new';
private string $state = self::NEW;
public function getState(): string
{
return $this->state;
}
public function setState(string $state): void
{
$this->state = $state
}
}
55. class StateMachine implements StateMachineInterface
{
public function apply(/** */): void
{
// Guard callback
if (!$this->can($transition)) {
throw new SMException();
}
// Before callback
$this->setState('failed');
// After callback
}
}
56. final class InventoryOperato
r
{
public function __invoke(TransitionEvent $transitionEvent): voi
d
{
$stateMachine = $transitionEvent->getStateMachine()
;
/** @var Payment $payment *
/
$payment = $stateMachine->getObject()
;
// Reduce inventory of bought product
s
}
}
$config =
[
// ..
.
'callbacks' =>
[
'before' =>
[
'reduce_amount' => [
'on' => ['pay']
,
'do' => new InventoryOperator()
,
]
,
]
,
]
,
];
59. $dispatcher = new EventDispatcher()
;
$dispatcher->addListener
(
'workflow.payment.enter.paid',
new PaymentPaidListener(
)
)
;
$workflow = new Workflow($definition, $marking, $dispatcher, 'payment');
final class PaymentPaidListener
{
public function __invoke(EnterEvent $event): void
{
// Reduce inventory of bought products
}
}
60. $dispatcher = new EventDispatcher()
;
$dispatcher->addListener
(
'workflow.payment.enter.paid',
new PaymentPaidListener(
)
);
$workflow = new Workflow($definition, $marking, $dispatcher, 'payment');
final class PaymentPaidListener
{
public function __invoke(EnterEvent $event): void
{
// Reduce inventory of bought products
}
}
61. $dispatcher = new EventDispatcher()
;
$dispatcher->addListener(
'workflow.payment.enter.paid',
new PaymentPaidListener(
)
)
;
$workflow = new Workflow($definition, $marking, $dispatcher, 'payment');
final class PaymentPaidListener
{
public function __invoke(EnterEvent $event): void
{
// Reduce inventory of bought products
}
}
62. $dispatcher = new EventDispatcher()
;
$dispatcher->addListener(
'workflow.payment.enter.paid',
new PaymentPaidListener(
)
)
;
$workflow = new Workflow($definition, $marking, $dispatcher, 'payment');
final class PaymentPaidListener
{
public function __invoke(EnterEvent $event): void
{
// Reduce inventory of bought products
}
}
63. $dispatcher = new EventDispatcher()
;
$dispatcher->addListener(
'workflow.payment.enter.paid',
new PaymentPaidListener(
)
)
;
$workflow = new Workflow($definition, $marking, $dispatcher, 'payment');
final class PaymentPaidListener
{
public function __invoke(EnterEvent $event): void
{
// Reduce inventory of bought products
}
}
67. guard -> possibility to validate transition
transition -> which transition will be executed
completed -> which transition was executed
announce -> which transition are available now
Types for transitions
68. class Workflow implements WorkflowInterface
{
public function apply(/** */
)
{
$this->leave(/** */)
;
$this->transition(/** */);
$this->enter(/** */)
;
/** Making transition */
$this->markingStore->setMarking($payment, 'failed', $context)
;
$this->entered(/** */)
;
$this->completed(/** */)
;
$this->announce(/** */)
;
}
}
74. final class BlockedGuardListener
{
public function __invoke(GuardEvent $event): void
{
$event->setBlocked(true);
}
}
$dispatcher->addListener
(
'workflow.payment.guard.block'
,
new BlockedGuardListener(
)
)
;
78. De
fi
ne possible states and relation between them
Protect business rules (with guards)
Trigger other services on transitions
Ease to add new possible transitions
Trigger other state changes as a reaction