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 Model States Laravel Package

spatie/laravel-model-states

Add state-machine behaviour to Eloquent models: each state is its own class, with serialization handled automatically and state transitions made easy.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • Implements the state pattern and state machines on top of Eloquent, so lifecycle logic moves out of controllers/observers and into dedicated state classes. For a TPM, this is fundamentally a domain-modeling decision, not a library decision: it raises the ceiling on correctness (illegal transitions become unrepresentable/throwable) at the cost of more classes per model.
  • The design is idiomatic Laravel: states are registered as Eloquent casts ('state' => PaymentState::class), configuration lives on the abstract state class, and transitions are declared explicitly (allowTransition(Pending::class, Paid::class)). That keeps the blast radius local to one model + one abstraction class + N small state classes.
  • Transition classes are pluggable (default_transition in config), and the StateChanged event plus finalState support give us a hook for audit logging and side effects without forking the package.
  • Competitive framing: PHP 8.1 native enums cover values, but not transition graphs or per-state behavior (the color() pattern). If our need is "enforce a lifecycle," this package is the better fit. If our need is "typed status constants," we should not adopt it — enums are enough and cheaper.

Integration feasibility

  • composer require spatie/laravel-model-states, optionally publish a config file. No migrations or schema changes are required — the persisted column is a string containing the state's morph value. That makes the integration reversible and pilotable on a single model without a data migration project.
  • Laravel 8 through 13 are supported; PHP 7.4+ on v2, with recent releases explicitly fixing PHP 8.5 and trait-binding-order regressions. The package tracks the framework closely.
  • The published surface is small and stable (v2 has been current across ~14 minor releases), and the trait is opt-in per model, so we can adopt incrementally.

Technical risk

  • Metadata quality is a decision blocker to verify. The record shows dependents: 0 alongside 1,307 stars and a "last release 2026-11-04" (future-dated). Either the dependent count is unpopulated or it is not measuring Packagist installs. A TPM should not cite adoption numbers from this dataset in an ADR without independent verification (Packagist download stats, internal dependency graph).
  • Concurrency is not handled. Two simultaneous requests can each read the same state and transition; there is no optimistic locking. High-contention lifecycles (payments, order fulfilment) need lockForUpdate() or a version column in the surrounding transaction — that is our responsibility, not the package's.
  • No audit trail by default. StateChanged gives us a hook, but we own the persistence, retention, and PII decisions for transition history.
  • Hydration is coupled to class registration. If the database contains a morph value whose state class is missing or renamed, hydration throws. Deploy ordering (write the new state class before any writer can produce it) becomes a release-engineering concern.
  • Third-party, MIT, no commercial SLA. Low vendor lock-in because the codebase is small and readable, but it is still a dependency an owning team must track.

Key questions

  1. Which models actually have a lifecycle with >3 states and non-trivial transition rules? If it's one, pilot scope is obvious; if it's eight, we need a standard.
  2. Do we need a transition audit log, and is it a compliance artifact or a debugging nicety?
  3. Is the persisted state column exposed in a public API or consumed by another service? If yes, we must pin morph aliases so refactoring class names doesn't break consumers.
  4. Do we need to distinguish "same-state transition" from "illegal transition" per domain? The package defaults are opinionated here.
  5. What is the concurrency profile of the target tables (read-heavy dashboards vs. write-contended checkout)?
  6. Who owns upgrades — is this team willing to track Spatie's major versions, given v2 was itself a breaking API overhaul?

Integration Approach

Stack fit

  • Natural fit for a Laravel monolith or modular monolith where lifecycle correctness is a recurring source of bugs. Poor fit for a service that only exposes status as an opaque string, or for non-Eloquent write paths (raw SQL, bulk imports) which bypass all transition guards entirely.
  • Works cleanly alongside API resources, validation (the package ships a validation rule), and admin panels — admin UIs become the highest-value consumer because they can render only legal next states.

Migration path

  • Pilot on one model with a well-understood lifecycle (e.g., Payment, Order, Subscription). The legacy status string column can usually be reused; no schema change is needed if we accept the string type.
  • Backfill is a mapping exercise, not a data migration: enumerate legacy status values, map each to a concrete state class, and assert that the set is closed. Any unknown legacy value is a finding — treat it as data quality debt surfaced by the pilot.
  • Keep the old constants/accessors temporarily as deprecated shims delegating to state objects, so untouched call sites keep working while we migrate call sites incrementally. Remove at the end of the pilot, not mid-flight.

