Skip to content

Child workflow signaling#290

Merged
rmcdaniel merged 5 commits intomasterfrom
feature/child-workflow-signaling
Dec 1, 2025
Merged

Child workflow signaling#290
rmcdaniel merged 5 commits intomasterfrom
feature/child-workflow-signaling

Conversation

@rmcdaniel
Copy link
Member

This PR introduces a safe mechanism for parent workflows to signal their children through two new features:

1. ChildWorkflowHandle Class

A new ChildWorkflowHandle class that wraps a child workflow and provides context-safe signaling:

  • Context Preservation: Automatically saves and restores parent workflow context when forwarding signals to children
  • Replay Safety: Skips re-signaling during workflow replay to avoid duplicate signals
  • Simple API: Uses __call() magic method to forward any signal method to the child

2. Workflow Methods: child() and children()

Two new methods added to the base Workflow class:

  • child(): Returns a ChildWorkflowHandle for the most recently created child workflow at the current execution index
  • children(): Returns an array of ChildWorkflowHandle instances for all child workflows at the current execution index

Both methods are deterministic - they filter children by parent_index <= $this->index, ensuring consistent results during workflow replay.

Usage Examples

Example 1: Parent Directly Signals Child

public function execute()
{
    // Start child (don't yield yet)
    $childPromise = ChildWorkflowStub::make(ChildWorkflow::class, 'arg');
    
    // Get handle and signal child
    $childHandle = $this->child();
    $childHandle->approve('approved');
    
    // Wait for child to complete
    $result = yield $childPromise;
    return $result;
}

Example 2: Parent Forwards External Signal to Child

class ParentWorkflow extends Workflow
{
    private bool $receivedSignal = false;
    private ?string $signalData = null;

    #[SignalMethod]
    public function externalSignal(string $data): void
    {
        $this->receivedSignal = true;
        $this->signalData = $data;
    }

    public function execute()
    {
        // Start child
        $childPromise = ChildWorkflowStub::make(ChildWorkflow::class);
        
        // Get child handle (called once in execute, saved to local variable)
        $childHandle = $this->child();
        
        // Wait for external signal
        yield WorkflowStub::await(fn () => $this->receivedSignal);
        
        // Forward signal to child using saved handle
        if ($childHandle && $this->signalData) {
            $childHandle->processData($this->signalData);
        }
        
        // Wait for child completion
        $result = yield $childPromise;
        return $result;
    }
}

@codecov
Copy link

codecov bot commented Nov 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (57b9c08) to head (bc4feec).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##              master      #290   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity       316       323    +7     
===========================================
  Files             46        47    +1     
  Lines           1327      1355   +28     
===========================================
+ Hits            1327      1355   +28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This comment was marked as resolved.

@rmcdaniel rmcdaniel merged commit fad469b into master Dec 1, 2025
3 checks passed
@rmcdaniel rmcdaniel deleted the feature/child-workflow-signaling branch December 1, 2025 01:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants