# Workflow

Workflow

Namespace Chevere\Workflow

The Workflow package provides tooling for defining an execution procedure based on the workflow pattern (opens new window). Its purpose is to abstract logic instructions as units of interconnected independent jobs.

👏 With this package you can design a workflow procedure in a similar fashion to GitHub Actions.

💡 Workflow introduction

Read Workflow for PHP (opens new window) at Rodolfo's blog for a compressive introduction to this package.

# Installing

Workflow is available through Packagist (opens new window) and the repository source is at GitHub (opens new window).

composer require chevere/workflow

# Creating a Workflow

To create a Workflow define its named Jobs. A Job is created by passing an Action object and its arguments, which can be raw values Variables adn References.

# With Synchronous jobs

A synchronous job block execution until it gets resolved. Use function sync to create a synchronous job.

In the example below a Workflow describes an image uploading procedure.

use function Chevere\Workflow\sync;
use function Chevere\Workflow\reference;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

workflow(
    user: sync(
        new GetUser(),
        request: variable('payload')
    ),
    validate: sync(
        new ValidateImage(),
        mime: 'image/png',
        file: variable('file')
    ),
    meta: sync(
        new GetMeta(),
        file: variable('file'),
    ),
    store: sync(
        new StoreFile(),
        file: variable('file'),
        name: reference('meta:name'),
        user: reference('user:output')
    ),
);

For the code above:

  • variable('payload') and variable('file') declares a Variable.
  • reference('meta:meta') and reference('user:output') declares a Reference.

The graph for this Workflow looks like this:

//$workflow->jobs()->graph();
[
    ['user'],
    ['validate'],
    ['meta'],
    ['store']
];

The graph above says that all jobs run one after each other as all jobs are defined using sync.

To complete the example, here's how to Run the Workflow previously defined:

use function Chevere\Workflow\run;

run(
    workflow: $workflow,
    arguments: [
        'payload' => $_REQUEST,
        'file' => '/path/to/file',
    ]
);

# With Asynchronous jobs

A asynchronous job runs in paraller, non-blocking. Use function async to create an asynchronous job.

In the example below a Workflow describes an image creation procedure for multiple image sizes.

use function Chevere\Workflow\sync;
use function Chevere\Workflow\reference;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

workflow(
    thumb: async(
        new ImageResize(),
        image: variable('image'),
        width: 100,
        height: 100,
        fit: 'thumb'
    ),
    medium: async(
        new ImageResize(),
        image: variable('image'),
        width: 500,
        fit: 'resizeByW'
    ),
    poster: async(
        new ImageResize(),
        image: variable('image'),
        width: 1500,
        fit: 'resizeByW'
    ),
    store: sync(
        new StoreFiles(),
        reference('thumb:filename'),
        reference('medium:filename'),
        reference('poster:filename'),
    ),
);

The graph for this Workflow looks like this:

//$workflow->jobs()->graph();
[
    ['thumb', 'medium', 'poster'],
    ['store']
];

The graph above says that thumb, medium and poster run non-blocking. Job store runs blocking.

To complete the example, here's how to Run the Workflow previously defined:

use function Chevere\Workflow\run;

run(
    workflow: $workflow,
    arguments: [
        'image' => '/path/to/file',
    ]
);

# Variable

A Workflow variable gets declared using function variable. This denotes a variable which must be injected by at Workflow run layer.

use function Chevere\Workflow\variable;

variable('myVar');

# Reference

A Job reference gets declared using function reference. This denotes a reference to a response key returned by a previous Job. It will auto declare the referenced Job as dependency.

use function Chevere\Workflow\reference;

reference(job: 'task', key: 'id');

# Job

The Job class defines an Action with arguments to pass, supporting direct arguments and variable references to previous jobs response keys.

👉 The action parameter must be a class name implementing the ActionInterface. See the Action component.

Argument can be passed passed "as-is", variable or reference on constructor using named arguments.

# Synchronous Job

use function Chevere\Workflow\job;

sync(new SomeAction(), ...$argument);

# Asynchronous Job

use function Chevere\Workflow\job;

async(new SomeAction(), ...$argument);

# Job variables and references

sync(
    new SomeAction()
    context: 'public',
    role: variable('role'),
    userId: reference('user', 'id'),
);

For the code above, argument context will be passed "as-is" (public) to SomeAction, arguments role and userId will be dynamic provided. When running the Workflow these arguments will be matched against the Parameters defined at the run method for SomeAction.

# Conditional running

Method withRunIf enables to pass arguments of type Variable or Reference for conditionally running a Job.

sync(
    new CompressImage(),
    file: variable('file')
)
    ->withRunIf(
        variable('compressImage'),
        reference('SomeAction', 'doImageCompress')
    )

For the code above, all conditions must meet to run the Job: The variable compressImage and the reference SomeAction:doImageCompress must be true.

# Dependencies

Use withDepends method to explicit declare previous jobs as dependencies. The dependent Job won't run until the dependencies are resolved.

job(new SomeAction())->withDepends('jobName');

# Running Workflow

To run a Workflow use the run function by passing a Workflow and an array for its variables (if any).

use function Chevere\Workflow\run;

$run = run($workflow, $variables);

Variable $run will be assigned to an object implementing Interfaces\RunInterface, which you can query for obtaining data from the workflow runtime.

# Injecting services

Pass a PSR-Container for injecting services that will be available at Action layer.

use Chevere\Container\Container;

$container = new Container();
$run = run($workflow, $variables, $container);