Skip to content

Events & hooks

Approval Flows fires domain events at every meaningful transition. Listen to them to run your own side effects — updating the record, posting to Slack, writing to a ledger — without touching the plugin.

The events

All live under Talivio\ApprovalFlows\Events:

Event Fired when Payload
ApprovalSubmitted a record is submitted for approval $approval
ApprovalStepPassed a step is satisfied and the run advances $approval, $step
ApprovalCompleted the final step passes; run is approved $approval
ApprovalRejected any approver rejects; run ends $approval
ApprovalEscalated a pending step crosses its escalation window $approval, $step

Each event exposes the Approval model. From there you can reach the underlying record via $approval->approvable, the flow via $approval->flow, and the audit log via $approval->actions.

Listening

use Talivio\ApprovalFlows\Events\ApprovalCompleted;
use Illuminate\Support\Facades\Event;

Event::listen(ApprovalCompleted::class, function (ApprovalCompleted $event) {
    $record = $event->approval->approvable; // e.g. your PurchaseOrder

    $record->update(['status' => 'approved']);
});

Or register a dedicated listener class in your AppServiceProvider / EventServiceProvider as usual.

A common pattern: mirror status onto the record

The plugin tracks approval state in its own tables and never mutates your model. If you want an approved/rejected column on the record itself, wire both outcomes:

Event::listen(ApprovalCompleted::class, fn ($e) =>
    $e->approval->approvable->update(['status' => 'approved']));

Event::listen(ApprovalRejected::class, fn ($e) =>
    $e->approval->approvable->update(['status' => 'rejected']));

Reading state without events

For synchronous checks you don't need events — the trait helpers query current state directly:

$record->approvalStatus();     // ApprovalStatus enum or null
$record->isPendingApproval();  // bool
$record->isApproved();         // bool
$record->latestApproval();     // Approval model
$record->approvals;            // full history (MorphMany)

The audit trail

Every action is an ApprovalAction row with an ApprovalActionType (approved, rejected, delegated, commented, escalated, cancelled), the acting user_id, the approval_step_id, an optional comment, and timestamps. Build a timeline straight from $approval->actions (newest first).

Programmatic actions

Beyond submitForApproval(), drive the run through ApprovalManager:

$manager = app(\Talivio\ApprovalFlows\ApprovalManager::class);

$manager->approve($approval, $user, comment: 'LGTM');
$manager->reject($approval, $user, comment: 'Over budget');
$manager->delegate($approval, from: $user, to: $deputy);
$manager->cancel($approval, $user); // submitter only

Each guards its preconditions and throws an ApprovalException when the user may not act, the run isn't pending, or (for cancel) the caller isn't the submitter.

Next: Multi-tenancy.