PHP Classes

How to Implement a PHP Workflow Management System That Can Execute a Set of Actions Using PHP Classes with the Package Workflow: Create and run action workflows

Recommend this page to a friend!
  Info   Documentation   Screenshots   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2025-01-29 (10 days ago) RSS 2.0 feedNot yet rated by the usersTotal: Not yet counted Not yet ranked
Version License PHP version Categories
workflow 1.0Custom (specified...8.1Tools, PHP 8
Description 

Author

This package can create and run action workflows.

It provides a class to define workflow actions using sub-classes that define code that will be executed for each action.

The package can also create workflows associating each workflow with a set of actions.

It can also execute a workflow with given parameters and return the workflow result as responses.

Picture of Rodolfo Berrios Arce
Name: Rodolfo Berrios Arce <contact>
Classes: 2 packages by
Country: Chile Chile
Innovation award
Innovation award
Nominee: 2x

Documentation

Workflow

Chevere

Build Code size Apache-2.0 PHPStan Mutation testing badge

Quality Gate Status Maintainability Rating Reliability Rating Security Rating Coverage Technical Debt CodeFactor

Summary

A Workflow is a configurable stored procedure that will run one or more jobs. Jobs are independent from each other, but interconnected as you can pass response references between jobs. Jobs supports conditional running based on variables and previous job responses.

Installing

Workflow is available through Packagist and the repository source is at chevere/workflow.

composer require chevere/workflow

What it does?

The Workflow package provides a robust system for defining and executing structured procedures based on the workflow pattern. It enables to organize complex logic into a series of interconnected, independent jobs that can be executed in a controlled manner.

By breaking down monolithic procedures into modular workflow jobs, developers gain several advantages:

  • Improved testability of individual components
  • Better code organization and maintainability
  • Reusable job definitions across different workflows
  • Clear visualization of process flows
  • Flexible execution patterns (sync/async)

::: tip ? Workflow introduction Read Workflow for PHP at Rodolfo's blog for a compressive introduction to this package. :::

How to use

The Workflow package provides a set of core functions in the Chevere\Workflow namespace that allow you to build and manage workflow processes. These functions work together to create flexible, maintainable workflow definitions.

Functions

| Function | Purpose | | -------- | :--------------------------------------------------------- | | workflow | Creates a new workflow container for organizing named jobs | | sync | Defines a synchronous job that blocks until completion | | async | Defines an asynchronous job that runs non-blocking | | variable | Declares a workflow-level variable for job inputs | | response | Creates a reference to access previous job outputs |

Key concepts

  • Job: Self-contained unit of work defined by Action
  • Variable: Shared workflow-level inputs accessed by multiple jobs
  • Response: Links between job outputs (`response()`) and inputs

Workflow example

php demo/chevere.php

Create MyAction action by extending Chevere\Action\Action. You can also use ActionTrait.

use Chevere\Action\Action;

class MyAction extends Action
{
    protected function main(string $foo): string
    {
        return "Hello, {$foo}";
    }
}

Create Workflow with your MyAction Job:

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

$workflow = workflow(
    greet: sync(
        new MyAction(),
        foo: variable('super'),
    ),
    capo: sync(
        new MyAction(),
        foo: response('greet'),
    ),
);

Run the Workflow:

use function Chevere\Workflow\run;

$hello = run(
    $workflow,
    super: 'Chevere',
);
echo $hello->response('greet')->string() . PHP_EOL;
// Hello, Chevere
echo $hello->response('capo')->string() . PHP_EOL;
// Hello, Hello, Chevere

Variable

Use function variable to declare a Workflow variable that will be injected when running the workflow. Variables allow you to pass external values into your workflow jobs during execution.

use function Chevere\Workflow\variable;

// Basic variable declaration
variable('myVar');

// Usage in a job
sync(
    new MyAction(),
    parameter: variable('myVar')
);

When running the workflow, you must provide values for all declared variables:

use function Chevere\Workflow\run;

run($workflow, myVar: 'some value');

Response

Use function response to declare a reference to a response returned by a previous Job. This allows you to chain job outputs as inputs to subsequent jobs.

? When using a response it will auto declare the referenced Job as a dependency, ensuring proper execution order.

use function Chevere\Workflow\response;

// Basic response declaration
response('job1');

// Usage in a Workflow
workflow(
    job1: sync(
        new SomeAction(),
    ),
    job2: sync(
        new MyAction(),
        parameter: response('job1')
    );
);

References can be also made on a response member identified by key.

use function Chevere\Workflow\response;

response('job1', 'id');

Job

The Job class defines an Action that can be executed as part of a workflow.

Arguments

Job arguments can be passed in three ways:

  • As-is values: Direct values passed to the Action
  • Variables: Workflow-level inputs declared using the `variable` function
  • Responses: References to previous job outputs declared using the `response` function
class SomeAction extends Action
{
    protected function main(
        string $context,
        int $userId,
        mixed ...$bag,
    ): void
    {
        // On runtime:
        // $context = 'public'
        // $userId = (( user.id response ))
        // $bag = ['group' => 'admin', 'mask' => 1024]
    }
}

$workflow = workflow(
    user: sync(
        new GetUser(),
        request: variable('userId')
    ),
    job1: sync(
        new SomeAction(),
        context: 'public',               // As-is value
        userId: variable('userId'),      // Variable
        group: response('user', 'group'),// Response
        mask: 1024,                      // As-is value
    );
);

run($workflow, userId: 123);

In the example above:

  • The `context` argument is passed as-is with the value `public`.
  • The `userId` argument is dynamically provided as a variable.
  • The `group` argument is dynamically provided as a response from a previous job.
  • The `mask` argument is passed as-is with the value `1024`.

When running the Workflow, these arguments will be matched against the parameters defined in the main method of SomeAction.

Asynchronous

Use function async to create an asynchronous job, which runs non-blocking.

Important: When using async jobs, your Actions must support serialization. For Actions that work with non-serializable resources like:

  • Database connections
  • File handles
  • Stream resources
  • Network sockets

You must use sync jobs instead.

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

use function Chevere\Workflow\{sync,async,response,variable,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'
    ),
    store: sync(
        new StoreFiles(),
        response('thumb', 'filename'),
        response('medium', 'filename'),
    ),
);

  • `variable('image')` declares a Variable.
  • `response('thumb', 'filename')` and `response('medium', 'filename')` declares a Response reference.

