---
title: "Status & guarded transitions — Requests for Laravel | Roundly"
description: "The five statuses, their helpers, the lifecycle graph, and the opt-in guard that rejects illegal moves."
url: https://roundly-consulting.com/open-source/docs/requests-for-laravel/status-lifecycle
language: en
---

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

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

# Status & guarded transitions

Every request is always in exactly one of five statuses. The enum carries lifecycle helpers and the shared enum helpers for selects and validation:

```php
$request->status->isOpen();                          // true only for New
$request->status->isTerminal();                      // Approved, Rejected, Cancelled, Expired
$request->status->canTransitionTo(Status::Approved); // bool, per the lifecycle graph
$request->status->allowedTransitions();              // list<Status>

// Shared helpers from enums-for-laravel:
Status::values();
Status::labels();
Status::options();
Status::validationRule();
$request->status->label();
```

## The lifecycle graph

```
New        -> Approved | Rejected | Cancelled | Expired
Approved   -> Rejected | New (reopen) | Cancelled
Rejected   -> New (reopen)
Expired    -> New (reopen)
Cancelled  -> (terminal)
```

## Enforcing transitions

Set requests.enforce\_transitions to true to enforce the graph. Illegal moves — for example approving a rejected request — then throw InvalidStatusTransition, which carries $from and $to:

```php
// config/requests.php: 'enforce_transitions' => true

use RoundlyConsulting\Requests\Exceptions\InvalidStatusTransition;

try {
    Requests::approve($rejectedRequest, $alice);
} catch (InvalidStatusTransition $e) {
    $e->from; // Status::Rejected
    $e->to;   // Status::Approved
}
```

- approve(), reject(), reopen(), cancel() and expire() check the graph before acting, against the stored status — with the flag on, expiring an approved request throws InvalidStatusTransition, even from a copy loaded while it was still New.
- A move to the current status is always allowed.
- The sync listener skips an engine resolution that would be illegal instead of throwing.
- With the flag off (the default) the guard is skipped and decisions resolve directly — except that a closed request stays closed either way.

## Closed requests

A cancelled or expired request is closed, whatever enforce\_transitions says: a Cancelled request is final, and an Expired one can only be reopened with Requests::reopen(). Any other action on it throws RequestAlreadyResolved. Cancelling an already-cancelled request, or expiring an already-expired one, does nothing.

## Concurrent changes

Every status write re-reads the request’s row with lockForUpdate() inside a transaction and runs its checks — already in that status, closed, enforce\_transitions — on the stored status, never on the copy you hold. That covers cancel(), expire(), reopen(), approve() / reject() on a request without approvers, the requests:expire sweep and the engine sync listener.

- A copy loaded before a concurrent decision, cancel or sweep can’t overwrite the newer status — it can’t expire an approved request, or approve, reopen or un-cancel a cancelled one.
- Events fire after the transaction, and only for a status that really changed, so an approval, a cancellation and a scheduled clean-up arriving together each announce their change once.

## Asking the graph

Check a move before you act — for example to show or hide an Approve button:

```php
use RoundlyConsulting\Requests\Enums\Status;
use RoundlyConsulting\Requests\Facades\Requests;

// Answers the same way whether or not enforcement is on
Requests::canTransition($request, Status::Approved); // bool
```

## 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/requests-for-laravel.md)
