Installation:
composer require api-platform/state
Add the bundle to config/bundles.php (Symfony) or register the service in config/services.php (Laravel via api-platform/core if applicable).
First Use Case:
StateProcessorInterface to handle state transitions (e.g., published, draft, archived).use ApiPlatform\State\Processor\StateProcessorInterface;
class PostController extends AbstractController
{
public function __construct(private StateProcessorInterface $stateProcessor) {}
public function publish(Post $post): Post
{
return $this->stateProcessor->process($post, 'publish');
}
}
Where to Look First:
StateMachine component (if used under the hood).State Transitions:
config/state_machines.yaml):
App\Entity\Post:
initial_state: draft
transitions:
publish: { from: draft, to: published }
archive: { from: published, to: archived }
$post = $this->stateProcessor->process($entity, 'publish');
Event-Driven States:
StateChangedEvent:
use ApiPlatform\State\Event\StateChangedEvent;
public function onStateChanged(StateChangedEvent $event): void
{
$entity = $event->getSubject();
$from = $event->getFromState();
$to = $event->getToState();
// Trigger side effects (e.g., notifications, logs)
}
Validation Guards:
transitions:
unpublish:
from: published
to: draft
guard: App\Guard\CanUnpublishPostGuard
Laravel-Specific:
StateProcessorInterface in AppServiceProvider:
$this->app->bind(StateProcessorInterface::class, function ($app) {
return new DefaultStateProcessor(
$app->make(StateMachineFactory::class),
$app->make(StateMachineLoaderInterface::class)
);
});
StateChangedEvent:
event(new StateChangedEvent($entity, $from, $to));
API Platform Integration:
StateProcessor in a custom StateProcessor class to add Laravel-specific logic (e.g., Eloquent model updates).Circular Dependencies:
StateProcessor into entity constructors. Use setters or lazy-load it in controllers/services.State Machine Misconfiguration:
initial_state is set in config. Missing states will throw StateMachineException.php bin/console debug:state-machine (Symfony) or a custom Artisan command.Race Conditions:
DB::transaction(function () use ($post) {
$this->stateProcessor->process($post, 'publish');
});
StateChangedEvent to log transitions:
public function onStateChanged(StateChangedEvent $event): void
{
\Log::info("State changed: {$event->getSubject()->getId()}", [
'from' => $event->getFromState(),
'to' => $event->getToState(),
]);
}
$this->stateProcessor->process($entity, 'transition', [], true); // Force debug mode
Custom State Machines:
StateMachineInterface for non-Symfony state machines (e.g., Laravel’s StateMachine package).Dynamic Transitions:
guard: { from: draft, to: published, closure: App\Guard\DynamicPublishGuard }
Laravel Eloquent Hooks:
saving() or saved() model events:
protected static function boot()
{
static::saved(function ($model) {
if ($model->isDirty('status')) {
$model->stateProcessor->process($model, $model->status);
}
});
}
return [
'App\Entity\Post' => [
'initial_state' => 'draft',
'transitions' => [
'publish' => [
'from' => 'draft',
'to' => 'published',
'guard' => new DynamicPublishGuard(),
],
],
],
];
php artisan config:clear
How can I help you explore Laravel packages today?