The graph for this Workflow says that thumb, medium and poster run non-blocking in parallel. Job store runs blocking (another node).

graph TD;
    thumb-->store;
    medium-->store;
    poster-->store;

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

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',
    ]
);

Synchronous

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

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

use function Chevere\Workflow\{sync,response,variable,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: response('meta', 'name'),
        user: response('user')
    ),
);

  • `variable('payload')` and `variable('file')` declares a Variable.
  • `response('meta', 'name')` and `response('user')` declares a Response reference.

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

graph TD;
    user-->validate-->meta-->store;

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

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

use function Chevere\Workflow\run;

run(
    $workflow,
    payload: $_REQUEST,
    file: '/path/to/file',
);

Conditional running

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

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

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

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('myJob');

Running

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

use function Chevere\Workflow\run;

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

Access Job response

Use response to retrieve a job response as a CastArgument object which can be used to get a typed response.

$thumbFile = $run->response('thumb')->string();

? If the response is of type array|ArrayAccess you can shortcut key access casting.

use function Chevere\Parameter\cast;

$id = $run->response('user', 'id')->int();

WorkflowTrait

The WorkflowTrait provides methods execute and run for easing handling a Workflow within a class.

use Chevere\Workflow\WorkflowTrait;

class Something
{
    use WorkflowTrait;

    public function __construct()
    {
        $workflow = workflow(
            job1: sync(
                new MyAction(),
                foo: variable('bar')
            )
        );
        // Use execute to run the Workflow
        $this->execute($workflow, bar: 'baz');
    }
}

$some = new Something();
$bar = $some->run()->response('job1')->string();

Exception handling

When running a Workflow, if a Job fails a WorkflowException will be thrown. This is an exception wrapper for the job that thrown the exception.

try {
    $run = run($workflow, ...$variables);
} catch (WorkflowException $e) {
    // Job that thrown the exception
    $e->name;
    // Job instance that thrown the exception
    $e->job;
    // The exception thrown by the Job
    $e->throwable;
}

// If using WorkflowTrait
try {
    $this->execute($workflow, ...$variables);
    $run = $this->run();
} catch (WorkflowException $e) {
    // ...
}

Demo

See the demo directory for a set of examples.

Testing

Workflow provides several approaches for testing your implementations. While the Workflow itself doesn't need testing (it's a configuration), you should test:

  1. Job actions (unit tests)
  2. Workflow execution order (graph)
  3. Job responses
  4. Exception handling

Testing Job Actions

The primary testing focus should be on your Action implementations:

use PHPUnit\Framework\TestCase;

class MyActionTest extends TestCase
{
    public function testAction(): void
    {
        $action = new MyAction();
        // ? Chevere automatically validates Action I/O
        $response = $action(foo: 'bar');
        $this->assertSame('expected', $response);
    }
}

Testing Workflow Graph

Verify the execution order by testing the Workflow graph:

