---
title: "Asking before acting — Lifecycle for Laravel | Roundly"
description: "Check a transition before applying it — can(), check(), allowedTransitions() and the structured, translated Decision and Denial objects."
url: https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel/asking-before-acting
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)

# Asking before acting

Ask whether a transition would be allowed — for buttons, menus and API hints:

```php
Lifecycles::for($listing)->by($user)->can('close');                         // bool
Lifecycles::for($listing)->by($user)->canTransitionTo(ListingStatus::Closed);

$decision = Lifecycles::for($listing)->by($user)->check('reopen');
$decision->allowed;          // false
$decision->codes();          // ['max_occurrences_reached']
$decision->messages();       // ['"Reopen" can be performed at most 3 times.']
$decision->retryAfter;       // a CarbonImmutable when every denial is temporary, else null

foreach (Lifecycles::for($listing)->by($user)->allowedTransitions(includeDenied: true) as $transition) {
    $transition->name;            // 'close'
    $transition->allowed;         // true/false
    $transition->denials;         // list<Denial> when refused
    $transition->requiresReason;  // show a reason field
    $transition->payloadFields;   // keys of its rules()
    $transition->availableAt;     // when a time-based denial lifts
}

Lifecycles::for($listing)->allowedStates();   // states reachable right now
```

## Decision, Denial and AvailableTransition

- Decision — allowed, denials and retryAfter (a CarbonImmutable when every denial is temporary, else null), plus denied(), has($code), find($code), first(), codes(), messages(), isRetryable() and toArray().
- Denial — code, a translated message, params, retryAfter, errors (payload validation errors by key) and source. Codes are DenialCode enum values (frozen, quota\_exceeded, rate\_limited, …) or your guard’s own codes.
- AvailableTransition — name, label, to, toLabel, allowed, denials, requiresReason, payloadFields, availableAt (when a time-based denial lifts) and meta.

## check() is advisory

check() runs the same pipeline as apply(), but apply() checks again under the row lock — someone else may act in between. A soft-deleted subject is refused subject\_trashed by the checks; apply() and rollback() throw SubjectTrashedException for it. For a subject without a state record yet (or with drift), check() counts minimum dwell and seals from now, as the adoption in apply() does. Without a reason or payload, check(), can() and allowedTransitions() report “requires a reason” through requiresReason / payloadFields instead of as a denial, so a button for a transition that needs a reason is not shown as disabled.

## Reading the handle

```php
$handle = Lifecycles::for($listing);

$handle->state();                // the model's attribute as loaded (an enum case or a string)
$handle->effectiveState();       // the expiry target once expires_at has passed
$handle->is(ListingStatus::Active, ListingStatus::Closed);
$handle->isTerminal();
$handle->enteredAt();            // ?CarbonImmutable
$handle->version();              // 0 before the first record exists
$handle->definition();           // CompiledDefinition
$handle->isFrozen();  $handle->frozenUntil();  $handle->frozenReason();
$handle->scheduled();            // list<ScheduledTransition>
$handle->expiresAt();  $handle->isExpired();  $handle->isInGrace();  $handle->isExpiringWithin('3 days');
$handle->history();  $handle->lastTransition();
```

state() is the model’s attribute as loaded — not re-read from the database (reload the model for that) and unchanged by an overdue expiry; effectiveState() is what the state will be once an overdue expiry runs, even before the sweep has run.

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