Hooks
The engine emits lifecycle events through an in-process HookDispatcher
(a container singleton). The package uses it internally for execution logging and
for resuming workflows waiting on a child. You can attach your own listeners for
observability — metrics, tracing, alerting.
use Qanna\WorkflowEngine\Engine\Enums\ExecutionStatus;
use Qanna\WorkflowEngine\Engine\HookDispatcher;
use Qanna\WorkflowEngine\Engine\Hooks\WorkflowFinished;
public function boot(): void
{
$hooks = $this->app->make(HookDispatcher::class);
$hooks->on(WorkflowFinished::NAME, function (WorkflowFinished $event) {
if ($event->status === ExecutionStatus::Failed) {
// notify, record a metric, …
report(new \RuntimeException("Workflow {$event->workflowId} failed: {$event->errorMessage}"));
}
});
}
on($event, $callback, $priority = 100, $once = false) — lower priority runs
first. once() registers a one-shot. off(), flush(), has(), count(),
events() round out the API. A listener returning false halts further
listeners for that event.
Listeners can be closures, [$object, 'method'] arrays, or invokable objects.
Listener parameters
Each hook carries an event object with the data listed below. The engine fills a listener's parameters by type or by name, not by position:
- A parameter typed as the event class (or its base class
Qanna\WorkflowEngine\Engine\Hooks\HookEvent) receives the event object. - Any other parameter receives the event property with the same name.
So these listeners are equivalent, and parameters can appear in any order:
use Qanna\WorkflowEngine\Engine\Hooks\NodeRetrying;
$hooks->on(NodeRetrying::NAME, function (NodeRetrying $event) {
Log::warning("Retrying {$event->nodeId}, attempt {$event->attempt} of {$event->maxAttempts}");
});
$hooks->on(NodeRetrying::NAME, function (int $attempt, int $maxAttempts, string $nodeId) {
Log::warning("Retrying {$nodeId}, attempt {$attempt} of {$maxAttempts}");
});
Ask only for what you need — unused properties are simply not passed. A
parameter that matches neither the event type nor a property keeps its default
value if it has one. If it has no default, dispatching the hook throws an
InvalidArgumentException naming the missing parameter — and because hooks are
dispatched while a workflow runs, that happens mid-execution, so check listener
parameter names against the table below.
Events
Every event class lives under Qanna\WorkflowEngine\Engine\Hooks and exposes
its hook name as a NAME constant, so WorkflowFinished::NAME and
'workflow.finished' are interchangeable.
| Hook | Event class | Properties |
|---|---|---|
workflow.starting | WorkflowStarting | string $workflowId, string $executionId |
workflow.started | WorkflowStarted | string $workflowId, string $executionId, mixed $triggerOutput |
workflow.resumed | WorkflowResumed | string $workflowId, string $executionId |
workflow.node-executing | NodeExecuting | string $workflowId, string $executionId, string $nodeId |
workflow.node-executed | NodeExecuted | string $workflowId, string $executionId, string $nodeId, NodeResult $result |
workflow.node-retrying | NodeRetrying | string $workflowId, string $executionId, string $nodeId, int $attempt, int $maxAttempts |
workflow.node-slow | NodeSlow | string $workflowId, string $executionId, string $nodeId, float $elapsed (seconds), int $timeout (seconds), string $message |
workflow.finished | WorkflowFinished | string $workflowId, string $executionId, ExecutionStatus $status, ?string $errorMessage, Execution $execution |
workflow.completed | WorkflowCompleted | string $workflowId, string $executionId, WorkflowExecution $execution |
workflow.trigger-ignored | TriggerIgnored | string $workflowId, array $payload, ?string $reason |
workflow.node-executedfires for successful, failed, and errored node results alike — inspect theNodeResult.workflow.node-retrying's$attemptis the attempt about to run, counting the first run as attempt 1.workflow.finishedfires whenever an execution stops running — succeeded, failed, cancelled, or suspended.workflow.completedfires once a top-level run or resume is fully finalised (including scheduling any resume), with the storedWorkflowExecutionrecord.
Upgrading: listeners used to receive the values above as positional arguments. Positions no longer matter — rename parameters to match the property names (for example
$error→$errorMessageonworkflow.finished) or type a single parameter as the event class.workflow.node-retryingandworkflow.node-slowno longer pass anarray $meta; use$attempt/$maxAttemptsand$elapsed/$timeout/$messageinstead.
These event names and payloads are not yet a formally frozen public API; treat them as stable-in-practice but verify after upgrades. For reacting to failures specifically, querying
WorkflowExecution::where('status', ExecutionStatus::Failed)is the more conservative choice.