public function testWorkflowOrder(): void
{
    $expectedGraph = [
        ['job1', 'job2'], // parallel jobs
        ['job3'],         // depends on job1, job2
    ];
    $this->assertSame(
        $expectedGraph,
        $workflow->jobs()->graph()->toArray()
    );
}

Testing Job Responses

Test how jobs interact by checking their responses:

public function testJobResponses(): void
{
    $run = run($workflow, input: 'test');
    // Access typed responses
    $this->assertSame(
        123,
        $run->response('job1')->int()
    );
    $this->assertSame(
        'test',
        $run->response('job2')->string()
    );
    // Access array responses
    $this->assertSame(
        10.2,
        $run->response('job3', 'rate')->float()
    );
}

Testing Exception Handling

Use ExpectWorkflowExceptionTrait to test error scenarios:

use Chevere\Workflow\Traits\ExpectWorkflowExceptionTrait;
use function Chevere\Workflow\run;

class WorkflowTest extends TestCase
{
    use ExpectWorkflowExceptionTrait;

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

Architecture

The architecture of the Workflow package is designed to provide a clear separation of concerns, making it easier to define, manage, and execute workflows. The following diagram illustrates the core components and their interactions:

graph TD
    subgraph Client Application
        WF[Workflow Definition]
        Run[run Function]
    end

    subgraph Core Components
        Jobs[Jobs]
        Graph[Graph]
        Job[Job]
        Action[Action]
    end

    subgraph References
        Var[Variables]
        Resp[Responses]
    end

    subgraph Execution
        Runner[Workflow Runner]
        Sync[Sync Executor]
        Async[Async Executor]
    end

    WF --> Jobs
    Jobs --> |define| Graph
    Jobs --> |manages| Job
    Job --> |executes| Action
    Job --> |depends on| Var
    Job --> |depends on| Resp
    Run --> Runner
    Runner --> |uses| Jobs
    Runner --> |resolves| Graph
    Runner --> |executes via| Sync
    Runner --> |executes via| Async

Documentation

Documentation is available at chevere.org/packages/workflow.

License

Copyright Rodolfo Berrios A.

This software is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


Screenshots (1)  
  • demo/src/php.jpeg
  Files folder image Files (88)  
File Role Description
Files folder image.ecs (3 files)
Files folder image.github (1 directory)
Files folder image.vscode (5 files)
Files folder imagedemo (8 files, 1 directory)
Files folder imagesrc (11 files, 3 directories)
Files folder imagetests (13 files, 1 directory)
Accessible without login Plain text file chevere.svg Data Auxiliary data
Accessible without login Plain text file composer.json Data Auxiliary data
Accessible without login Plain text file infection.json.dist Data Auxiliary data
Accessible without login Plain text file LICENSE Lic. License text
Accessible without login Plain text file phpunit-coverage.xml Data Auxiliary data
Accessible without login Plain text file phpunit.xml Data Auxiliary data
Accessible without login Plain text file README.md Doc. Documentation
Accessible without login Plain text file sonar-project.properties Data Auxiliary data

  Files folder image Files (88)  /  .ecs  
File Role Description
  Accessible without login Plain text file .header Data Auxiliary data
  Accessible without login Plain text file ecs-chevere.php Class Class source
  Accessible without login Plain text file ecs.php Example Example script

  Files folder image Files (88)  /  .github  
File Role Description
Files folder imageworkflows (2 files)

  Files folder image Files (88)  /  .github  /  workflows  
File Role Description
  Accessible without login Plain text file sonarcloud.yml Data Auxiliary data
  Accessible without login Plain text file test.yml Data Auxiliary data

  Files folder image Files (88)  /  .vscode  
File Role Description
  Accessible without login Plain text file coverage.code-snippets Data Auxiliary data
  Accessible without login Plain text file docblock.code-snippets Data Auxiliary data
  Accessible without login Plain text file settings.json Data Auxiliary data
  Accessible without login Plain text file templates.code-snippets Class Class source
  Accessible without login Plain text file test.code-snippets Class Class source

  Files folder image Files (88)  /  demo  
File Role Description
Files folder imageActions (5 files)
  Accessible without login Plain text file chevere.php Class Class source
  Accessible without login Plain text file hello-world.php Example Example script
  Accessible without login Plain text file image-resize.php Example Example script
  Accessible without login Plain text file loader.php Aux. Configuration script
  Accessible without login Plain text file README.md Doc. Documentation
  Accessible without login Plain text file run-if.php Example Example script
  Accessible without login Plain text file sync-vs-async.php Example Example script
  Accessible without login Plain text file union.php Example Example script

