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:
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
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
)
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'))
)
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!
)
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:
$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:
thumb and poster run in parallel (both use variable('image'))
storeThumb waits for thumb
storePoster waits for poster
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