PHP code example of chevere / workflow

1. Go to this page and download the library: Download chevere/workflow library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

chevere / workflow example snippets


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!

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

use Chevere\Action\Action;

class FetchUser extends Action
{
    public function __invoke(int $userId): array
    {
        // Fetch user from database
        return ['id' => $userId, 'name' => 'John', 'email' => '[email protected]'];
    }
}

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);

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

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

workflow(
    first: sync(ActionA::class),  // Runs first
    second: sync(ActionB::class), // Waits for first
    third: sync(ActionC::class),  // Waits for second
);
// Graph: first → second → third

workflow(
    resize1: async(ResizeImage::class, size: 'thumb'),
    resize2: async(ResizeImage::class, size: 'medium'),
    resize3: async(ResizeImage::class, size: 'large'),
    store: sync(StoreFiles::class, files: response('resize1'), ...)
);
// Graph: [resize1, resize2, resize3] → store

workflow(
    example: sync(
        MyAction::class,
        literal: 'fixed value',           // Literal value
        dynamic: variable('userInput'),   // Runtime variable
        chained: response('otherJob'),    // Previous job output
    )
);

$workflow = workflow(
    job1: sync(
        SomeAction::class,
        name: variable('userName'),
        age: variable('userAge')
    )
);

// Provide values when running
$result = run($workflow, userName: 'Alice', userAge: 30);

$workflow = workflow(
    fetch: sync(
        FetchData::class,
        url: variable('endpoint')
    ),
    process: sync(
        ProcessData::class,
        data: response('fetch')  // Gets entire response from 'fetch'
    ),
    extract: sync(
        ExtractField::class,
        value: response('fetch', 'items')  // Gets 'items' key from response
    )
);

response('user')           // job:user       Entire response object
response('user', 'id')     // job:user->id   id property from object response
response('post', 'id')     // job:post['id'] id key from array response

$workflow = workflow(
    // Independent async jobs run in parallel
    thumb: async(ImageResize::class, size: 'thumb', file: variable('image')),
    medium: async(ImageResize::class, size: 'medium', file: variable('image')),
    large: async(ImageResize::class, size: 'large', file: variable('image')),
    // Sync job waits for all above
    store: sync(
        StoreFiles::class,
        thumb: response('thumb'),
        medium: response('medium'),
        large: response('large')
    )
);
$graph = $workflow->jobs()->graph()->toArray();
// [
//     ['thumb', 'medium', 'large'],  // Level 0: parallel
//     ['store']                      // Level 1: after dependencies
// ]

$workflow = workflow(
    ja: async(
        fn (): int => 1
    ),
    jb: async(
        fn (): int => 2
    )
        ->withRunIf(response('ja'))
        ->withRunIfNot(variable('var')),
    j1: async(
        #[_return(new _arrayp(
            id: new _int(),
            name: new _string()
        ))]
        fn (): array => [
            'id' => 123,
            'name' => 'example',
        ]
    ),
    j2: sync(
        fn (int $n, string $m): int => $n + $m,
        n: response('j1', 'id'),
        m: response('j1', 'name')
    ),
    j3: sync(
        fn (int $a): int => $a,
        a: response('jb')
    ),
    j4: sync(
        fn (int $i, int $j): int => $i * $j,
        i: response('j2'),
        j: response('j3')
    ),
);
$mermaid = Mermaid::generate($workflow);

use function Chevere\Workflow\run;

// Basic execution
$result = run($workflow, var1: 'value1', var2: 'value2');

// With dependency injection container
$result = run($workflow, $container, var1: 'value1');

$result = run($workflow, ...);

// Get typed responses
$result->response('jobName')->string();     // string
$result->response('jobName')->int();        // int
$result->response('jobName')->float();      // float
$result->response('jobName')->bool();       // bool
$result->response('jobName')->array();      // array

// Access array keys directly
$result->response('jobName', 'key')->string();
$result->response('jobName', 'nested', 'key')->int();

if ($result->skip()->contains('optionalJob')) {
    // Job was skipped
}

use Chevere\Workflow\Interfaces\WorkflowProviderInterface;
use Chevere\Workflow\Interfaces\WorkflowInterface;

class MyProvider implements WorkflowProviderInterface
{
    public static function workflow(): WorkflowInterface
    {
        return workflow(/* ... */);
    }
}

use Chevere\Workflow\WorkflowDiscovery;

$discovery = WorkflowDiscovery::fromDirectory('/path/to/src');