  Files folder image Files (88)  /  demo  /  Actions  
File Role Description
  Accessible without login Plain text file FetchUrl.php Class Class source
  Accessible without login Plain text file Greet.php Class Class source
  Accessible without login Plain text file ImageResize.php Class Class source
  Accessible without login Plain text file ReturnsUnion.php Class Class source
  Accessible without login Plain text file StoreFile.php Class Class source

  Files folder image Files (88)  /  src  
File Role Description
Files folder imageExceptions (3 files)
Files folder imageInterfaces (9 files)
Files folder imageTraits (2 files)
  Accessible without login Plain text file CallableTask.php Class Class source
  Accessible without login Plain text file Caller.php Class Class source
  Accessible without login Plain text file functions.php Example Example script
  Accessible without login Plain text file Graph.php Class Class source
  Accessible without login Plain text file Job.php Class Class source
  Accessible without login Plain text file Jobs.php Class Class source
  Accessible without login Plain text file ResponseReference.php Class Class source
  Accessible without login Plain text file Run.php Class Class source
  Accessible without login Plain text file Runner.php Class Class source
  Accessible without login Plain text file Variable.php Class Class source
  Accessible without login Plain text file Workflow.php Class Class source

  Files folder image Files (88)  /  src  /  Exceptions  
File Role Description
  Accessible without login Plain text file JobsException.php Class Class source
  Accessible without login Plain text file RunnerException.php Class Class source
  Accessible without login Plain text file WorkflowException.php Class Class source

  Files folder image Files (88)  /  src  /  Interfaces  
File Role Description
  Accessible without login Plain text file CallerInterface.php Class Class source
  Accessible without login Plain text file GraphInterface.php Class Class source
  Accessible without login Plain text file JobInterface.php Class Class source
  Accessible without login Plain text file JobsInterface.php Class Class source
  Accessible without login Plain text file ResponseReferenceInterface.php Class Class source
  Accessible without login Plain text file RunInterface.php Class Class source
  Accessible without login Plain text file RunnerInterface.php Class Class source
  Accessible without login Plain text file VariableInterface.php Class Class source
  Accessible without login Plain text file WorkflowInterface.php Class Class source

  Files folder image Files (88)  /  src  /  Traits  
File Role Description
  Accessible without login Plain text file ExpectWorkflowExceptionTrait.php Class Class source
  Accessible without login Plain text file WorkflowTrait.php Class Class source

  Files folder image Files (88)  /  tests  
File Role Description
Files folder imagesrc (18 files)
  Accessible without login Plain text file CallableTaskTest.php Class Class source
  Accessible without login Plain text file FunctionsTest.php Class Class source
  Accessible without login Plain text file GraphTest.php Class Class source
  Accessible without login Plain text file JobsTest.php Class Class source
  Accessible without login Plain text file JobTest.php Class Class source
  Accessible without login Plain text file ResponseReferenceTest.php Class Class source
  Accessible without login Plain text file RunnerParallelTest.php Class Class source
  Accessible without login Plain text file RunnerSequentialTest.php Class Class source
  Accessible without login Plain text file RunnerTest.php Class Class source
  Accessible without login Plain text file RunTest.php Class Class source
  Accessible without login Plain text file VariableTest.php Class Class source
  Accessible without login Plain text file WorkflowTest.php Class Class source
  Accessible without login Plain text file WorkflowTraitTest.php Class Class source

  Files folder image Files (88)  /  tests  /  src  
File Role Description
  Accessible without login Plain text file TestActionAppendString.php Class Class source
  Accessible without login Plain text file TestActionFileWrite.php Class Class source
  Accessible without login Plain text file TestActionIntToString.php Class Class source
  Accessible without login Plain text file TestActionNoParams.php Class Class source
  Accessible without login Plain text file TestActionNoParamsArrayIntResponse.php Class Class source
  Accessible without login Plain text file TestActionNoParamsBoolResponses.php Class Class source
  Accessible without login Plain text file TestActionObjectConflict.php Class Class source
  Accessible without login Plain text file TestActionParam.php Class Class source
  Accessible without login Plain text file TestActionParamFooResponse1.php Class Class source
  Accessible without login Plain text file TestActionParamFooResponseBar.php Class Class source
  Accessible without login Plain text file TestActionParams.php Class Class source
  Accessible without login Plain text file TestActionParamsAlt.php Class Class source
  Accessible without login Plain text file TestActionParamsFooBarResponse2.php Class Class source
  Accessible without login Plain text file TestActionParamStringRegex.php Class Class source
  Accessible without login Plain text file TestActionThrows.php Class Class source
  Accessible without login Plain text file TestActionUnion.php Class Class source
  Accessible without login Plain text file TestActionUseWorkflowTrait.php Class Class source
  Accessible without login Plain text file TestActionVariadic.php Class Class source

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads  
 100%
Total:0
This week:0