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

innoge/laravel-enum-states

A state machine for Eloquent built on native PHP enums. Define transitions, defaults, and lifecycle actions on the enum itself, register fields with a single attribute, and keep your normal model casts. Invalid transitions throw; defaults apply automatically.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package's core design claim — states are native PHP enum cases, the transition graph lives on the enum, and fields are registered via a single PHP attribute — is architecturally attractive for a TPM because it reduces the number of new concepts a team must absorb. Compare against the incumbent pattern (state classes + model trait + casts, e.g. spatie/laravel-model-states): that approach introduces a class-per-state, a trait on every model, and custom cast classes, all of which are places where behavior can drift. Here, the enum is domain vocabulary the team already owns, the model keeps its ordinary enum cast, and there is no persistence layer added. The trade-off is equally clear: by refusing state classes, the package also refuses a natural home for per-state behavior (labels, colors, allowed actor roles, side-effect handlers), so those tend to leak into Blade/API/Filament layers or match statements scattered across the codebase. Whether that is a win depends on how much state-conditional behavior exists in the domain — for a status field with 5–8 linear states it is a clear win; for an order/claim lifecycle with per-state business rules, it is a refactor waiting to happen.

Integration feasibility. High on paper. A typical adoption is: composer require, change a string column cast to the enum, add the attribute to the model, implement the transition method(s) on the enum. That is an afternoon, not a sprint, and it requires no database migration because the underlying column (string or int) is unchanged. The unknowns are all about enforcement: the "no model trait" positioning implies the package hooks Eloquent through some other mechanism (attribute discovery plus a booted callback, a global scope, or an explicit transition call). A TPM needs to know which, because it determines whether the guard is a hard boundary or an opt-in helper.

Technical risk. Dominated by vendor risk, not technology risk:

Risk Severity Note
Bus factor / longevity High 5 stars, 0 dependents, single agency maintainer (InnoGE), no ecosystem signal
Laravel major-version lag Medium Small packages often trail new Laravel releases by months
Silent bypass of transitions Medium–High If guards only apply on a dedicated transition call, direct $model->status = X; $model->save() routes around them
No transition history Medium "No extra tables" is a feature and a ceiling — audit/event sourcing needs an append-only log
Concurrency Medium No mention of optimistic locking; read-modify-write on a status column is a classic lost-update path
Pre-1.0 / API churn Unknown Verify the version constraint in composer.json before committing

Key questions to put to the maintainer (or answer in a 1-day spike):

  1. How is the transition guard enforced — on saving/updating Eloquent events, or only when an explicit transition method is invoked? Can mass assignment or update() bypass it?
  2. Does a rejected transition throw a typed, catchable exception suitable for mapping to a 422 in HTTP and a dead-letter in queues?
  3. Is the attribute read via reflection on every transition, or cached at boot? What is the per-request cost?
  4. Can a single model declare multiple state fields, and do transitions on field A and field B interact?
  5. Is there any hook/event for post-transition side effects (mail, jobs, webhooks), and does it run inside the same DB transaction?
  6. Does it support int-backed enums and non-UnitEnum casts, and does it interact cleanly with where('status', Status::Active) query building and scopes?
  7. What are the declared PHP and Laravel version constraints, and is there a support policy?
  8. Is there optimistic concurrency (version column or conditional UPDATE ... WHERE status = ?) or is that left to the caller?

Integration Approach

Stack fit. Requires PHP 8.1+ (native enums, attributes) and a modern Laravel/Eloquent version — verify exact constraints before planning. It is a pure-library, zero-infrastructure package: no migrations, no config file to publish (or a minimal one), no queue worker or cache dependency, no service provider side effects beyond package discovery. That makes it trivially compatible with containerized CI, Laravel Octane, and read-replica setups, and it means rollback is composer remove plus reverting a cast — an unusually clean escape hatch for a third-party dependency.

Migration path. Three distinct starting points, with very different costs:

  • String/constant status columns (most common): Low. The DB values must match the enum's backing values exactly; if legacy rows contain 'PENDING', 'pending', or NULL, run a one-off normalization query and add a NOT NULL default before flipping the cast. Hydration of an unrecognized value throws a ValueError, so a stale row becomes a 500 — this is the single highest-probability migration incident and should be scripted and rehearsed.
  • Plain enum casts with ad-hoc if guards: Low–medium. The work is consolidating scattered transition checks into the enum graph; behavior is preserved but you are deleting code, which needs test coverage to prove equivalence.
  • spatie/laravel-model-states or a bespoke state machine: Medium–high. State classes carry behavior, custom casts, and often a history table. This package will not absorb any of that, so expect to relocate per-state logic into the enum (as methods) or into domain services, and to keep or replace the history table separately. Do not treat this as a drop-in swap.

