›
byrcsc/laravel-approval · 1.x
Listen for approval lifecycle events, which fire inside the transaction that caused them.
Every event extends ApprovalEvent and carries its request. A listener that
needs only the request can type-hint the base class:
use ByRcsc\LaravelApproval\Events\ApprovalEvent;
Event::listen(ApprovalEvent::class, function (ApprovalEvent $event) {
$event->request;
});| Event | Fires when | Also carries |
|---|---|---|
ApprovalSubmitted | A request exists and its first stage is somebody's turn | stage (nullable) |
ApprovalCompleted | Every applicable stage was satisfied | none |
ApprovalApproved | The request reached its final approved state | none |
ApprovalRejected | The rejection policy ended the request | stage, actor, comment |
ApprovalReturned | An approver sent it back to the requester | stage, actor, comment |
ApprovalWithdrawn | The requester withdrew it | actor, comment |
ApprovalCancelled | An administrator cancelled it | actor, comment |
ApprovalSystemClosed | The engine closed it, for example on record deletion | reason |
ApprovalResubmitted | A closed request was revised into a new one | previous |
ApprovalCompleted and ApprovalApproved are separate events.
ApprovalCompleted means every applicable stage was satisfied.
ApprovalApproved means the request reached its final state and any draft was
applied. A request that completes and then conflicts fires ApprovalCompleted
but not ApprovalApproved.
On ApprovalRejected, actor is null when a deadline rejected the request.
That happens when an auto_reject stage passes its deadline.
On ApprovalResubmitted, $request is the replacement and $previous is the
request it replaces.
| Event | Fires when | Also carries |
|---|---|---|
StageApprovalRecorded | One approval was recorded | stage, actor, approvalsReceived, approvalsRequired |
StageCompleted | A stage's approval rule was satisfied | stage, next (nullable) |
StageOverdue | A deadline passed, once per stage | stage, action |
StageOverdueReminder | An overdue stage is still undecided | stage, action |
StageStranded | Living holders can no longer meet the threshold | stage, livingHolders, requiredApprovals |
StageCompleted carries the next waiting stage as next. next is null when
the completed stage was the last one, and ApprovalCompleted follows.
StageOverdue fires before the escalation strategy acts, so a listener
sees the state that went overdue.
| Event | Fires when | Also carries |
|---|---|---|
DraftApplied | The draft was written onto the record | approvable, draft |
DraftConflicted | Fully approved, but the record had moved | approvable (nullable), attributes |
On DraftApplied, the record is already saved, so a listener reads the new
values.
On DraftConflicted, attributes names the attributes that changed, and
approvable is null when the record itself is gone. Listen for this event if
someone should review the conflict. A conflicted request stays open until a
person resolves it.
| Event | Fires when | Also carries |
|---|---|---|
ApprovalDelegated | An approver handed their assignment on | assignment, approver, delegate, comment |
DelegationEnded | A standing delegation handed one back | assignment, approver, delegate |
ApproverReassigned | An administrator rewrote an assignment | assignment, from (nullable), to, actor |
ApproversResynced | A live request's assignments were rebuilt | changes, actor, addedAssignments |
ApproversEscalated | An overdue stage's policy added approvers | stage, assignments |
ApprovalDelegated also fires when a
standing delegation routes an assignment,
except on stage activation, where ApprovalSubmitted or StageCompleted
already names the delegate. DelegationEnded fires only for assignments nobody
had decided yet.
On ApproverReassigned, from is null when the record that held the
assignment has itself been deleted.
The package dispatches ApproversEscalated after it creates the new
assignments, so recipient calculation sees only the new holders. Escalation is
automatic, so the event has no human actor.
The package dispatches every event inside the transaction that caused it. As a result:
A listener that does anything slow or external should be queued.
use Illuminate\Contracts\Queue\ShouldQueue;
final class PostToSlack implements ShouldQueue
{
public function handle(ApprovalCompleted $event): void { /* … */ }
}A queued listener should dispatch after commit. The transaction can still roll back the request after the listener receives it:
public $afterCommit = true;The shipped notification listener defers its send with DB::afterCommit() for
the same reason.
Because every event extends ApprovalEvent, a single listener can feed an
activity stream:
Event::listen(ApprovalEvent::class, function (ApprovalEvent $event): void {
ActivityLog::create([
'type' => class_basename($event),
'request_ulid' => $event->request->ulid,
]);
});The package already keeps its own append-only history. See audit trail and evidence. Use events to drive your application's reactions, not to rebuild the trail.
The notification layer is one listener for a subset of these events, and it is off by default. To react to an event the shipped notifications do not cover, listen for it directly. See notifications.