jobmetric/laravel-state-machine
jobmetric/laravel-state-machine adds finite state machines to Laravel applications. Define states and transitions for Eloquent models, enforce guards, trigger events and callbacks, and manage safe state changes with a clean, configurable API.
Architecture fit: The package provides a Laravel-native state machine abstraction (generated via state-machine.php.stub) that fits cleanly into service-layer or domain-model patterns common in Laravel applications. It layers on top of Eloquent models and is designed to be generated per-domain, which aligns well with DDD-lite or modular monolith architectures. The new getAllTransitions / getCurrentState accessors for fields make it viable as a source of truth for UI state rendering and workflow orchestration, not just backend enforcement.
Integration feasibility: Low-friction. Composer-installable, Laravel service-provider driven, and now with a state-machine:debug artisan command plus accompanying test coverage — which materially de-risks adoption because ops and QA can inspect transition graphs without writing code. The composer.json bump to orchestra/testbench indicates active alignment with current Laravel test harness versions, a positive signal for LTS compatibility.
Technical risk: Moderate-low. Primary risks: (1) the stub is a code generator — teams will own the generated code, so upgrades to the package's stub in future releases may not retroactively apply; (2) coupling of business rules into generated state classes can obscure logic from reviewers unfamiliar with the pattern; (3) the orchestra/testbench version change could be a soft compatibility gate for projects pinned to older Laravel/PHPUnit matrices. The release notes do not describe any breaking API changes, so this is a feature-additive minor.
Key questions: Which Laravel/PHP versions does 2.4.0 support after the testbench bump? Does getCurrentState return a raw string, an enum, or a state object (affects serialization/API contracts)? Is the debug command safe to run in production or does it require an env guard? Are transitions introspectable programmatically (relevant for building admin UIs and audit tooling)? What is the upgrade path if the stub evolves in 3.x?
Stack fit: Strong for Laravel monoliths and modular monoliths using Eloquent. It slots alongside existing event systems (state transitions typically fire model events, which integrate with observers, jobs, and broadcasting). For teams already using spatie/laravel-model-states or similar, this is a competing rather than complementary primitive — pick one per domain to avoid dual state models.
Migration path: For greenfield domains, generate state machines directly via the stub. For existing domains with ad-hoc status columns, the migration is: (a) map existing enum/string statuses to the new machine's states, (b) backfill getCurrentState against persisted rows, (c) wrap existing status writes behind transition calls, (d) use state-machine:debug in staging to validate the graph matches legacy behavior before cutover. Plan this per-aggregate, not big-bang.
Compatibility: The orchestra/testbench change is the compatibility canary. Before pinning 2.4.0, verify the CI matrix (Laravel 10/11/12, PHP 8.1–8.3) runs green in a scratch branch. The new debug command and accessors are additive, so a 2.3.x → 2.4.0 upgrade should be a no-op for consumers that do not use generated stubs — but re-verify if any downstream code extended the stub output.
Sequencing: (1) Spike in a single non-critical domain to validate the stub output matches team conventions. (2) Adopt getAllTransitions / getCurrentState in read paths (API responses, admin panels) — lowest risk, highest visibility. (3) Wire state-machine:debug into a staging-only CI job that fails on invalid transition graphs. (4) Only then migrate write paths and enforcement. (5) Defer stub regeneration until a second domain is onboarded, to keep the first migration contained.
Maintenance: Generated code means ongoing ownership of state classes; the package maintains the generator and runtime, the team maintains the outputs. Budget a small recurring cost to review stub outputs when the package bumps minor versions.
Support: The addition of a debug command and its test coverage improves operability — on-call engineers can inspect transition state without reading source. Recommend documenting the command in the runbook and gating it behind local/staging middleware if it exposes model data.
Scaling: State machines themselves are cheap; the scaling concern is the read patterns around getCurrentState — ensure it is not called in hot loops or N+1 query paths, and consider caching transitions per model class if getAllTransitions is used in list endpoints. Transition enforcement writes are row-local, so concurrency is governed by the underlying DB, not the package — ensure transitions are wrapped in transactions or optimistic-locking where races matter.
Failure modes: (1) Stub drift — a future package minor changes the stub, teams regenerate, and diffs introduce behavior changes; mitigate with generated-code review in PRs. (2) Silent transition bypass — direct writes to the status column circumvent the machine; mitigate with model guards or DB triggers if integrity is critical. (3) Testbench version mismatch breaking CI on upgrade; mitigate with a matrix branch before pinning. (4) Debug command exposure in production leaking schema; mitigate with env guards. (5) State explosion — the debug command will surface this early; treat large graphs as a design smell, not a package limitation.
Ramp-up: Expect ~1 sprint for one engineer to become productive (stub conventions, transition semantics, testbench constraints) and ~2 sprints before team-wide adoption without a champion. The debug command and improved phpdoc reduce the onboarding curve meaningfully versus prior versions — this release is a net positive for ramp-up and is a reasonable moment to pilot the package in a bounded domain.
How can I help you explore Laravel packages today?