---
title: "The Lifecycles facade — Lifecycle for Laravel | Roundly"
description: "Tour the Lifecycles facade — for($model) handles, model() helpers, the definitions() and schedules() accessors, the sweep and direct writes."
url: https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel/facade
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Lifecycle for Laravel](https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel.md)

# The Lifecycles facade

Everything goes through one facade, RoundlyConsulting\\Lifecycle\\Facades\\Lifecycles (also registered as the global alias Lifecycles). for($model) returns a handle on one lifecycle of one subject, model() works on a model class, definitions() and schedules() group the definition and schedule operations, and a few flat verbs run the package-wide jobs:

```php
use RoundlyConsulting\Lifecycle\Facades\Lifecycles;

// A handle per subject (and lifecycle), carrying the call's context
Lifecycles::for($listing)->by($user)->because('Back in stock')->apply('reopen');
Lifecycles::for($order, 'payment_status')->apply('authorize');

// Ask before acting
Lifecycles::for($listing)->by($user)->can('close');

// Time: schedules, expiry and freezes
Lifecycles::for($listing)->by($editor)->schedule('publish', $publishAt);
Lifecycles::for($listing)->renew();
Lifecycles::for($listing)->by($moderator)->because('Under review')->freeze(until: now()->addDays(3));

// Undo
Lifecycles::for($listing)->by($user)->rollback();

// Model-level helpers, definitions, schedules and the sweep
Lifecycles::model(Listing::class)->graph();
Lifecycles::definitions()->validate(ListingLifecycle::class);
Lifecycles::schedules()->due();
Lifecycles::sweep();

// Deliberate direct writes, adopted at once
Lifecycles::allowDirectWrites(fn () => $listing->update(['status' => ListingStatus::Closed]));
```

## Flat methods

| Method | Returns | Does |
| --- | --- | --- |
| `for($model, ?$lifecycle)` | `LifecycleHandle` | A handle on one lifecycle of a subject (the first declared one when none is named). |
| `model(Model::class, ?$lifecycle)` | `ModelLifecycle` | Class-level helpers: states, initial and terminal states, transitions, graph, bulk adopt. |
| `definitions()` | `DefinitionsAccessor` | Compiled definitions, validation, graphs and the configured subjects. |
| `schedules()` | `SchedulesAccessor` | Run due schedules, send warnings, list due schedules, retry a failed one. |
| `sweep(?$limit, ?$queue, ?$connection)` | `SweepResult` | Warnings, then every due expiry and schedule — what lifecycle:sweep runs. A null queue follows schedules.queue.enabled; a null connection sweeps the default database connection. |
| `prune(PruneOptions)` | `PruneResult` | Delete old history rows and finished schedules. |
| `adopt($model, ?$lifecycle) / adoptAll(Model::class, ?$lifecycle, $chunk, $scheduleExpiry)` | `bool / int` | Reconcile one subject, or every row of a model, with the stored state. |
| `allowDirectWrites(fn () => …)` | `mixed` | Run code that writes lifecycle attributes directly; the change is adopted on save. |
| `fake()` | `LifecycleFake` | Swap the manager for a recording fake (see Testing). |
| `apply(TransitionRequest), check(), rollback(RollbackRequest), …` | `—` | The request-DTO layer — see DI and actions. |

## The for($model) handle

Lifecycles::for($model, ?$lifecycle) returns an immutable LifecycleHandle; null selects the first lifecycle of lifecycleDefinitions(). The context methods each return a new handle, so a handle can be shared safely. A model that is not a LifecycleSubject throws UnknownLifecycleException, and so does an undeclared lifecycle.

