Skip to main content
Response references enable data flow between jobs in a workflow. They allow you to use the output of one job as input to another, automatically establishing execution dependencies.

Creating response references

Use the response() helper function to reference job outputs:
use function Chevere\Workflow\response;

// Reference entire job response
$fullResponse = response('fetchUser');

// Reference specific response key
$userId = response('fetchUser', 'id');
$userName = response('fetchUser', 'name');

Response interface

Response references implement ResponseReferenceInterface from src/Interfaces/ResponseReferenceInterface.php:22-40:
interface ResponseReferenceInterface extends Stringable
{
    /**
     * Provides the name/identifier of the job whose response is being referenced.
     */
    public function job(): string;

    /**
     * Provides the optional key to access a specific field within the job's response.
     *
     * When null, the entire response from the job is referenced.
     * When provided, only the specified field/property of the response is referenced.
     */
    public function key(): ?string;
}
The implementation from src/ResponseReference.php:19-56:
final class ResponseReference implements ResponseReferenceInterface
{
    public function __construct(
        private string $job,
        private ?string $key = null,
    ) {
        $this->assertArgument($job, 100);
        if ($key === null) {
            return;
        }
        $this->assertArgument($key, 101);
    }

    public function __toString(): string
    {
        return match ($this->key) {
            null => $this->job,
            default => "{$this->job}:{$this->key}",
        };
    }

    public function job(): string
    {
        return $this->job;
    }

    public function key(): ?string
    {
        return $this->key;
    }
}

Using response references

Entire response

Reference the complete output of a job:
demo/image-resize.php
use Chevere\Demo\Actions\ImageResize;
use Chevere\Demo\Actions\StoreFile;
use function Chevere\Workflow\async;
use function Chevere\Workflow\response;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

$workflow = workflow(
    thumb: async(
        new ImageResize(),
        file: variable('image'),
        fit: 'thumbnail',
    ),
    storeThumb: async(
        new StoreFile(),
        file: response('thumb'),  // Entire response from thumb job
        dir: variable('saveDir'),
    ),
);

Specific response keys

Access individual fields from structured responses:
workflow(
    fetchUser: async(
        new FetchUser(),
        id: variable('userId')
    ),
    // Response might be: ['id' => 123, 'name' => 'Alice', 'email' => '[email protected]']
    
    sendEmail: async(
        new SendEmail(),
        to: response('fetchUser', 'email'),    // Just the email field
        name: response('fetchUser', 'name'),   // Just the name field
    )
)

Automatic dependency inference

Response references automatically create job dependencies. From src/Job.php:416-426:
private function inferDependencies(mixed $argument): void
{
    $condition = $argument instanceof ResponseReferenceInterface;
    if (! $condition) {
        return;
    }
    if ($this->dependencies->contains($argument->job())) {
        return;
    }
    $this->dependencies = $this->dependencies->withPush($argument->job());
}
Example:
workflow(
    fetch: async(new Fetch(), url: variable('url')),
    process: async(
        new Process(),
        data: response('fetch')  // Automatically depends on 'fetch'
    ),
    store: async(
        new Store(),
        data: response('process')  // Automatically depends on 'process'
    )
)
// Execution order: fetch → process → store
You don’t need to use withDepends() when using response references - dependencies are created automatically.

Response type validation

The workflow validates that response types match parameter expectations. From src/Jobs.php:236-296:
private function mapParameter(
    string $argument,
    string $collection,
    ParameterInterface $parameter,
    VariableInterface|ResponseReferenceInterface $value,
): void {
    $map = $this->{$collection};
    $subject = 'Response';
    $identifier = strval($value);
    if ($value instanceof VariableInterface) {
        $subject = 'Variable';
    } else {
        try {
            $responseJob = $this->map->get($value->job());
            $accept = $responseJob->return();
            if ($value->key() !== null) {
                $found = false;
                if ($accept instanceof ObjectParameterInterface) {
                    $className = $accept->className();
                    try {
                        $reflection = (new ReflectionClass($className))->getProperty($value->key());
                    } catch (ReflectionException) {
                        throw new LogicException(
                            (string) message(
                                "Invalid response reference **%response%** as job **%job%** doesn't define such property",
                                job: $value->job(),
                                response: strval($value),
                            )
                        );
                    }
                    // ... validation continues
                }
            }
        } catch (OutOfBoundsException) {
            throw new OutOfBoundsException(
                (string) message(
                    '%subject% **%key%** not found',
                    subject: $subject,
                    key: $identifier
                )
            );
        }
    }
}

Response key access patterns

Response keys work with different return types:

Array responses

// Action returns: ['id' => 1, 'name' => 'Alice']
response('job', 'id')    // Access 'id' key
response('job', 'name')  // Access 'name' key

Object responses

// Action returns object with public properties
class User {
    public int $id;
    public string $name;
    public string $email;
}

response('fetchUser', 'id')     // Access $id property
response('fetchUser', 'email')  // Access $email property
Only public properties of objects can be accessed via response references. Private and protected properties will throw an error.

Parameters interface

