hyn/state-machine
A lightweight state machine for Laravel that lets you define states and transitions on Eloquent models, validate and apply transitions cleanly, and keep business logic out of your controllers.
Architecture fit
hyn/state-machine is a configuration-driven state machine for Eloquent models: states and transitions are declared in a PHP config array, models opt in via a trait, the current state persists in a single column, and each transition emits Laravel events. That fits a conventional Laravel domain-model architecture where lifecycle rules (draft → submitted → approved) need to be explicit and observable. It is architecturally closer to Symfony Workflow than to the "state-as-classes" pattern of spatie/laravel-model-states: topology lives in config, while per-state behavior has to live in guards and event listeners. That's readable for small machines and tends to leak for large ones.
Integration feasibility Surface area is small — a trait, a config file, a migration for the state column, and event dispatch. No new infrastructure, no queue, no external service. The real feasibility question is not design fit but package health. At 16 stars and a low composite score, this is a long-tail package from an organization (hyn) whose other well-known package, multi-tenant, went dormant. Practical integration risk is dominated by install-and-support, not by architecture.
Technical risk High, concentrated in three areas:
composer.json against our target framework before anything else.Key questions
composer.json constraint set, and does it install cleanly on our Laravel/PHP matrix?lockForUpdate, or a bare attribute update that can race?$model->state = 'x'?Stack fit Acceptable only in one of two scenarios: (a) we are pinned to an older Laravel/PHP baseline where the package installs unmodified, or (b) we accept a fork as an explicit, owned deliverable. On a modern Laravel baseline, assume "fork" from day one and price it accordingly.
Migration path
Do not let the package API spread through the codebase. Introduce it behind an internal domain service or adapter interface — OrderLifecycle::submit($order) — so controllers, jobs, and listeners never reference Hyn\... types directly. With that seam, replacing the package with spatie/laravel-model-states, Symfony Workflow, or a native PHP enum + transition table is a one-file change rather than a refactor. Start with a state string column (indexed) and a saving observer that rejects writes to that column from outside the transition path.
Compatibility
Before committing: run the install against our exact matrix, confirm service provider auto-discovery or manual registration, confirm the trait does not collide with $fillable, casts, or existing observers, confirm config:cache works, and confirm the package's events interleave correctly with our queued listeners and DB::transaction boundaries. Also check interaction with saveQuietly() — a lifecycle library that can be silenced is a lifecycle library that will be silenced.
Sequencing
spatie/laravel-model-states. At 16 stars, the default should be "buy maintained" or "build small," not "adopt this."Maintenance Assume we own it. Plan to vendor or fork, pin the version, and add it to a third-party abandonment watchlist with a named owner. If it installs unmodified, the recurring cost is friction on every Laravel major upgrade. If forked, it's a small but permanent tax — budget a few days per year plus upgrade-cycle attention.
Support Community-only, effectively no upstream. There will be no CVE process and no one to escalate to. That means internal code ownership by the platform team, a runbook entry, and on-call awareness that transition failures surface as domain exceptions, not infrastructure alerts.
Scaling The library itself is not the bottleneck: config loads per request, state is a single indexed column, transitions are single-row updates. The scaling risk is contention and concurrency — two requests transitioning the same aggregate simultaneously. Wrap transitions in a transaction with row-level locking and decide explicitly whether transitions are idempotent. Also watch bulk operations: a batch job transitioning 10k records will emit 10k events and can drown listeners or a queue.
Failure modes
Ramp-up
Developer ramp-up is genuinely low — half a day to a day. The organizational risk is the opposite of complexity: because it is easy, engineers will use it directly everywhere, and the escape hatch ($model->state = ...) is always one line away. Enforce the adapter boundary and the column guard early, or the "small, replaceable dependency" framing quietly stops being true.
How can I help you explore Laravel packages today?