// `workflow()` providers e.g. [OrderWorkflow::class, UserWorkflow::class, ...]
$workflowProviders = $discovery->providers();

// Job action classes e.g. [UserCreate::class, OrderCreate::class, ...]
$workflowDependencies = $discovery->dependencies();

$discovery->build('/path/to/build');

$discovery = WorkflowDiscovery::fromBuild('/path/to/build');

$dependencies = new Dependencies(
    ...$discovery->dependencies(),
    ...$discovery->providers()
);
$dependencies->assert($container);

foreach ($discovery->dependencies() as $class) {
    if (! $container->has($class)) {
        throw new RuntimeException("Missing dependency: {$class}");
    }
}

use Chevere\Container\Container; // or any PSR-11 container
use function Chevere\Workflow\run;

// Create container with dependencies
$container = new Container(
    logger: new Logger(),
    database: new Database()
);

// Run workflow with container
// When using chevere/container it will auto-inject and assert
$result = run($workflow, $container, ...$vars);

use Chevere\Action\Action;

class SendNotification extends Action
{
    // Dependencies injected automatically
    public function __construct(
        private LoggerInterface $logger,
        private MailerInterface $mailer
    ) {}

    public function __invoke(string $email, string $message): bool
    {
        $this->logger->info("Sending email to {$email}");
        return $this->mailer->send($email, $message);
    }
}

// Provide dependencies in container
$container = new Container(
    logger: new ConsoleLogger(),
    mailer: new SmtpMailer()
);

$workflow = workflow(
    notify: sync(
        SendNotification::class,  // Dependencies auto-injected
        email: variable('userEmail'),
        message: 'Welcome!'
    )
);

$result = run($workflow, $container, userEmail: '[email protected]');

class ProcessOrder
{
    // Dependencies injected automatically
    public function __construct(
        private DatabaseInterface $database,
        private PaymentGateway $payment
    ) {}

    public function __invoke(int $orderId, float $amount): array
    {
        $order = $this->database->getOrder($orderId);
        $result = $this->payment->charge($amount);
        return ['order' => $order, 'payment' => $result];
    }
}

// Provide dependencies in container
$container = new Container(
    database: new MySQLDatabase(),
    payment: new StripeGateway()
);

$workflow = workflow(
    process: sync(
        ProcessOrder::class,  // Dependencies auto-injected
        orderId: variable('orderId'),
        amount: variable('amount')
    )
);

$result = run($workflow, $container, orderId: 123, amount: 99.99);

use function Chevere\Workflow\{workflow, sync, variable, run, response};

$workflow = workflow(
    isTooBig: sync(
        fn(string $path, int $maxBytes): bool => filesize($path) > $maxBytes,
        path: variable('file'),
        maxBytes: variable('maxBytes')
    ),
    compress: sync(
        CompressImage::class,
        file: variable('file')
    )->withRunIf(
        true,                       // literal
        variable('shouldCompress'), // workflow variable
        response('isTooBig'),       // job response value
        fn(RunInterface $run) => $run->variable('shouldCompress')->bool(), // closure condition variable
        fn(RunInterface $run) => $run->response('isTooBig')->bool(), // closure condition using response
    )
);
$result = run($workflow,
    file: '/path/to/image.jpg',
    shouldCompress: true,
    maxBytes: 1_000_000
);

$workflow = workflow(
    exists: sync(ExistsAction::class),
    update: sync(UpdateAction::class)
        ->withRunIf(response('exists')),
    cleanup: sync(CleanupAction::class)
        ->withDepends('update')
);

$workflow = workflow(
    exists: sync(ExistsAction::class),
    update: sync(UpdateAction::class)
        ->withRunIf(response('exists')),
    cleanup: sync(CleanupAction::class)
        ->withAfter('update')
);

$workflow = workflow(
    fetch: sync(
        FetchFromApi::class,
        url: variable('endpoint')
    )->withRetry(
        timeout: 300,     // Max 300 seconds total
        maxAttempts: 5,   // Try up to 5 times
        delay: 10         // Wait 10 seconds between attempts
    )
);

use Chevere\Workflow\Exceptions\WorkflowException;

try {
    $result = run($workflow, ...);
} catch (WorkflowException $e) {
    echo $e->name;        // Name of the failed job
    echo $e->job;         // Job instance
    echo $e->throwable;   // Original exception
}

use Chevere\Workflow\Exceptions\WorkflowException;
use Chevere\Workflow\Exceptions\EarlyReturnException;