Compatibility

  • Coexists with native enums only if we choose one representation per column — do not mix. Pick states-as-classes for lifecycle columns, enums for value-only columns, and document the rule.
  • Morph aliasing must be decided up front. Default behavior derives the persisted value from the class name; explicitly aliasing states decouples our public contract from PHP namespaces. This is the single most important design decision in the integration.
  • Bulk operations, imports, and seeders must go through state objects or be explicitly exempted and documented — otherwise we get rows the package can never hydrate.

Sequencing

  1. Week 0 — ADR + spike. One developer, one model, one day. Output: a written decision on states-vs-enums, aliasing strategy, and audit-log approach. Verify package health independently of this dataset.
  2. Weeks 1–2 — Pilot. Convert one model. Define the transition graph explicitly with the domain owner (this is where the real value is: the graph is a business artifact, not code). Wire StateChanged into an audit table. Add an exception handler mapping invalid transitions to HTTP 409.
  3. Week 3 — Harden. Concurrency test (parallel transition attempts), deploy-ordering runbook, hydration failure alarm.
  4. Weeks 4+ — Expand. Apply to the next two models using the pilot as the template; publish an internal one-pager with the transition-graph convention so reviews are consistent.
  5. Quarter 2 — Retro. Decide whether to keep the abstraction at scale or narrow it to the models where it earned its keep.

Operational Impact

Maintenance

  • Low but non-zero. Release cadence is steady (patch and minor releases across 2.13–2.14 with framework compatibility fixes), and the maintainer is a well-known, long-lived OSS shop. The real cost is major-version tracking: v2 was a substantial breaking change (configuration moved from models to state classes, several methods removed). Budget one deliberate upgrade per major, not continuous churn.
  • Because the trait is opt-in per model, upgrade surface is bounded and can be staged model-by-model.

Support

  • No SLA, no vendor phone number. Support is GitHub issues plus a documentation site. Mitigation: MIT license + small codebase means a fork is a realistic fallback for a team of this size.
  • Failure of the upstream project would not be a business-continuity event, but it would be an engineering tax. That asymmetry argues for adoption on high-value models, not blanket adoption.

Scaling

  • Runtime overhead is negligible — one object instantiation per model hydration, no extra queries.
  • Watch three things instead: (1) queue serialization of large numbers of models carrying state casts; (2) event volume on transition-heavy tables, since every transition dispatches StateChanged — audit listeners must be queued or batched; (3) write contention on the same rows, which the package does not solve for us.
  • Bulk writes and ETL pipelines are the scaling blind spot: they scale fine but silently bypass transition guards, producing states the application believes are impossible.

Failure modes

Failure Symptom Mitigation
Unregistered/renamed state class Exception on hydration for affected rows Deploy new state class before any writer; keep old classes as deprecated aliases until data is clean
Illegal transition attempt Uncaught exception surfaces as 500 Central handler → 409 with current-state context in the response
Null state on new rows Cast returns null, downstream NPEs DB default + default() on the state class; add a data-quality check
Concurrent transitions Lost update, duplicated side effects lockForUpdate() in the transition transaction; idempotency keys on side effects
Bulk import/seed Rows with values no state class maps to Ban direct column writes in review; CI check that legacy status enums stay closed
Audit listener throws Transition rolls back or partially applies Queue audit writes; decide explicitly whether audit is inside or outside the transaction

Ramp-up

  • One to two sprints for a mid-level Laravel team to be productive: the state pattern, transition graphs, and the aliasing decision are all learnable but none are "obvious." The first sprint's cost is mostly the transition-graph modeling conversation with the domain owner, which is where the real return lives — that artifact outlives the package.
  • New engineers encounter it on one model with a clear template, which is cheap; encountering it on eight inconsistent models is expensive. Enforce the template via code review and a written convention.
  • Recommended guardrails for the owning team: a documented states-vs-enums rule, a pinned minor version with monthly review, and an alert on hydration exceptions — the last one is the canary for every deploy-ordering mistake we will make.
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.
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator