›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-approval
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

byrcsc/laravel-approval · 1.x

Events and listeners.

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;
});

Request lifecycle

EventFires whenAlso carries
ApprovalSubmittedA request exists and its first stage is somebody's turnstage (nullable)
ApprovalCompletedEvery applicable stage was satisfiednone
ApprovalApprovedThe request reached its final approved statenone
ApprovalRejectedThe rejection policy ended the requeststage, actor, comment
ApprovalReturnedAn approver sent it back to the requesterstage, actor, comment
ApprovalWithdrawnThe requester withdrew itactor, comment
ApprovalCancelledAn administrator cancelled itactor, comment
ApprovalSystemClosedThe engine closed it, for example on record deletionreason
ApprovalResubmittedA closed request was revised into a new oneprevious

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.

Stages

EventFires whenAlso carries
StageApprovalRecordedOne approval was recordedstage, actor, approvalsReceived, approvalsRequired
StageCompletedA stage's approval rule was satisfiedstage, next (nullable)
StageOverdueA deadline passed, once per stagestage, action
StageOverdueReminderAn overdue stage is still undecidedstage, action
StageStrandedLiving holders can no longer meet the thresholdstage, 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.

Drafts

EventFires whenAlso carries
DraftAppliedThe draft was written onto the recordapprovable, draft
DraftConflictedFully approved, but the record had movedapprovable (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.

Assignments

EventFires whenAlso carries
ApprovalDelegatedAn approver handed their assignment onassignment, approver, delegate, comment
DelegationEndedA standing delegation handed one backassignment, approver, delegate
ApproverReassignedAn administrator rewrote an assignmentassignment, from (nullable), to, actor
ApproversResyncedA live request's assignments were rebuiltchanges, actor, addedAssignments
ApproversEscalatedAn overdue stage's policy added approversstage, 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.

Events fire inside the transaction

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.

Listening to the base class

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.

Events versus notifications

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.

What to read next

  • Notifications to see which events have included listeners.
  • Audit trail and evidence to compare events with persisted actions.
  • Testing to fake mutations or assert dispatched events.
PreviousQueries and timelinesNextNotifications
View source

On this page

  1. Request lifecycle
  2. Stages
  3. Drafts
  4. Assignments
  5. Events fire inside the transaction
  6. Listening to the base class
  7. Events versus notifications
  8. What to read next