Cancel & expire
Requests::cancel($request); // status = Cancelled
Requests::expire($request); // status = Expiredcancel() fires RequestStatusChanged and RequestCancelled; expire() fires RequestStatusChanged and RequestExpired.
Both also close the request’s open approval round, so a late decision can’t resolve it and pull the request back: through Requests it throws RequestAlreadyResolved, straight through the approvals engine ($alice->approve($request)) the engine’s ClosedApprovalRequestException. The round is closed as cancelled or expired and announced with the engine’s ApprovalRequestResolved event. A cancelled request is final; an expired one can only be reopened.
Deadlines
Stamp an expiry on creation — per request with expiresAt(), or globally with default_ttl. default_ttl is a positive whole number of minutes (a numeric string from env() works too); not set (null or blank) means no default expiry, and anything else throws InvalidConfigurationException:
// Per request:
Requests::make()->title('Leave request')->expiresAt(now()->addDays(7))->create();
// Globally — config/requests.php, in minutes (integer):
'default_ttl' => 60 * 24 * 14, // 14 days unless expiresAt() is givenA deadline alone doesn’t change the status. Until requests:expire or expire() runs, a past-due request stays New, and isExpired() returns true:
$request->isExpired(); // true when still New and past expires_atExpiring from code
Requests::expireDue() expires every open, past-due request and returns the count:
use RoundlyConsulting\Requests\Facades\Requests;
Requests::expireDue(); // int — how many requests were expired
Requests::expireDue(dryRun: true); // int — how many are due; nothing changes
Requests::expireDue(chunk: 1000); // rows processed per batchExpiring on a schedule
The requests:expire command is a thin wrapper over Requests::expireDue() (see Artisan commands). Schedule it:
// routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('requests:expire')->hourly();Only open (New) requests past their expires_at are expired, and their approval rounds are closed with them; approved and rejected requests are untouched. The command also lapses the approval decisions and rounds on requests whose own expiry has passed — scoped to the request model, so the rest of your app’s approvals are left to their own sweep. Requests::expireDue() expires requests only, so make that scoped call yourself when you sweep from code:
use RoundlyConsulting\Approvals\Facades\Approvals;
use RoundlyConsulting\Requests\Facades\Requests;
use RoundlyConsulting\Requests\Models\Request;
Requests::expireDue(); // expire past-due requests
Approvals::expire(subjectType: Request::class); // lapse their decisions and rounds — requests onlyShow 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.