Workflow
Declarative workflow engine for PHP with automatic dependency resolution, sync/async job execution, and type-safe response chaining.
Install / Use
/learn @chevere/WorkflowREADME
Workflow
Summary
Chevere Workflow is a PHP library for building and executing multi-step procedures with automatic dependency resolution. Define independent jobs that can run synchronously or asynchronously, pass data between them using typed responses, and let the engine handle execution order automatically.
Key features:
- Declarative job definitions: Define what to do, not how to orchestrate it
- Automatic dependency graph: Jobs execute in optimal order based on their dependencies
- Sync and async execution: Mix blocking and non-blocking jobs freely
- Type-safe responses: Access job outputs with full type safety
- Conditional execution: Run jobs based on variables or previous responses
- Built-in retry policies: Handle transient failures automatically
- Testable: Each job is independently testable and workflow graph can be verified
You define jobs and how they connect and depend on each other, Chevere Workflow figures out the execution order and runs them accordingly.
Integrations
- VS Code Extension: Complete language server support plus graph visualization
- Laravel Integration: Package for integrating with Laravel applications
Installing
Workflow is available through Packagist and the repository source is at chevere/workflow.
composer require chevere/workflow
Quick Start
Here's a minimal example to get you started:
use function Chevere\Workflow\{workflow, sync, variable, run};
// 1. Define a workflow with jobs
$workflow = workflow(
greet: sync(
fn(string $name): string => "Hello, {$name}!",
name: variable('username')
)
);
// 2. Run with variables
$result = run($workflow, username: 'World');
// 3. Get typed responses
echo $result->response('greet')->string();
// Output: Hello, World!
Workflow Provider Convention
Implement WorkflowProviderInterface to expose a workflow definition from a class:
use Chevere\Workflow\Interfaces\WorkflowProviderInterface;
use Chevere\Workflow\Interfaces\WorkflowInterface;
class MyProvider implements WorkflowProviderInterface
{
public static function workflow(): WorkflowInterface
{
return workflow(/* ... */);
}
}
This is the recommended pattern for packages and applications. It separates workflow configuration from execution logic and enables discovery by tooling such as the Chevere Workflow VSCode extension.
Core Concepts
Workflow is built around four main concepts:
| Concept | Description | | ------------ | --------------------------------------------------- | | Job | A unit of work that produces a response | | Variable | External input provided when running the workflow | | Response | Reference to output from a previous job | | Graph | Automatic execution order based on job dependencies |
How It Works
- You define jobs using
sync()orasync()functions - Jobs declare their inputs: literal values,
variable()references, orresponse()from other jobs - The engine builds a dependency graph automatically
- Jobs execute in optimal order (parallel when possible)
- Access typed responses after execution
Functions Reference
| Function | Purpose |
| ------------ | ----------------------------------------- |
| workflow() | Create a workflow from named jobs |
| sync() | Create a synchronous (blocking) job |
| async() | Create an asynchronous (non-blocking) job |
| variable() | Declare a runtime variable |
| response() | Reference another job's output |
| run() | Execute a workflow with variables |
Jobs
Jobs are the building blocks of a workflow. Each job wraps an executable unit (Action, Closure, Invocable class, or any PHP callable) and declares its input arguments.
Creating Jobs with Closures
Use closures for simple, inline operations:
use function Chevere\Workflow\{workflow, sync, async, variable, response, run};
$workflow = workflow(
// Simple calculation
add: sync(
fn(int $a, int $b): int => $a + $b,
a: 10,
b: variable('number')
),
// Format the result
format: sync(
fn(int $sum): string => "Sum: {$sum}",
sum: response('add')
)
);
$result = run($workflow, number: 5);
echo $result->response('format')->string(); // Sum: 15
Creating Jobs with Action Classes
For complex or reusable logic, use Action classes as these additionally support method definitions for acceptParameters() and acceptReturn() to define parameter and return rules that are automatically applied at runtime.
use Chevere\Action\Action;
class FetchUser extends Action
{
public function __invoke(int $userId): array
{
// Fetch user from database
return ['id' => $userId, 'name' => 'John', 'email' => 'john@example.com'];
}
}
class SendEmail extends Action
{
public function __invoke(string $email, string $subject): bool
{
// Send email logic
return true;
}
}
$workflow = workflow(
user: sync(
FetchUser::class,
userId: variable('id')
),
notify: sync(
SendEmail::class,
email: response('user', 'email'),
subject: 'Welcome!'
)
);
$result = run($workflow, id: 123);
Creating Jobs with Invocable Classes
Use invocable classes (classes with __invoke method) for reusable logic without needing Action base class:
class CalculateTotal
{
public function __invoke(array $items, float $taxRate): float
{
$subtotal = array_sum(array_column($items, 'price'));
return $subtotal * (1 + $taxRate);
}
}
class FormatCurrency
{
public function __invoke(float $amount, string $currency = 'USD'): string
{
return $currency . ' ' . number_format($amount, 2);
}
}
$workflow = workflow(
total: sync(
CalculateTotal::class,
items: variable('items'),
taxRate: 0.08
),
formatted: sync(
FormatCurrency::class,
amount: response('total'),
currency: 'EUR'
)
);
$result = run($workflow, items: [
['name' => 'Item 1', 'price' => 10.00],
['name' => 'Item 2', 'price' => 20.00]
]);
echo $result->response('formatted')->string(); // EUR 32.40
Creating Jobs with Callables
Use any PHP callable including array callbacks, function names, or static methods:
class StringHelper
{
public static function uppercase(string $text): string
{
return strtoupper($text);
}
public function reverse(string $text): string
{
return strrev($text);
}
}
$helper = new StringHelper();
$workflow = workflow(
// Using built-in PHP function
trim: sync(
'trim',
string: variable('input')
),
// Using static method
upper: sync(
[StringHelper::class, 'uppercase'],
text: response('trim')
),
// Using instance method
reversed: sync(
[$helper, 'reverse'],
text: response('upper')
)
);
$result = run($workflow, input: ' hello ');
echo $result->response('reversed')->string(); // OLLEH
Sync vs Async Jobs
Synchronous jobs (sync) block execution until complete. Use for operations that must run in sequence:
workflow(
first: sync(ActionA::class), // Runs first
second: sync(ActionB::class), // Waits for first
third: sync(ActionC::class), // Waits for second
);
// Graph: first →
