Queuety
Workflows

Sub-workflows

A workflow can spawn a child workflow as one of its steps. The parent pauses until the sub-workflow completes, then merges the child's final state into the parent's state.

The ->sub_workflow() method

use Queuety\Queuety;

$enrichment = Queuety::workflow( 'enrich_user' )
    ->then( FetchSocialHandler::class )
    ->then( FetchCreditScoreHandler::class );

Queuety::workflow( 'onboarding' )
    ->then( CreateAccountHandler::class )
    ->sub_workflow( 'enrich_user', $enrichment )
    ->then( SendWelcomeEmailHandler::class )
    ->dispatch( [ 'email' => 'user@example.com' ] );

The sub_workflow() method takes two arguments:

  1. A name for the sub-workflow
  2. A WorkflowBuilder instance defining the sub-workflow's steps

How it works

When the parent workflow reaches a sub_workflow step:

  1. The sub-workflow is dispatched as an independent workflow with its own ID
  2. The parent workflow pauses
  3. The sub-workflow runs through its steps normally
  4. When the sub-workflow completes, its final state is merged into the parent's state
  5. The parent workflow resumes with the next step

Parent/child state

The sub-workflow receives the parent's current accumulated state as its initial payload. This means child steps can access any data produced by earlier parent steps:

class CreateAccountHandler implements Step {
    public function handle( array $state ): array {
        $user_id = wp_create_user( $state['email'], wp_generate_password() );
        return [ 'user_id' => $user_id ];
    }

    public function config(): array {
        return [ 'needs_wordpress' => true ];
    }
}

// This sub-workflow step can access 'user_id' from the parent
class FetchSocialHandler implements Step {
    public function handle( array $state ): array {
        $profile = fetch_social_profile( $state['user_id'] );
        return [ 'social_data' => $profile ];
    }

    public function config(): array {
        return [];
    }
}

State merging

When the sub-workflow completes, its final state (excluding internal keys like _steps, _queue, _priority, _max_attempts) is merged into the parent's state. The next parent step sees all data from both the parent's earlier steps and the sub-workflow:

class SendWelcomeEmailHandler implements Step {
    public function handle( array $state ): array {
        // Has access to:
        // - $state['email'] (from initial dispatch)
        // - $state['user_id'] (from CreateAccountHandler)
        // - $state['social_data'] (from sub-workflow's FetchSocialHandler)
        // - $state['credit_score'] (from sub-workflow's FetchCreditScoreHandler)

        send_welcome_email( $state['email'], $state['social_data'] );
        return [ 'welcome_sent' => true ];
    }

    public function config(): array {
        return [ 'needs_wordpress' => true ];
    }
}

Sub-workflow options

The sub-workflow builder supports the same options as a regular workflow:

$sub = Queuety::workflow( 'heavy_processing' )
    ->on_queue( 'background' )
    ->with_priority( Priority::Normal )
    ->max_attempts( 5 )
    ->then( StepA::class )
    ->then( StepB::class );

Queuety::workflow( 'parent' )
    ->sub_workflow( 'heavy', $sub )
    ->then( FinalStep::class )
    ->dispatch( $payload );

Nested sub-workflows

Sub-workflows can themselves contain sub-workflows, allowing arbitrary nesting:

$inner = Queuety::workflow( 'inner' )
    ->then( InnerStepA::class )
    ->then( InnerStepB::class );

$middle = Queuety::workflow( 'middle' )
    ->then( MiddleStepA::class )
    ->sub_workflow( 'inner', $inner )
    ->then( MiddleStepB::class );

Queuety::workflow( 'outer' )
    ->then( OuterStepA::class )
    ->sub_workflow( 'middle', $middle )
    ->then( OuterStepB::class )
    ->dispatch( $payload );

On this page