try {
    $result = run($workflow, ...);
} catch (WorkflowException $e) {
    if($e->throwable instanceof EarlyReturnException) {
        // Handle early return (e.g., return a default response)
        return;
    }
}

use Chevere\Workflow\Traits\WorkflowTrait;
use function Chevere\Workflow\{workflow, sync, variable};

class OrderProcessor
{
    use WorkflowTrait;

    public function process(int $orderId): void
    {
        $workflow = workflow(
            validate: sync(ValidateOrder::class, id: variable('orderId')),
            charge: sync(ChargePayment::class, order: response('validate')),
            fulfill: sync(FulfillOrder::class, order: response('charge'))
        );

        $this->execute($workflow, orderId: $orderId);
    }

    public function getResult(): string
    {
        return $this->run()->response('fulfill')->string();
    }
}

$workflow = workflow(
    step: sync(MyAction::class, value: variable('input'))
);

$report = $workflow->lint();
// {
//   "violations": [...],
//   "stages": [...],
//   "mermaid": "graph TB;\n    ..."
// }

use PHPUnit\Framework\TestCase;

class FetchUserTest extends TestCase
{
    public function testFetchUser(): void
    {
        $action = new FetchUser();
        $result = $action(userId: 123);

        $this->assertSame(123, $result['id']);
        $this->assertArrayHasKey('name', $result);
    }
}

public function testWorkflowGraph(): void
{
    $workflow = workflow(
        a: async(ActionA::class),
        b: async(ActionB::class),
        c: sync(ActionC::class, x: response('a'), y: response('b'))
    );
    $graph = $workflow->jobs()->graph()->toArray();

    $this->assertSame([['a', 'b'], ['c']], $graph);
}

use Chevere\Workflow\Traits\WorkflowProviderTestTrait;

class MyWorkflowProviderTest extends PHPUnit\Framework\TestCase
{
    use WorkflowProviderTestTrait;

    public function testProviderGraph(): void
    {
        $this->assertWorkflowGraph(
            [['a', 'b'], ['c']],
            MyProvider::class
        );
    }
}

public function testWorkflowResponses(): void
{
    $result = run($workflow, input: 'test');

    $this->assertSame('expected', $result->response('job1')->string());
    $this->assertSame(42, $result->response('job2', 'count')->int());
}

use Chevere\Workflow\Traits\ExpectWorkflowExceptionTrait;

class WorkflowExceptionTest extends TestCase
{
    use ExpectWorkflowExceptionTrait;

    public function testJobFailure(): void
    {
        $this->expectWorkflowException(
            closure: fn() => run($workflow, input: 'invalid'),
            exception: InvalidArgumentException::class,
            job: 'validate',
            message: 'Invalid input provided'
        );
    }
}

$workflow = workflow(
    // Parallel image resizing
    thumb: async(
        ImageResize::class,
        file: variable('image'),
        width: 150,
        height: 150
    ),
    medium: async(
        ImageResize::class,
        file: variable('image'),
        width: 800
    ),
    // Store after all resizing completes
    store: sync(
        StoreFiles::class,
        thumb: response('thumb'),
        medium: response('medium'),
        directory: variable('outputDir')
    )
);

$result = run($workflow,
    image: '/uploads/photo.jpg',
    outputDir: '/processed/'
);

$workflow = workflow(
    validate: sync(
        ValidateRegistration::class,
        email: variable('email'),
        password: variable('password')
    ),
    createUser: sync(
        CreateUser::class,
        data: response('validate')
    ),
    sendWelcome: async(
        SendWelcomeEmail::class,
        user: response('createUser')
    ),
    logEvent: async(
        LogRegistration::class,
        userId: response('createUser', 'id')
    )
);

$workflow = workflow(
    analyze: sync(
        AnalyzeContent::class,
        content: variable('text')
    ),
    translate: sync(
        TranslateContent::class,
        text: variable('text'),
        targetLang: variable('lang')
    )->withRunIf(
        variable('needsTranslation')
    ),
    publish: sync(
        PublishContent::class,
        content: response('analyze'),
        translated: response('translate')
    )
);

$result = run($workflow,
    text: 'Hello world',
    lang: 'es',
    needsTranslation: true
);
sh
CHEVERE_WORKFLOW_LINT_ENABLE=1 php my-workflow.php
sh
php demo/hello-world.php          # Basic workflow
php demo/chevere.php              # Chained jobs
php demo/closure.php              # Using closures
php demo/sync-vs-async.php        # Performance comparison
php demo/image-resize.php         # Parallel processing
php demo/run-if.php               # Conditional execution