Skip to main content
Chevere Workflow is a PHP library for building and executing multi-step procedures with automatic dependency resolution. This guide will get you up and running with a working example.

Installation

Install Workflow via Composer:
composer require chevere/workflow

Your first workflow

Let’s create a simple workflow that greets a user:
1

Import the functions

Start by importing the core Workflow functions:
use function Chevere\Workflow\{workflow, sync, variable, run};
2

Define the workflow

Create a workflow with a single job that takes a username:
$workflow = workflow(
    greet: sync(
        fn(string $name): string => "Hello, {$name}!",
        name: variable('username')
    )
);
3

Run the workflow

Execute the workflow and provide the required variables:
$result = run($workflow, username: 'World');
4

Access the response

Get the typed response from the job:
echo $result->response('greet')->string();
// Output: Hello, World!

Chaining jobs with responses

Jobs can pass data to each other using the response() function. This automatically creates dependencies between jobs:
use function Chevere\Workflow\{workflow, sync, variable, response, run};

$workflow = workflow(
    calculate: sync(
        fn(int $a, int $b): int => $a + $b,
        a: 10,
        b: variable('value')
    ),
    format: sync(
        fn(int $result): string => "Result: {$result}",
        result: response('calculate')
    )
);

$run = run($workflow, value: 5);
echo $run->response('format')->string();
// Output: Result: 15
The format job automatically waits for calculate to complete because it uses response('calculate').

Using Action classes

For reusable logic, use Action classes instead of closures:
use Chevere\Action\Action;

class MyAction extends Action
{
    public function __invoke(string $foo): string
    {
        return "Hello, {$foo}";
    }
}
Use the Action class in your workflow:
use function Chevere\Workflow\{response, run, sync, variable, workflow};

$workflow = workflow(
    greet: sync(
        MyAction::class,
        foo: variable('name')
    ),
    repeat: sync(
        MyAction::class,
        foo: response('greet')
    ),
    finish: sync(
        fn(string $foo): string => "Done: {$foo}",
        foo: response('greet')
    )
);

$result = run($workflow, name: 'Chevere');

echo $result->response('greet')->string();
// Output: Hello, Chevere

echo $result->response('repeat')->string();
// Output: Hello, Hello, Chevere

echo $result->response('finish')->string();
// Output: Done: Hello, Chevere

Parallel async execution

Use async() to run independent jobs concurrently:
use function Chevere\Workflow\{async, response, run, variable, workflow};

$workflow = workflow(
    // These three jobs run in parallel
    thumb: async(
        ImageResize::class,
        file: variable('image'),
        fit: 'thumbnail'
    ),
    poster: async(
        ImageResize::class,
        file: variable('image'),
        fit: 'poster'
    ),
    banner: async(
        ImageResize::class,
        file: variable('image'),
        fit: 'banner'
    ),
    // This job waits for all async jobs to complete
    storeThumb: async(
        StoreFile::class,
        file: response('thumb'),
        dir: variable('saveDir')
    )
);

$run = run(
    $workflow,
    image: '/path/to/image.jpg',
    saveDir: '/output/'
);

// View the execution graph
$graph = $run->workflow()->jobs()->graph()->toArray();
// [
//     ['thumb', 'poster', 'banner'],  // Level 0: parallel
//     ['storeThumb']                   // Level 1: after dependencies
// ]
Jobs without dependencies run in parallel, while jobs that use response() wait for their dependencies.

Conditional execution

Control whether a job runs using withRunIf():
use function Chevere\Workflow\{run, sync, variable, workflow};

$workflow = workflow(
    greet: sync(
        fn(string $username): string => "Hello, {$username}!",
        username: variable('username')
    )->withRunIf(
        variable('sayHello')
    )
);

$run = run(
    $workflow,
    username: 'Alice',
    sayHello: true
);

// Check if a job was skipped
if ($run->skip()->contains('greet')) {
    echo "Job was skipped";
} else {
    echo $run->response('greet')->string();
    // Output: Hello, Alice!
}
Run the same workflow with sayHello: false and the job will be skipped.

Next steps

Now that you’ve got the basics, explore these concepts:

Jobs

Learn about sync vs async jobs, Action classes, and job arguments

Variables

Understand runtime variables and how to pass data to workflows

Responses

Access job outputs and chain data between jobs

Dependency injection

Use PSR-11 containers to inject dependencies into Action classes

Build docs developers (and LLMs) love