Skip to main content
Jobs are the fundamental building blocks of Chevere Workflow. Each job wraps an action (or callable) with its arguments, execution mode, dependencies, and conditional logic.

Creating jobs

Jobs are created using the sync() or async() helper functions:
use function Chevere\Workflow\sync;
use function Chevere\Workflow\async;

// Synchronous job (blocking)
$job = sync(
    new MyAction(),
    param1: 'value',
    param2: 123
);

// Asynchronous job (non-blocking)
$job = async(
    new MyAction(),
    param1: 'value',
    param2: 123
);
The difference between sync() and async() is execution mode: synchronous jobs block execution until complete, while asynchronous jobs can run in parallel with other async jobs at the same dependency level.

Job interface

Jobs implement the JobInterface with the following type signature:
src/Interfaces/JobInterface.php
interface JobInterface
{
    public function parameters(): ParametersInterface;
    public function return(): ParameterInterface;
    public function action(): ActionInterface|string|Closure;
    public function arguments(): array;
    public function dependencies(): VectorInterface;
    public function isSync(): bool;
    public function runIf(): VectorInterface;
    public function runIfNot(): VectorInterface;
    public function caller(): CallerInterface;
    public function retryPolicy(): RetryPolicyInterface;
    
    public function withArguments(mixed ...$argument): self;
    public function withRunIf(ResponseReferenceInterface|VariableInterface|callable|bool ...$context): self;
    public function withRunIfNot(ResponseReferenceInterface|VariableInterface|callable|bool ...$context): self;
    public function withIsSync(bool $flag = true): self;
    public function withDepends(string ...$jobs): self;
    public function withRetry(int $timeout = 0, int $maxAttempts = 1, int $delay = 0): self;
}

Action types

Jobs can execute different types of actions:
1

Action instances

Pass an instance of a class implementing ActionInterface:
sync(new Greet(), username: 'World')
2

Action class names

Pass a fully-qualified class name string:
async(Greet::class, username: variable('name'))
3

Closures

Use anonymous functions for inline logic:
sync(function(string $input): string {
    return strtoupper($input);
}, input: 'hello')
4

Callables

Any PHP callable (function names, invokable objects, etc.):
sync('strtoupper', string: 'hello')

Argument passing

Jobs support three types of arguments from src/Job.php:265:

Raw values

Direct PHP values validated against parameter types:
sync(
    new MyAction(),
    name: 'John',      // string
    age: 25,           // int
    active: true       // bool
)

Variables

Runtime values provided when executing the workflow:
use function Chevere\Workflow\variable;

sync(
    new Greet(),
    username: variable('username')
)

Response references

Outputs from other jobs:
use function Chevere\Workflow\response;

workflow(
    fetch: async(new FetchUrl(), url: variable('url')),
    store: async(
        new StoreFile(),
        file: response('fetch'),  // entire response
        dir: variable('saveDir')
    )
)
Arguments can be passed positionally or by name. Named arguments are recommended for clarity.

Dependencies

Jobs automatically infer dependencies from response references, but you can also declare explicit dependencies:
workflow(
    validate: async(new ValidateData(), data: variable('input')),
    process: async(new ProcessData(), data: variable('input'))
        ->withDepends('validate'),  // Wait for validate
    notify: async(new SendNotification())
        ->withDepends('validate', 'process')  // Wait for both
)
From src/Job.php:189-195:
public function withDepends(string ...$jobs): JobInterface
{
    $new = clone $this;
    $new->addDependencies(...$jobs);
    
    return $new;
}

Conditional execution

Jobs can run conditionally using withRunIf() or withRunIfNot():
workflow(
    greet: sync(
        new Greet(),
        username: variable('username')
    )->withRunIf(
        variable('sayHello')  // Only run if sayHello is true
    )
)
Conditional variables must be of type bool. The system validates this at workflow construction time.
From src/Job.php:165-179:
public function withRunIf(ResponseReferenceInterface|VariableInterface|callable|bool ...$context): JobInterface
{
    $new = clone $this;
    $new->pushRunConditional('runIf', ...$context);
    
    return $new;
}

public function withRunIfNot(ResponseReferenceInterface|VariableInterface|callable|bool ...$context): JobInterface
{
    $new = clone $this;
    $new->pushRunConditional('runIfNot', ...$context);
    
    return $new;
}

Retry policy

Configure automatic retries for unreliable operations:
async(new FetchExternalAPI(), url: variable('apiUrl'))
    ->withRetry(
        timeout: 30,        // Max 30 seconds across all attempts
        maxAttempts: 3,     // Try up to 3 times
        delay: 5            // Wait 5 seconds between attempts
    )
From src/Job.php:197-206:
public function withRetry(
    int $timeout = 0,
    int $maxAttempts = 1,
    int $delay = 0
): JobInterface {
    $new = clone $this;
    $new->retryPolicy = new RetryPolicy($timeout, $maxAttempts, $delay);
    
    return $new;
}

Execution modes

Synchronous jobs

Created with sync(), these jobs block execution:
$job = sync(new DatabaseTransaction(), data: variable('data'));
// Equivalent to:
$job = async(new DatabaseTransaction(), data: variable('data'))
    ->withIsSync(true);
Use synchronous jobs when:
  • Order matters critically
  • You need to ensure completion before proceeding
  • Working with stateful operations

Asynchronous jobs

Created with async(), these jobs can run in parallel:
workflow(
    thumb: async(new ImageResize(), file: variable('image'), fit: 'thumbnail'),
    poster: async(new ImageResize(), file: variable('image'), fit: 'poster')
)
Use asynchronous jobs when:
  • Jobs are independent
  • Parallel execution improves performance
  • No strict ordering requirements

Type safety

Jobs enforce type safety through parameter validation from src/Job.php:408-414:
private function assertParameter(string $name, ParameterInterface $parameter, mixed $value): void
{
    if ($value instanceof ResponseReferenceInterface || $value instanceof VariableInterface) {
        return;
    }
    assertNamedArgument($name, $parameter, $value);
}
This ensures:
  • Raw values match expected types
  • Variables and references are validated at runtime
  • Clear error messages for type mismatches

Best practices

1

Use descriptive job names

Job names become identifiers for dependencies and responses:
workflow(
    fetchUser: async(...),      // Good
    processUserData: async(...), // Good
    job1: async(...),           // Avoid
)
2

Prefer async by default

Use async() unless you specifically need blocking behavior:
// Prefer this
async(new ProcessImage(), ...)

// Only when necessary
sync(new AcquireLock(), ...)
3

Let dependencies be inferred

Response references automatically create dependencies:
workflow(
    fetch: async(new Fetch(), ...),
    // No need for withDepends('fetch')
    process: async(new Process(), data: response('fetch'))
)
4

Leverage retry for external calls

Network operations benefit from automatic retries:
async(new APICall(), ...)
    ->withRetry(timeout: 60, maxAttempts: 3, delay: 5)

Build docs developers (and LLMs) love