Open source
Lifecycle for Laravel
composer require roundly-consulting/lifecycle-for-laravelOverview
Status lifecycles — a state machine — for any Eloquent model with a status: orders, tickets, listings, subscriptions, applications. Declare a model’s states and the named transitions between them once, in a small definition class — then every move goes through one guard pipeline that checks who may act, limits and race-free quotas, and writes an append-only history row. Expiry, “expiring soon” warnings, scheduled transitions and rollbacks are built in, and every refusal is a structured, translated reason your UI can show. Native and MIT-licensed, built only on Laravel and Roundly’s own foundation packages — no third-party dependencies.
What you get
Declarative definitions
Backed-enum or string states, an initial state, terminal states and named transitions with wildcards — several lifecycles per model.
One guard pipeline
Gate abilities, actor rules, reasons, payload validation, custom guards and deadlines — every refusal is a structured, translated Denial.
Limits and race-free quotas
Max occurrences, cooldowns, minimum dwell, rate limits and per-scope quotas that hold even under concurrent requests.
Expiry and scheduling
Per-state TTLs, grace periods, once-only warnings, extend and renew, and any transition scheduled for later — one sweep runs them all.
Rollbacks and history
Undo the last transition or roll back to a point, with windows, compensation and snapshots — on top of an append-only history.
Safe under concurrency
A row lock plus a compare-and-swap write per transition, after-commit events, optimistic versions and idempotency keys.
Facade, DI or actions
One Lifecycles facade, an injectable manager or single-purpose actions — plus a fake that refuses what the real engine refuses without a database.
Documentation
Installation
Install via Composer, choose key types, publish the migrations, and schedule the sweep when states expire or transitions are scheduled.
Configuration
Every config key, default and env variable — key types, subjects, strict writes, actors, transactions, history, rollbacks and schedules.
Defining a lifecycle
Declare states, the initial and terminal states and named transitions in a definition class — every builder method and how to validate it.
Models with a lifecycle
Implement LifecycleSubject, add the HasLifecycle trait and map attributes to definitions — several lifecycles per model, relations and reserved names.
The Lifecycles facade
Tour the Lifecycles facade — for($model) handles, model() helpers, the definitions() and schedules() accessors, the sweep and direct writes.
DI and actions
Skip the facade: inject LifecycleManager or call a single-purpose action with a request DTO — the handle → manager → action map.
Applying transitions
Apply named transitions with an actor, reason and payload — transitionTo(), attempt(), results, optimistic versions and idempotency keys.
Asking before acting
Check a transition before applying it — can(), check(), allowedTransitions() and the structured, translated Decision and Denial objects.
Restrictions and guards
The guard pipeline in order — system context, actor rules, Gate abilities, reasons, payloads, custom guards — and every denial code.
Limits, quotas and freezes
Max occurrences, cooldowns, minimum dwell, seals, rate limits, race-free quotas per scope, and freezing one lifecycle of a subject.
Handlers, hooks, stamps and snapshots
Run side effects inside the transaction with handlers and onEnter/onExit hooks, stamp timestamps, snapshot attributes and compensate on rollback.
Expiry
Per-state TTLs from an interval, a closure or a column — grace periods, once-only warnings, effectiveState() and extend, renew or neverExpire().
Scheduled transitions and the sweep
Schedule any transition for later and run due expiries, warnings and schedules with lifecycle:sweep — inline or queued, with retries.
Rollbacks and history
Undo the last transition or roll back to a history point — all or nothing, with windows, compensation and snapshots — plus the append-only history.
Querying
Query scopes for state, expiry, grace, freezes and time in state — plus withLifecycle() eager loading to avoid N+1 queries.
Strict writes and drift
Why direct status writes throw, which write paths bypass the engine, and how allowDirectWrites() and adoption reconcile drift.
Definitions and graphs
Inspect states and transitions with Lifecycles::model() and definitions(), and export Mermaid or DOT diagrams with lifecycle:graph.
API resources and validation
LifecycleResource and TransitionRecordResource for APIs, the ValidTransition and ValidState rules, and 422 or Retry-After from refusals.
Events
Every event, when it fires and its payload — listen for LifecycleTransitioned, not Eloquent’s updated, and keep side effects after commit.
Artisan commands
make:lifecycle, sweep, graph, validate, show, adopt and prune — signatures, options and when to run each.
Concurrency and databases
One transaction and lock order per change, compare-and-swap state writes, MySQL READ COMMITTED for quotas, deadlock retries and Octane.
Migrating from status enums
Map hand-rolled status enums, checks and commands onto the package, and move an existing table over with lifecycle:adopt.
Testing
Swap in Lifecycles::fake() to record transitions and refuse on demand, with every assertion — or test the real engine with factories and time travel.
Requirements
PHP 8.4+, Laravel 12 or 13, and SQLite, PostgreSQL or MySQL/MariaDB — with the package’s tables in the same database as your models.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.