Installation
composer require bastsys/state-bundle
Register the bundle in config/app.php under providers:
Bastsys\StateBundle\StateBundle::class,
Basic Usage
Define a state machine for an entity (e.g., Order):
use Bastsys\StateBundle\StateMachine;
class Order
{
private $stateMachine;
public function __construct()
{
$this->stateMachine = new StateMachine([
'draft' => ['next' => 'pending'],
'pending' => ['next' => 'processing', 'prev' => 'draft'],
'processing' => ['next' => 'completed', 'prev' => 'pending'],
'completed' => ['prev' => 'processing'],
]);
}
public function transitionToNext()
{
$this->stateMachine->transitionToNext();
}
public function getCurrentState()
{
return $this->stateMachine->getCurrentState();
}
}
First Use Case Validate state transitions in a controller:
public function processOrder(Order $order)
{
if ($order->getCurrentState() === 'pending') {
$order->transitionToNext(); // Moves to 'processing'
}
}
State-Driven Logic Use state checks to gate features:
if ($order->getCurrentState() === 'completed') {
$this->generateInvoice($order);
}
Event-Based Transitions
Trigger transitions via events (e.g., OrderProcessed):
event(new OrderProcessed($order));
$order->transitionToNext(); // 'processing' → 'completed'
Validation Rules Integrate with Laravel validation:
$validator = Validator::make($request->all(), [
'status' => Rule::in($order->stateMachine->getAllowedTransitions()),
]);
Eloquent Models
Store state in a database column (e.g., status) and hydrate the StateMachine on model boot:
protected static function boot()
{
parent::boot();
static::created(function ($model) {
$model->stateMachine = new StateMachine($model->getStates());
});
}
API Responses Return state metadata in JSON:
return response()->json([
'state' => $order->getCurrentState(),
'allowed_transitions' => $order->stateMachine->getAllowedTransitions(),
]);
Testing Mock state transitions in unit tests:
$order->stateMachine->setCurrentState('processing');
$this->assertEquals('processing', $order->getCurrentState());
State Machine Initialization
StateMachine with valid states.Circular Dependencies
A ↔ B) can cause infinite loops if not handled.next/prev without mutual recursion.Database Sync
transitionToNext() to save the model:
public function transitionToNext()
{
$this->stateMachine->transitionToNext();
$this->save(); // Persist state
}
State Dump Log the current state machine for debugging:
\Log::debug('State Machine:', $order->stateMachine->toArray());
Transition Errors
Catch InvalidTransitionException for invalid moves:
try {
$order->transitionToNext();
} catch (InvalidTransitionException $e) {
\Log::error($e->getMessage());
}
Custom Transitions
Extend the StateMachine class to add guards:
class CustomStateMachine extends StateMachine
{
public function transitionToNext()
{
if (!$this->canTransition()) {
throw new InvalidTransitionException('Custom guard failed');
}
parent::transitionToNext();
}
}
State Metadata Attach actions or callbacks to states:
$states = [
'draft' => [
'next' => 'pending',
'actions' => ['notify_admin'],
],
];
Localization Use language arrays for state names:
$states = [
'draft' => [
'next' => 'pending',
'label' => trans('states.draft'),
],
];
How can I help you explore Laravel packages today?