Workflow Hooks

Alpha notice: Hook invocation order is tied to runtime transactions today. Do not rely on hooks for graph routing — use transitions and DBFlow::reject() strategies instead.

Workflow hooks let your Laravel application react when an instance starts, completes approval, is rejected, or is cancelled. They are host callbacks — not Laravel Event classes and not WorkflowLogEvent rows.

Registration

Register one hook class per workflow key during application boot:

use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Services\WorkflowHooksRegistry;

DBFlow::registerWorkflowHooks(
    app(WorkflowHooksRegistry::class),
    'procurement_request_approval',
    ProcurementRequestWorkflowHooks::class,
);

Refund demo equivalent:

DBFlow::registerWorkflowHooks(
    app(WorkflowHooksRegistry::class),
    'refund_dispute_approval',
    RefundDisputeWorkflowHooks::class,
);

WorkflowHooks contract

use DbflowLabs\Core\Contracts\WorkflowHooks;
use DbflowLabs\Core\Models\WorkflowInstance;

interface WorkflowHooks
{
    public function onStarted(WorkflowInstance $instance): void;
    public function onApproved(WorkflowInstance $instance): void;
    public function onRejected(WorkflowInstance $instance): void;
    public function onCancelled(WorkflowInstance $instance): void;
}

Implement all four methods even when some are no-ops.

Hook reference

Hook Fires when Typical host use
onStarted Instance enters running after DBFlow::start() Set pending_review, stamp submitted_at
onApproved Instance reaches an approved terminal outcome Set approved, unlock downstream actions
onRejected Instance is rejected per runtime strategy Set rejected, notify submitter
onCancelled DBFlow::cancel() succeeds Roll back to draft or mark withdrawn — gate cancel() in the host; Core does not restrict to submitter

Access the business record through $instance->workflowable.

Example: procurement status mapping

From dbflow-demoProcurementRequestWorkflowHooks:

final class ProcurementRequestWorkflowHooks implements WorkflowHooks
{
    public function onStarted(WorkflowInstance $instance): void
    {
        $instance->workflowable?->update([
            'status' => ProcurementRequestStatus::PendingReview,
            'submitted_at' => now(),
        ]);
    }

    public function onApproved(WorkflowInstance $instance): void
    {
        $instance->workflowable?->update([
            'status' => ProcurementRequestStatus::Approved,
            'reviewed_at' => now(),
        ]);
    }

    public function onRejected(WorkflowInstance $instance): void
    {
        $instance->workflowable?->update([
            'status' => ProcurementRequestStatus::Rejected,
            'reviewed_at' => now(),
        ]);
    }

    public function onCancelled(WorkflowInstance $instance): void
    {
        // Demo rolls back to draft unless archived — adjust for production policy
        $instance->workflowable?->update([
            'status' => ProcurementRequestStatus::Draft,
        ]);
    }
}

See Purchase Request Approval for the full status table.

Example: refund outcome mapping

Refund hooks map workflow completion to won / lost business outcomes while investigation status remains host-managed. Implementation: app/DBFlow/RefundDispute/RefundDisputeWorkflowHooks.php in dbflow-demo.

Appropriate uses

  • Mirror workflow outcomes onto Eloquent columns
  • Write domain-specific audit notes outside dbflow_workflow_logs
  • Enqueue notifications or jobs after state changes
  • Unlock Filament actions gated on host status (for example archive after approval)

Avoid in hooks

  • Graph routing — use transitions[].condition and RejectStrategy instead
  • Starting another workflow synchronously — risk nested transactions and duplicate active_key conflicts
  • Heavy external API calls — prefer queued jobs triggered from the hook
  • Silent exception swallowing — failed host updates can desync UI and instance status

Hooks vs audit logs

WorkflowLogger writes immutable rows to dbflow_workflow_logs with WorkflowLogEvent types (workflow_started, task_approved, task_rejected, …). Hooks are for mutable host state and side effects.

Do not confuse WorkflowLogEvent with Laravel's event dispatcher.

Testing hooks

Assert host columns in integration tests after driving DBFlow::start(), approve(), reject(), and cancel(). dbflow-demo procurement and refund suites cover hook side effects — see Testing Workflows.

What's next

Something wrong? Open an issue on GitHub