Define DFA like state machines for your eloquent models, keep track of changes and prevent unwanted flows of your business logic.
-
Define state machines as native enum
-
Apply state machines to fields on eloquent models
-
Only allow transitions defined in the state machine
-
Allow additional properties to be applied to transitions
-
Postpone transitions
-
Guards for transition
-
General events before/after each transition via named event
-
Events for each transition like eloquent models
-
Actions for defined transitions
-
Action auto discovery + caching
-
Gates to authorize transitions
-
Pending/Running Transitions + lock any transition till the pending is performed or aborted
-
Create common interface for past, pending and postponed transitions
-
Add method to get all past/pending/postponed transitions
-
Add loop handling to represent model updates
-
Convert tests to pest
-
Wrap transitions in transactions
-
Add failed transitions table + model
-
Add executed guards + actions to the transition model
-
Visualize state machines either via console or some html output including guards and actions
The state represents the current status of your model. (But model can have multiple states) The state is defined as an enum, in which the cases represent the set of possible states.
enum State: string {
case Created = 'created';
case Intermediate = 'intermediate';
case Finished = 'finished';
}
The possible transitions are defined via the method transitions
that uses the PHP match
operation to define all possible
outgoing transitions for a state.
enum State: string implements States {
case Created = 'created';
case Intermediate = 'intermediate';
case Finished = 'finished';
public function transitions(): array
{
return match ($this) {
self::Created => [self::Intermidiate],
self::Intermidiate => [self::Finished],
default => []
};
}
}
As default the first case of the enum will be used as initial/starting state. But it can be defined via the use of an attribute.
In difference to the theoretical definition of an DFA, we don't use final states.
Transitions are not determined by input, but explicitly giving the state to transition to.
$model->status()->transitionTo(State::Intermidiate);
It's not always enough to limit transitions by its start and end. For that it is possible to add transition guards that can perform any additional validation.
Additionally, transitions can be limited by authorization gates.
Actions can be run before (as part of the transition) and after a transition is performed. Be aware that a failed before transition cancels the whole transition.
Before Actions can modify the model or perform any other action.
Changes to the model will be included in the changed_attributes
of the transition.
(Except for actions that cause a pending transition)
The after action should be primary for things like notifications etc.
Some transitions carry out actions that take time, for example q queued a job. During this time no further
transitions should be allowed to happen on that model.
To achieve this, the transition must be marked as pending, save to the database and create some sort of lock,
for example via redis.
Once all is done for the transition, it must be marked finished
. That releases the lock and moves the transitions
from pending to past.
A transition will be marked as pending if one of the before actions is queued.
Sometimes a transition shall be marked for the future, for example an expiration. The same result could be achieved by checking the expiration in a cron job and trigger the transition then. A postponed transition has the advantage to see at once when this transition should happen. The transition will be canceled once the state transitioned to a state which does not allow the postponed transition anymore.
Based on and inspired by https://github.com/asantibanez/laravel-eloquent-state-machines by https://github.com/asantibanez!