| Method | Returns | Does |
| --- | --- | --- |
| `by(?$actor), asSystem(), because(?$reason), with(array $payload), expectingVersion(int), idempotencyKey(string)` | `LifecycleHandle` | Context for the next calls — each returns a new handle; the last of by() / asSystem() wins. |
| `apply($transition) / transitionTo($state)` | `TransitionResult` | Apply by name, or the one transition to a target state. Throws TransitionDeniedException. |
| `attempt($transition)` | `TransitionAttempt` | Like apply(), with the refusal returned instead of thrown. |
| `can($transition) / canTransitionTo($state)` | `bool` | Would it be allowed right now? |
| `check($transition) / checkTransitionTo($state)` | `Decision` | The full, advisory decision with every denial. |
| `allowedTransitions(includeDenied: false) / allowedStates()` | `list` | AvailableTransition objects (refused ones too with includeDenied), or the reachable states. |
| `state() / effectiveState() / is(...$states) / isTerminal()` | `state / bool` | The model’s attribute as loaded (not re-read from the database), the state once an overdue expiry runs, membership, finality. |
| `enteredAt() / version() / definition()` | `?CarbonImmutable / int / CompiledDefinition` | Entry time of the current stay, the record version, the compiled definition. |
| `history($limit = 50) / lastTransition()` | `Collection / ?TransitionRecord` | History rows, newest first. |
| `rollback(force: false) / rollbackTo($record, force: false)` | `RollbackResult` | Undo the last transition, or everything after a history row — all or nothing. |
| `canRollback() / canRollbackTo($record)` | `Decision` | Would the rollback be allowed right now? |
| `freeze(until: null) / unfreeze() / isFrozen() / frozenUntil() / frozenReason()` | `bool / …` | Freeze this lifecycle (the handle’s reason and actor are recorded) and read the freeze. |
| `schedule($transition, $at) / cancelScheduled($transition) / scheduled()` | `ScheduledTransition / bool / list` | Run a transition later, cancel it, list the open schedules. |
| `retryScheduled($transition)` | `bool` | Put this subject’s newest failed schedule of a transition back to pending (an expiry by its expiry transition), on the subject’s own connection. |
| `expiresAt() / isExpired() / isInGrace() / isExpiringWithin($interval)` | `?CarbonImmutable / bool` | Read the pending expiry. |
| `expireAt($at) / extend($by) / renew(?$for) / neverExpire()` | `CarbonImmutable / bool` | Change the expiry of the current stay. |
| `adopt()` | `bool` | Reconcile this subject with its stored state now. |

## Lifecycles::model()

Class-level helpers for one lifecycle of a model:

| Method | Returns | Does |
| --- | --- | --- |
| `definition()` | `CompiledDefinition` | The compiled definition. |
| `states() / initial() / terminal()` | `list / state / list` | Declared, initial and terminal states. |
| `transitions()` | `list<TransitionDefinition>` | Every transition, in declaration order. |
| `graph(?GraphFormat)` | `string` | A Mermaid or DOT diagram. |
| `adopt(chunk: 500, scheduleExpiry: true)` | `int` | Reconcile every row of the model; returns how many changed (= lifecycle:adopt). |

## Lifecycles::definitions()

| Method | Returns | Does |
| --- | --- | --- |
| `get($definitionClass)` | `CompiledDefinition` | Compile (once per process) and return a definition. |
| `of($model, ?$lifecycle)` | `CompiledDefinition` | The definition behind a model’s lifecycle. |
| `validate($definitionClass)` | `ValidationReport` | isValid(), errors(), warnings() — without throwing. |
| `graph($definitionClass, ?GraphFormat)` | `string` | A Mermaid or DOT diagram. |
| `subjects() / registered()` | `list` | config('lifecycle.subjects') and the definitions behind them. |
| `flush()` | `void` | Drop the compiled definitions (tests that redefine a lifecycle). |

## Lifecycles::schedules()

| Method | Returns | Does |
| --- | --- | --- |
| `runDue(?$limit, ?$queue, ?$connection)` | `SweepResult` | Run due schedules, without warnings. |
| `warn(?$limit, ?$connection)` | `int` | Send due expiry warnings; returns how many fired. |
| `retry($scheduleId, ?$connection)` | `bool` | Failed → pending, attempts reset. Schedule ids are per database connection. |
| `due(?$now, $limit = 100, ?$connection)` | `Collection<ScheduledTransition>` | Pending schedules due by now, oldest first. Read-only. |
| `failed($limit = 100, ?$connection)` | `Collection<ScheduledTransition>` | Failed schedules, most recently failed first — with their subject and outcome. |

The HasLifecycle shorthands ($listing->transition(), $listing->lifecycle()->…) call the same manager, so everything here — including Lifecycles::fake() — applies to them too.

## 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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel.md)