Compatibility. Confirm interaction with Filament/Nova admin panels (they often write fields directly, which is exactly the bypass risk above), API resources ($model->status->value vs. serialized enum), factory states, and queue payload serialization for models carrying enum casts. If the app exposes a public or partner API, the enum's backing values become a contract — treat renames as breaking changes and pin the package with a caret constraint plus a lockfile committed to the repo.

Sequencing. Recommended order, roughly 2–3 weeks of a single engineer's part-time effort:

  1. Spike (1 day): build a throwaway model, answer the key questions in code, and specifically attempt to bypass a guard. Decide go/no-go on that result.
  2. Pilot (2–3 days): adopt on the lowest-risk stateful model — one with few write paths and good test coverage. Instrument nothing; just observe.
  3. Boundary (3–5 days): introduce a thin application-level contract (e.g. CanTransition / a small TransitionService) that the rest of the app calls, so the package is behind a seam. This is the highest-leverage step for a TPM: it converts a vendor-risk conversation into a one-file swap if the package is abandoned.
  4. Rollout: expand model by model, in dependency order of the domain, not in order of developer enthusiasm.
  5. Retire the old guards: delete ad-hoc if checks and add a CI lint or static-analysis rule that flags direct writes to state columns outside the boundary.

Operational Impact

Maintenance. The package surface is small — an attribute, an enum contract, and a transition method — which is good: small surface means few upgrade surprises and a realistic hand-patch if the maintainer goes quiet. MIT license means forking is legally unencumbered. The real maintenance task is not the library, it is the discipline around it: the graph on the enum must be reviewed like a schema change. Add an owner and a code-review rule that any change to a transition method requires a test for both the allowed and the rejected path. Budget for a Laravel major-upgrade check once a year, and put the package on the same watch list as your other small dependencies.

Support. Community-only: GitHub issues, no SLA, no commercial backing disclosed. 0 dependents means the issue tracker is quiet — which reads as "stable" but equally as "unexercised." Mitigation is the adapter seam above plus committing the lockfile. Escalate to fork-and-maintain if a Laravel upgrade ever blocks you; the code volume is small enough that a mid-level engineer can own a fork.

Scaling. Runtime cost is negligible — enum casts are cheap, and the only open question is whether attribute reflection happens per transition or once at boot; either is fine at typical request volumes, and only worth measuring if you are doing thousands of transitions per second. The scaling concern is write concurrency, not throughput. Two workers transitioning the same record from Pending to Paid and Cancelled simultaneously will both read Pending, both pass the guard, and the last write wins — silently, with no error. If your domain has concurrent actors (payment webhooks plus admin actions, for example), you need a lockForUpdate() in a transaction or a conditional UPDATE ... WHERE status = <expected> around every transition. Do not assume the package provides this; ask, then verify with a two-process test.

Failure modes, ranked by blast radius:

  1. Silent state corruption via concurrency — lost updates, no exception, discovered by a customer. Highest severity, hardest to detect.
  2. Guard bypass — a controller, admin panel, importer, or Model::update() writes the column directly, skipping the graph. The state machine becomes documentation rather than enforcement.
  3. Hydration failure on dirty data — an unrecognized or NULL value in the column throws on model load, turning one bad row into a 500 on any page that touches it.
  4. Non-transactional side effects — if post-transition hooks (mail, jobs, webhooks) run outside the transaction, a rollback leaves the notification sent but the state unchanged, or vice versa.
  5. No audit trail — nothing in the package records who transitioned what and when. If your compliance, billing, or support story needs that (it usually does), plan a separate append-only state_transitions table written by your adapter layer. The package's "no extra tables" promise is a starting-point convenience, not an auditability answer.
  6. Abandonment — low probability of catastrophe given the small surface and MIT license, but non-zero; the seam in step 3 of the sequencing plan is what makes it survivable.

Ramp-up. Low for developers: native enums and attributes are standard PHP 8.1+ knowledge, and the mental model ("the enum knows its own legal next states") is easier to teach than state classes. Expect under a day for a mid-level Laravel developer to be productive, and near-zero for reviewers who only need to read a transition method. The costlier ramp-up is organizational, not technical: teaching the team that the transition method is the only sanctioned write path, and enforcing that with review and static analysis until it is habit. Budget for that in the rollout plan — it is the difference between a state machine and a state machine that works.

TPM bottom line. Worth a one-day spike on a low-risk model. Adopt behind an application-level seam with a committed lockfile, and treat the concurrency and bypass questions as gate conditions rather than follow-ups. Do not adopt as a replacement for an existing state-class implementation with material per-state behavior, and do not rely on it for audit history.

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.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table