Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

State Machine Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

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:

  • Version ceiling. Expect composer constraints pinned to PHP 7.x / Laravel 5.5–7-era. On Laravel 9/10/11 this likely will not resolve without a fork. Verify composer.json against our target framework before anything else.
  • Abandonment. Single-maintainer, no security process, no recent releases. A state machine encodes business-critical invariants; sitting on an unmaintained dependency in that path is a poor trade against ~200 lines of in-house code or a maintained alternative.
  • Correctness semantics. Whether transitions are transactional, whether concurrent transitions on the same row are guarded, and whether invalid transitions are prevented or merely discouraged at the persistence layer are all things we must confirm by reading source, not docs.

Key questions

  1. What is the current composer.json constraint set, and does it install cleanly on our Laravel/PHP matrix?
  2. How are transitions persisted — inside a transaction with lockForUpdate, or a bare attribute update that can race?
  3. Are invalid transitions blocked at the model layer (mutator/observer) or only through the package API? Can a developer defeat it with $model->state = 'x'?
  4. Is transition history auditable out of the box, or do we build it from events?
  5. How are guards/conditions expressed and unit-tested? Closures in config are hard to test and impossible to type-check.
  6. What is the event contract, and does it survive queued listeners plus a rolled-back transaction (i.e., is there any outbox pattern, or will we lose events)?

Integration Approach

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

  1. Spike (1–2 days): install in a throwaway branch, model one non-critical entity, exercise one transition plus one guard plus one event. Output: a go/no-go on compatibility.
  2. Build-vs-buy decision: compare against a hand-rolled enum machine (roughly 1–2 engineer-weeks including tests) and spatie/laravel-model-states. At 16 stars, the default should be "buy maintained" or "build small," not "adopt this."
  3. Pilot: one model, one adapter interface, one team.
  4. Gate: do not exceed ~3 dependent models until ownership and upgrade strategy are written down.

Operational Impact

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

  • Bypass: direct writes to the state column silently violate the machine. Mitigate with an observer/DB constraint and code review.
  • Config drift: states removed from config leave orphaned rows; renaming a transition breaks call sites at runtime, not compile time. Requires data migrations alongside config changes.
  • Lost events: transitions that emit and then roll back push phantom events to queued listeners. No outbox means consumers must tolerate this.
  • Late failure: malformed config surfaces on first transition in production rather than at boot. Add a startup/test assertion that every configured machine resolves.
  • Guard untestability: closures in config resist unit testing; push logic into named classes.

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.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment