Product Decisions This Supports
- Workflow/status modeling as a first-class product capability. Any feature with an entity lifecycle — order fulfillment, subscription billing states, KYC/approval pipelines, onboarding, ticket triage, payout status — can be expressed as explicit states and allowed transitions instead of scattered
if ($status === ...) checks in controllers.
- Build vs. buy: buy the primitives, build the domain. At 1.0.0, this package is a candidate for the "state transition plumbing" layer so the team only writes the domain rules (what states mean, what must be true to move between them). Weigh against writing ~200 lines of in-house enum + transition code, or adopting Symfony Workflow / Spatie's model-states.
- Roadmap enabler. Low-cost path to features that customers ask for repeatedly: "why can't I cancel now?", "who changed this to shipped?", "block refunds unless delivered." Explicit transitions make these answerable and gate-able rather than emergent.
- Guard rails on side effects. Transitions are where emails, invoices, webhooks, and audit records get fired. Making the transition the single trigger point reduces duplicate-charge and duplicate-notification classes of bugs.
- Architecture decision input. Forces a decision on where state lives: a plain string column plus a machine class, versus a persisted transition history table. That choice drives reporting, support tooling, and compliance/audit commitments.
- Stability signal. Reaching 1.0.0 is a commitment to a stable public API — a reasonable point to standardize on it across services rather than forking or hand-rolling per team.
When to Consider This Package
Adopt when most of the following hold:
- An entity has three or more states and transitions that are not simply linear, or rules that differ by actor (customer vs. admin vs. system).
- You want transitions declarative and testable in isolation, with guards and post-transition actions, rather than inline in controllers, jobs, and observers.
- The team is Laravel/PHP-idiomatic and values a small, focused dependency over a full BPM engine.
- You need a stable, versioned API — the 1.0.0 tag lets you pin the major and stop worrying about churn.
- The workflow is per-aggregate and in-process — no long-running orchestration, no cross-service saga, no human task inbox with SLAs.
Look elsewhere when:
- You need a visual workflow designer, business-user-editable rules, or an ops console — that's a workflow/BPM product (Camunda, Temporal, Inngest), not a library.
- You need durable execution, retries, timers, and compensation across services — model that in an orchestrator, not in a request-cycle state machine.
- You need hierarchical or parallel statecharts with deep history — Symfony Workflow is more mature here and better documented for complex graph semantics.
- You need audit history out of the box — verify whether transition logging is provided or must be added on your side; if it's not, budget for it explicitly.
- The team has already standardized on Spatie
laravel-model-states or Symfony Workflow; a second state library is a maintenance tax with no product upside.
- The flow is genuinely two states with one transition — an enum and a single method is cheaper than any dependency.
Adoption caveats to check before committing: PHP/Laravel version support matrix, documentation depth for guards and side effects, test coverage, and whether state definitions are config-driven or code-driven (this determines who can change a workflow — an engineer, or an operator).
How to Pitch It (Stakeholders)
To executives: We have several flows — orders, subscriptions, approvals — where "what status is this in and what's allowed next" is currently implicit in code. That's where our most expensive bugs live: double charges, notifications fired twice, refunds issued on undelivered orders, support unable to explain why a customer is stuck. This package makes those rules explicit and enforceable in one place, with a small footprint and no new infrastructure. It just hit 1.0.0, meaning the API is stable enough to standardize on. Cost is measured in days, not a quarter, and it pays back in reduced incident volume and faster delivery of status-dependent features.
To engineering: A focused, idiomatic Laravel state machine: define states and allowed transitions, attach guards (cannot ship unless paid) and actions (send receipt, write audit row) to the transition itself. Keeps transition logic out of controllers and observers, is trivially unit-testable, and pins cleanly at ^1.0. It is not an orchestrator — no timers, retries, or cross-service sagas — and it is not a replacement for Symfony Workflow if we ever need hierarchical/parallel statecharts. Before adoption I want three things verified in a spike: how transition history is persisted (or whether we add it), where guards vs. side effects execute relative to the database transaction, and whether state graphs are config-driven so ops can review them. If those check out, I'd propose standardizing on it for new lifecycle work and migrating one existing flow as the proving ground.