Overview
Durable Workflow uses a state machine to manage workflow execution. Each workflow progresses through a series of states from creation to completion (or failure). Understanding these states is essential for monitoring and debugging workflows.
Workflow States
Workflows can be in one of six states:
Created
The workflow has been instantiated but not yet started. This is the initial state after calling WorkflowStub::make().
Pending
The workflow has been dispatched to the queue and is waiting to be processed by a worker.
Running
The workflow is actively executing its execute() method.
Waiting
The workflow has yielded an activity or timer and is waiting for it to complete.
Completed
The workflow has finished successfully and returned a value.
Failed
The workflow encountered an unrecoverable error.
There’s also a special Continued state for workflows that use the continuation feature.
State Classes
Each state is represented by a class that extends WorkflowStatus:
src/States/WorkflowCreatedStatus.php
final class WorkflowCreatedStatus extends WorkflowStatus
{
public static string $name = 'created' ;
}
src/States/WorkflowPendingStatus.php
final class WorkflowPendingStatus extends WorkflowStatus
{
public static string $name = 'pending' ;
}
src/States/WorkflowRunningStatus.php
final class WorkflowRunningStatus extends WorkflowStatus
{
public static string $name = 'running' ;
}
src/States/WorkflowWaitingStatus.php
final class WorkflowWaitingStatus extends WorkflowStatus
{
public static string $name = 'waiting' ;
}
src/States/WorkflowCompletedStatus.php
final class WorkflowCompletedStatus extends WorkflowStatus
{
public static string $name = 'completed' ;
}
src/States/WorkflowFailedStatus.php
final class WorkflowFailedStatus extends WorkflowStatus
{
public static string $name = 'failed' ;
}
State Transitions
The WorkflowStatus base class defines which transitions are allowed:
src/States/WorkflowStatus.php
abstract class WorkflowStatus extends State
{
public static function config () : StateConfig
{
return parent :: config ()
-> default ( WorkflowCreatedStatus :: class )
-> allowTransition ( WorkflowCreatedStatus :: class , WorkflowPendingStatus :: class )
-> allowTransition ( WorkflowPendingStatus :: class , WorkflowFailedStatus :: class )
-> allowTransition ( WorkflowPendingStatus :: class , WorkflowRunningStatus :: class )
-> allowTransition ( WorkflowRunningStatus :: class , WorkflowCompletedStatus :: class )
-> allowTransition ( WorkflowRunningStatus :: class , WorkflowContinuedStatus :: class )
-> allowTransition ( WorkflowRunningStatus :: class , WorkflowFailedStatus :: class )
-> allowTransition ( WorkflowRunningStatus :: class , WorkflowWaitingStatus :: class )
-> allowTransition ( WorkflowWaitingStatus :: class , WorkflowFailedStatus :: class )
-> allowTransition ( WorkflowWaitingStatus :: class , WorkflowPendingStatus :: class );
}
}
Valid Transitions
Here are the allowed state transitions:
Invalid transitions throw a TransitionNotFound exception. This prevents workflows from entering inconsistent states.
State transitions are performed using the transitionTo() method:
public function transitionTo ( $newState , ... $transitionArgs )
{
$newState = $this -> resolveStateObject ( $newState );
$from = static :: getMorphClass ();
$to = $newState :: getMorphClass ();
if ( ! $this -> stateConfig -> isTransitionAllowed ( $from , $to )) {
throw TransitionNotFound :: make ( $from , $to , $this -> model :: class );
}
$this -> model -> { $this -> field } = $newState ;
$this -> model -> save ();
$model = $this -> model ;
$currentState = $model -> { $this -> field } ?? null ;
if ( $currentState instanceof self ) {
$currentState -> setField ( $this -> field );
}
event ( new StateChanged (
$this ,
$currentState instanceof self ? $currentState : null ,
$this -> model ,
$this -> field
));
return $model ;
}
When a transition occurs:
The new state is validated
The model is updated and saved
A StateChanged event is dispatched
State Transitions in Workflow Execution
Starting a Workflow
When you call start(), the workflow transitions from created to pending:
$workflow = WorkflowStub :: make ( MyWorkflow :: class );
// Status: WorkflowCreatedStatus
$workflow -> start ( $arg1 , $arg2 );
// Status: WorkflowPendingStatus (dispatched to queue)
Running a Workflow
When the queue worker picks up the workflow, it transitions to running:
public function handle () : void
{
// ...
try {
if ( ! $this -> replaying ) {
$this -> storedWorkflow -> status -> transitionTo ( WorkflowRunningStatus :: class );
}
} catch ( TransitionNotFound ) {
if ( $this -> storedWorkflow -> toWorkflow () -> running ()) {
$this -> release ();
}
return ;
}
// ...
}
Waiting for Activities
When the workflow yields an activity, it transitions to waiting:
if ( ! $resolved ) {
if ( ! $this -> replaying ) {
$this -> storedWorkflow -> status -> transitionTo ( WorkflowWaitingStatus :: class );
}
return ;
}
Completing a Workflow
When the workflow finishes, it transitions to completed:
if ( ! $this -> replaying ) {
try {
$return = $this -> coroutine -> getReturn ();
} catch ( Throwable $th ) {
throw new Exception ( 'Workflow failed.' , 0 , $th );
}
if ( $return instanceof ContinuedWorkflow ) {
$this -> storedWorkflow -> status -> transitionTo ( WorkflowContinuedStatus :: class );
return ;
}
$this -> storedWorkflow -> output = Serializer :: serialize ( $return );
$this -> storedWorkflow -> status -> transitionTo ( WorkflowCompletedStatus :: class );
WorkflowCompleted :: dispatch (
$this -> storedWorkflow -> id ,
json_encode ( $return ),
now () -> format ( 'Y-m-d\TH:i:s.u\Z' )
);
}
Failing a Workflow
When an error occurs, the workflow transitions to failed:
public function failed ( Throwable $throwable ) : void
{
try {
$this -> storedWorkflow -> toWorkflow () -> fail ( $throwable );
} catch ( TransitionNotFound ) {
return ;
}
}
Checking Workflow State
You can check a workflow’s current state using the WorkflowStub:
$workflow = WorkflowStub :: load ( $workflowId );
if ( $workflow -> created ()) {
echo "Workflow hasn't started yet" ;
}
if ( $workflow -> running ()) {
echo "Workflow is executing" ;
}
if ( $workflow -> completed ()) {
echo "Workflow finished: " . json_encode ( $workflow -> output ());
}
if ( $workflow -> failed ()) {
echo "Workflow encountered an error" ;
foreach ( $workflow -> exceptions () as $exception ) {
echo $exception -> exception ;
}
}
State Storage
Workflow states are stored in the workflows table using the status column. The StoredWorkflow model uses a custom caster:
src/Models/StoredWorkflow.php
protected $casts = [
'status' => WorkflowStatus :: class ,
];
The status is stored as a string (e.g., “created”, “pending”, “running”) but cast to the appropriate state class when accessed.
State Events
When a state changes, a StateChanged event is dispatched. You can listen to this event for monitoring:
use Workflow\Events\ StateChanged ;
Event :: listen ( StateChanged :: class , function ( $event ) {
Log :: info ( 'Workflow state changed' , [
'workflow_id' => $event -> model -> id ,
'from' => $event -> oldState :: class ,
'to' => $event -> newState :: class ,
]);
});
Replaying and State
During replay, state transitions are skipped:
if ( ! $this -> replaying ) {
$this -> storedWorkflow -> status -> transitionTo ( WorkflowRunningStatus :: class );
}
This prevents duplicate state transitions when reconstructing workflow state from history.
Best Practices
Always check state before operations
Before calling methods like output() or start(), verify the workflow is in the correct state.
Handle transition exceptions
Wrap state transitions in try-catch blocks to gracefully handle invalid transitions.
Listen to StateChanged events for observability and debugging.
Use completed(), failed(), running() instead of comparing status classes directly.
Next Steps
Durability Learn how state is persisted and replayed
WorkflowStub Master workflow interaction methods