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

Laravel State Machine Laravel Package

sebdesign/laravel-state-machine

A lightweight Laravel package for adding state machines to Eloquent models. Define states, transitions, and guards, then move records safely between states with validation, events, and clear exceptions. Integrates cleanly with Laravel and supports custom classes.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit sebdesign/laravel-state-machine is a thin Laravel integration layer over Symfony's symfony/workflow component. It does not implement its own state engine; it contributes a service provider, configuration-driven graph/state-machine definitions, a registry/factory for resolving machines, and a HasStateMachine-style trait for Eloquent models. That is a favourable architecture for us: business rules for lifecycle transitions live in config and guard/transition classes rather than being smeared across controllers, and the underlying engine is a mature, widely deployed Symfony component. It fits bounded-context domains with explicit lifecycles (order fulfilment, subscription billing, approval/onboarding, document review, KYC) far better than ad-hoc status enums plus scattered if checks, and it is a lighter commitment than introducing an event-sourcing framework.

The trade-off is that the package's value is concentrated in glue code, not in the engine. Its longevity therefore tracks two independent variables: Symfony Workflow's API stability and Laravel's service-provider/config conventions. Both are stable, so the architectural risk is moderate-low.

Integration feasibility Integration is inexpensive in engineering terms: composer require, publish the config, declare states/transitions, apply the trait to the relevant models, and optionally listen for transition events. A pilot on a single entity is realistically a one-to-two sprint side-work item, most of which is domain modelling rather than plumbing. The release under review (v3.4.7) is a single-change patch: a Laravel 13.x compatibility contribution from laravel-shift. For any team planning to move onto Laravel 13, this is the gating item — prior to it, the package's illuminate/* constraints would have blocked the framework upgrade or forced a --ignore-platform-reqs workaround in CI. Nothing in the notes indicates breaking API change, config change, or deprecation.

Technical risk

  • Single-change patch, low blast radius but also low signal. The changelog implies a constraint widening. I cannot confirm from the notes alone whether Laravel 13 is now covered by CI or merely permitted by composer.json. Untested constraint widening is the classic source of "works until it doesn't" incidents.
  • Maintainer concentration. Effectively a small-maintainer package with corporate-adjacent contributions. Acceptable, but we should treat it as a dependency with a bus factor and pin versions rather than float.
  • Version-coupling. Symfony Workflow major releases and Laravel majors move on different cadences; the package sits between them. Each future Laravel or Symfony major is a potential multi-month lag.
  • Semantics risk is in the domain, not the code. The real failure surface is transition design (illegal states reachable, guards incomplete, transitions not idempotent) and the fact that persistence/history/audit is our responsibility, not the package's.
  • Concurrency. Concurrent transitions on the same aggregate are not magically serialised. We must own row-level locking or optimistic concurrency on the state column.

Key questions for the maintainer / our own verification

  1. Is Laravel 13 support CI-verified across the test matrix, or constraint-only? What is the tested PHP floor and supported symfony/workflow range?
  2. Are there any behavioural differences on Laravel 13 (event dispatcher, container resolution, config caching) versus 12?
  3. Is a 4.x planned, and does it change config schema, trait API, or the registry contract?
  4. What is the release cadence and the maintainer's stated support window for 3.x?
  5. Does the package persist transition history in any form, or is that entirely consumer-owned?

Integration Approach

Stack fit Assumes a conventional Laravel application (Eloquent models, service container, config publishing, queued event listeners). It composes cleanly with our existing patterns — policy/authorisation checks map naturally onto guards, and transition events map onto our existing domain-event and audit-logging conventions. It is a poor fit only where we already have a workflow engine (e.g. Temporal/Camunda) in the same bounded context; we should not run two orchestrators over one lifecycle.

Migration path

  • Adopt on new lifecycles first, or on a legacy status field with a documented state map (existing values → declared states) so the migration is additive and reversible.
  • Keep the state column name and type unchanged on first pass; backfill only where legacy values have no equivalent state.
  • Introduce guards and transition classes incrementally — start permissive, then tighten — so existing data does not fail validation on day one.
  • Do not combine the package adoption with a Laravel major upgrade in the same release train.

Compatibility v3.4.7 is the minimum viable version for any Laravel 13 workstream. Teams remaining on Laravel 11/12 can stay on 3.4.6 without loss of function, since the only delta is framework compatibility. Conversely, the Laravel 13 upgrade is now unblocked at the dependency level — but should not be treated as validated until we have run our own suite against 13 with this version pinned.

Sequencing

  1. Pin ^3.4.7 in composer.json; run the full test suite plus a config-cache warm on the target Laravel version.
  2. Land a single-entity pilot with read-only observability (log transitions, no behaviour change) to validate events and guards in production shape.
  3. Promote to enforcement (guards active, illegal transitions rejected) on the pilot domain only.
  4. Propagate pattern to a second domain before standardising in engineering guidelines; publish the internal convention doc (state naming, guard placement, history persistence) at that point.
  5. Only then decouple the Laravel 13 upgrade train from the state-machine adoption train, and let each proceed on its own schedule.

Operational Impact

Maintenance Low code-maintenance burden; the cost is configuration ownership. Every new state or transition is a config change that needs review from whoever owns the domain rules — this shifts review load from engineers to domain owners, which is desirable but must be resourced. Budget a recurring dependency-review slot per framework or Symfony major.

Support No vendor support; reliance on upstream OSS and internal expertise. Recommend designating one owner (a "champion") per adopting team, plus a documented fallback: because the engine is Symfony's, an exit path to using symfony/workflow directly, or to a hand-rolled transition table, is realistic and cheap. That materially de-risks the dependency.

Scaling Runtime overhead is negligible — transition resolution is in-memory config evaluation. Scaling concerns are data-side: state column indexing, transition-history table growth (if we persist it), and write contention on hot aggregates. Config caching keeps per-request definition parsing out of the hot path; verify the package's config is cache-friendly on 13. Operationally, the number of declared graphs becomes a cognitive-load problem before it becomes a performance problem — enforce naming conventions and co-locate each graph with its owning domain.

Failure modes to instrument

  • Illegal/blocked transition attempted (guard rejection) — should be a metric, spike indicates UI exposing invalid actions.
  • Transition event listener failures — the state change and side effects are not automatically atomic; define whether listeners are queued or synchronous and alert on either.
  • Concurrent transition conflict / lost update on the state column.
  • Post-framework-upgrade silent misbehaviour where a widened constraint was never exercised in CI — mitigate by pinning and running our own suite.
  • Config cache staleness after deploys.
  • Drift between declared states and values present in the database.

Ramp-up A developer with Laravel experience needs roughly a day to be productive with the trait and config; genuine fluency — guards, transition events, history modelling, and the concurrency story — is closer to one to two weeks of paired work on a real lifecycle. The dominant learning cost is not the package API but the discipline of modelling state explicitly. Plan a short internal walkthrough before the second team adopts it, and capture the pilot's conventions as the reusable template; that is the cheapest way to prevent each team from inventing its own dialect of states and transition names.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core