For actions returning types implementing ParametersAccessInterface:
// Action returns structured data
response('job', 'fieldName')  // Access defined parameter

Response tracking

The workflow tracks all response references from src/Jobs.php:154-181:
private function storeReferences(string $job, JobInterface $item): void
{
    $return = $item->return();
    $this->references = $this->references
        ->withPut(
            strval(response($job)),
            $return,
        );
    if ($return instanceof ObjectParameterInterface) {
        $properties = (new ReflectionClass($return->className()))->getProperties(ReflectionProperty::IS_PUBLIC);
        foreach ($properties as $property) {
            $this->references = $this->references
                ->withPut(
                    strval(response($job, $property->getName())),
                    reflectionToParameter($property),
                );
        }
    }
    if ($return instanceof ParametersAccessInterface && ! ($return instanceof UnionParameterInterface)) {
        foreach ($return->parameters() as $key => $parameter) {
            $this->references = $this->references
                ->withPut(
                    strval(response($job, $key)),
                    $parameter,
                );
        }
    }
}

Using in conditionals

Response references can control job execution with boolean responses:
workflow(
    validate: async(
        new ValidateInput(),
        data: variable('input')
    ),
    // Returns: ['isValid' => true/false, 'errors' => [...]]
    
    process: async(
        new ProcessData(),
        data: variable('input')
    )->withRunIf(
        response('validate', 'isValid')  // Only run if validation passed
    ),
    
    notifyError: async(
        new NotifyError(),
        errors: response('validate', 'errors')
    )->withRunIfNot(
        response('validate', 'isValid')  // Only run if validation failed
    )
)
From src/Jobs.php:398-428, boolean responses are validated:
private function handleRunIfReference(mixed $runIf): void
{
    if (! $runIf instanceof ResponseReferenceInterface) {
        return;
    }
    $return = $this->map->get($runIf->job())->return();
    if ($runIf->key() !== null) {
        if (! $return instanceof ParametersAccessInterface) {
            throw new OutOfBoundsException(
                (string) message(
                    'Response **%response%** job `%job%` doesn\'t bind to `%parameter%` parameter',
                    response: strval($runIf),
                    job: $runIf->job(),
                    parameter: $runIf->key()
                )
            );
        }
        $return = $return->parameters()->get($runIf->key());
    }
    if ($return->type()->primitive() === 'bool') {
        return;
    }
    
    throw new TypeError(
        (string) message(
            'Response **%response%** must be of type `bool`, `%type%` provided',
            response: strval($runIf),
            type: $return->type()->primitive()
        )
    );
}

Accessing responses at runtime

After workflow execution, retrieve responses from the run object:
$run = run($workflow, input: 'data');

// Get response for a job
$result = $run->response('jobName');

// Access typed value
$stringValue = $result->string();
$intValue = $result->int();
$arrayValue = $result->array();

Best practices

1

Use specific keys when possible

Prefer specific response keys over entire responses:
// Better: Only pass what's needed
sendEmail: async(
    new SendEmail(),
    to: response('user', 'email')
)

// Avoid: Passing entire object when only email is needed
sendEmail: async(
    new SendEmail(),
    user: response('user')  // Then extract email in action
)
2

Chain logically related jobs

Use responses to create clear data flows:
workflow(
    fetch: async(new FetchData(), ...),
    validate: async(new ValidateData(), data: response('fetch')),
    transform: async(new TransformData(), data: response('validate')),
    store: async(new StoreData(), data: response('transform'))
)
3

Leverage automatic dependencies

Let response references create dependencies instead of manual withDepends():
// Good: Dependencies inferred from responses
workflow(
    a: async(new A()),
    b: async(new B(), input: response('a'))
)

// Redundant: Don't add withDepends when using responses
workflow(
    a: async(new A()),
    b: async(new B(), input: response('a'))
        ->withDepends('a')  // Unnecessary!
)
4

Design actions with structured returns

Return arrays or objects with named fields for better response access:
// Good: Structured return
return [
    'success' => true,
    'userId' => $id,
    'message' => 'User created'
];

// Allows:
response('createUser', 'userId')
response('createUser', 'success')

Parallel job responses

Multiple jobs can consume the same response in parallel:
demo/image-resize.php
$workflow = workflow(
    thumb: async(
        new ImageResize(),
        file: variable('image'),
        fit: 'thumbnail',
    ),
    poster: async(
        new ImageResize(),
        file: variable('image'),
        fit: 'poster',
    ),
    storeThumb: async(
        new StoreFile(),
        file: response('thumb'),
        dir: variable('saveDir'),
    ),
    storePoster: async(
        new StoreFile(),
        file: response('poster'),
        dir: variable('saveDir'),
    )
);
Execution:
  1. thumb and poster run in parallel (both use variable('image'))
  2. storeThumb waits for thumb
  3. storePoster waits for poster
  4. storeThumb and storePoster can run in parallel
  • Jobs - Jobs that produce and consume responses
  • Variables - Runtime inputs vs job outputs
  • Execution graph - How responses create dependencies

Build docs developers (and LLMs) love