samuel4x4/state-machine-bundle
samuel4x4/state-machine-bundle provides a simple state machine for PHP/Laravel apps: define states and transitions, guard changes, and hook actions into workflow updates. Keep status logic centralized, explicit, and testable instead of scattering conditionals across models.
-bundle suffix, typical DependencyInjection/*Extension.php + *Bundle.php layout). Laravel does not have a bundle concept, so if you're a Laravel developer you should check composer.json for "type": "symfony-bundle" before assuming it drops in. If it does, plan to use the underlying library/service classes directly rather than the bundle wiring.README.md → composer.json (type, require, autoload paths) → the bundle class and any Resources/config/services.* files → the core state-machine classes under src/. The bundle's DI wiring is the only Symfony-specific part; the state machine logic itself is usually framework-agnostic.composer require samuel4x4/state-machine-bundle
If it registers Laravel auto-discovery (check extra.laravel in composer.json) you'll get a service provider; otherwise you'll need to use classes directly or write a thin provider.Order status flow (pending → paid → shipped → delivered, with cancel side-transitions). Start by defining the transition map, then wire one guard (e.g. canShip requires paid and non-empty shipping_address). Get that working before touching persistence.tests/, and the bundle's example/config fixtures.Order model in Eloquent and store status as a string/enum column; use the package only to evaluate and apply transitions. This keeps you portable if you later swap in symfony/workflow or spatie/laravel-model-states.OrderStateMachine (or config array) and inject it wherever status changes happen — controllers, jobs, listeners. Avoid re-declaring transitions in multiple places; drift is the #1 source of bugs in state-machine code.$machine->apply($order, 'ship'); // throws if guard fails
$order->save();
rather than mutating $order->status directly. The value is the guard + transition validation, so use it on every write path.lockForUpdate()). State machines validate state at check time; without a transaction you can still double-fire paid → shipped.OrderShipped, OrderCancelled) inside your wrapper. This is usually the extension point, not the bundle's internals — dispatch from the service you wrote around the machine.AppServiceProvider (or a small custom provider).config/state_machine.php and mergeConfigFrom() in your provider.AppServiceProvider::register() so it's resolvable from app().composer require "just works." Verify:
extra.laravel — if absent, no auto-discovery.EventDispatcher, Cache, or Config components — if it type-hints Symfony\Contracts\EventDispatcher\EventDispatcherInterface you'll need Laravel's bridge or a shim.config/bundles.php registration (Symfony-only).composer require samuel4x4/state-machine-bundle:^X.Y). Read the CHANGELOG/releases before upgrading; a future-dated release (2026-02-16) is odd, so confirm the tag is stable and not a pre-release.false silently, wrap it and throw yourself — otherwise invalid transitions become silent data corruption.bin/console config:dump won't exist in Laravel and config:cache won't know about it. Any bundle default config must be flattened into your Laravel config and merged manually.TransitionNotFoundException with no context.src/ classes as unstable — wrapping them in your own service class lets you swap implementations later (especially since this package has no dependents to pressure-test its API).spatie/laravel-model-states or a hand-rolled Transition class will be lower risk. Reach for this package when you need named events, guards, and a reusable transition definition — and you're willing to own the Symfony-vs-Laravel glue.How can I help you explore Laravel packages today?