spatie/laravel-model-states
Add state and state machine behavior to Laravel Eloquent models. Represent each state as a class, automatically serialize to/from the database, and perform clean, explicit transitions with configurable rules—ideal for workflows like payments, orders, and approvals.
Payment, Order) have well-defined lifecycle states (e.g., Pending, Shipped, Cancelled). This aligns with Laravel’s Eloquent ORM and promotes separation of concerns by encapsulating state-specific logic in dedicated classes.$casts) ensures type safety and serialization/deserialization transparency.Payment could have state and fulfillment states), enabling complex workflows without bloating model logic. The attribute-based configuration (PHP 8+) further reduces boilerplate.HasStates trait, a cast definition, and state classes. No database migrations are needed if the state column already exists (though it’s recommended to add one).Paid::class vs. paid) require directory-based autoloading, which could introduce subtle bugs if state classes are misplaced or renamed. Mitigation: Use IDE refactoring tools and enforce naming conventions.StateChanged events must be listened to if side effects (e.g., notifications, audits) are needed. Default events may not cover all use cases.StateConfig setup. Misconfigured transitions could lead to inconsistent state machines.Failed")?laravel-audit or custom logs)?Order, Subscription, Payment).if ($order->status === 'shipped')) to state classes.Payment) to validate the approach.$payment->state->transitionTo(Paid::class)).if ($order->status === 'cancelled')) in favor of state methods.v1.x of the package.laravel-permission). Monitor for namespace collisions if using custom state directories.Paid vs. Failed).Payment::find($id)->state->transitionTo(Paid::class)->dispatch()).php artisan vendor:publish --provider="Spatie\ModelStates\ModelStatesServiceProvider").config/model-states.php.HasStates trait and cast definitions to target models.app/
States/
Payment/
PaymentState.php
Pending.php
Paid.php
Failed.php
Pending → Paid but not Pending → Cancelled).StateChanged events to Laravel Log or a dedicated table.try-catch in transition logic).Refunded state) require minimal model updates.StateConfig, making them easier to modify than scattered if-else logic.dd($model->state) to inspect current state. Log transition attempts for auditing.allowTransition() rules in StateConfig.$name properties.DB::transaction(function () use ($payment) {
$payment->state->transitionTo(Paid::class);
$payment->update(['updated_at' => now()]);
});
Pending vs. Paid counts) using Laravel’s cache:
Cache::remember('payment_states', now()->addHours(1), function () {
return Payment::getStates();
});
| Failure Scenario | Impact | Mitigation |
|---|---|---|
| Invalid state transition | Data inconsistency | Use try-catch and rollback transactions. Log failed attempts. |
| State class missing | Serialization errors | Enforce ** |
How can I help you explore Laravel packages today?