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:
$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:
// 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 — with the flag on, expiring an approved request throws.
- 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.
Asking the graph
Check a move before you act — for example to show or hide an Approve button:
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); // boolShow 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.