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:
Action instances
Pass an instance of a class implementing ActionInterface:sync(new Greet(), username: 'World')
Action class names
Pass a fully-qualified class name string:async(Greet::class, username: variable('name'))
Closures
Use anonymous functions for inline logic:sync(function(string $input): string {
return strtoupper($input);
}, input: 'hello')
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
Use descriptive job names
Job names become identifiers for dependencies and responses:workflow(
fetchUser: async(...), // Good
processUserData: async(...), // Good
job1: async(...), // Avoid
)
Prefer async by default
Use async() unless you specifically need blocking behavior:// Prefer this
async(new ProcessImage(), ...)
// Only when necessary
sync(new AcquireLock(), ...)
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'))
)
Leverage retry for external calls
Network operations benefit from automatic retries:async(new APICall(), ...)
->withRetry(timeout: 60, maxAttempts: 3, delay: 5)