›
›
›
  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.

React to approval lifecycle events safely around database transactions.

Every event extends ApprovalEvent and carries the request it happened to, so a listener that only cares which record moved 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 deliberately separate. Completion means the routing finished; approval means the request reached its final state, draft applied. A request that completes and then conflicts fires the first and not the second.

On ApprovalRejected, actor is null when the clock rejected it, an auto_reject stage whose deadline passed.

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 stage now waiting as next, or null when this was the last one, in which case 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 what drifted and approvable is null when the record itself is gone. This is the event to listen for if somebody should be told to go and look, a conflicted request sits there until a person resolves it.

Assignments

EventFires whenAlso carries
ApprovalDelegatedAn approver handed their assignment onassignment, approver, delegate, comment
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

On ApproverReassigned, from is null when the record that held the assignment has itself been deleted.

ApproversEscalated is dispatched after the new assignments exist, so recipient calculation can only ever see the new holders. Escalation is automated: the system is the source, not a human actor.

Events fire inside the transaction

Every event this package fires is dispatched inside the transaction that caused it. Two consequences:

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 request it was handed may still be rolled back:

public $afterCommit = true;

The shipped notification listener already defers its send with DB::afterCommit(), for exactly this 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,
    ]);
});

Note that the package already keeps its own append-only history, see audit trail and evidence. Use events to drive your application's reactions, not to reimplement the trail.

Events versus notifications

The notification layer is a thin listener over a subset of these events, and it is off by default. Anything the shipped notifications do not cover, you can reach by listening 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