Execution Flow
This page traces the complete lifecycle of a workflow from dispatch to completion, using an order processing workflow as an example.
Overview
Event Dispatch -> Instance Creation -> Step Loop -> Transition -> CompletionPhase 1: Dispatch
When you call Workflowable::dispatch($event) or Workflowable::dispatchSync($event):
- The engine looks up all active
Workflowdefinitions whoseevent_namematches the event - For each matching workflow, a
WorkflowInstanceis created with:status: pendingcurrent_step: initial_stepfrom the definitionworkflow_event: the serialized WorkflowEvent DTOstate: the event's constructor parameters as key-value pairs
- A
WorkflowWasCreateddomain event is recorded (Spatie event sourcing) - A
WorkflowStartedLaravel event is dispatched
For async dispatch, each instance is wrapped in an ExecuteWorkflowJob and pushed to the queue.
Phase 2: Step Execution
The engine enters a loop, executing steps until it hits a terminal state:
- Resolve step type - The step's
typefield is looked up in theStepTypeRegistry - Create StepExecution - A record is created to track this execution attempt
- Dispatch StepStarted - Laravel event fired
- Execute the handler:
- action: Resolves the handler class and calls it with the
ExecutionContext - conditional: Resolves the registered conditional handler and calls
evaluate()with theExecutionContext - custom: Delegates to the registered
StepTypeimplementation
- action: Resolves the handler class and calls it with the
- Record result - Output is merged into instance state,
StepCompletedorStepFaileddispatched
ExecutionContext
The handler receives an ExecutionContext that provides:
$context->getVariable('orderId'); // Get a single variable
$context->getVariables(); // Get all variables
$context->getInstance(); // Get the WorkflowInstance model
$context->getStepDefinition(); // Get the current step's definition arrayStep Results
Handlers return arrays that get merged into the workflow's state:
class ProcessOrder
{
public function handle(ExecutionContext $context): array
{
// Business logic...
return ['processed' => true, 'receipt_id' => 'RCP-123'];
}
}After this step, $context->getVariable('processed') returns true in subsequent steps.
Phase 3: Transition
After a step completes:
- The engine looks up the transition for the current step
- Simple transition:
'process_order' => 'check_amount'always goes tocheck_amount - Conditional transition:
'check_amount' => ['true' => 'a', 'false' => 'b']routes based on the step's result - The
current_stepis updated and aTransitionTriggeredevent is dispatched - The loop continues with the next step
Phase 4: Completion
The workflow ends when it transitions to a terminal step:
'completed'- Instance status set tocompleted,WorkflowCompletedevent dispatched'failed'- Instance status set tofailed,WorkflowFailedevent dispatched
If no valid transition exists, the workflow fails.
Retry Behavior
If a step fails and has a retry policy:
StepRetryingevent is dispatched with the attempt number and delay- The step is re-executed after the configured delay
- This continues up to
max_attempts - If all attempts fail,
StepFailedis dispatched withwillRetry: false
Safety Limits
The engine enforces a maximum step count per execution run (configurable via execution.max_steps in config, default: 1000) to prevent infinite loops.