{"id":27994498,"url":"https://github.com/chevere/workflow","last_synced_at":"2025-05-08T19:12:58.357Z","repository":{"id":54638537,"uuid":"455196315","full_name":"chevere/workflow","owner":"chevere","description":"PHP library for defining and executing workflows with parallel processing capabilities. Allows creation of complex task chains with dependencies, conditional execution, and variable injection.","archived":false,"fork":false,"pushed_at":"2025-04-13T14:25:03.000Z","size":680,"stargazers_count":90,"open_issues_count":4,"forks_count":2,"subscribers_count":3,"default_branch":"1.0","last_synced_at":"2025-05-08T19:12:41.149Z","etag":null,"topics":["chevere","php","workflow","workflow-engine"],"latest_commit_sha":null,"homepage":"https://chevere.org/packages/workflow","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chevere.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-02-03T14:26:40.000Z","updated_at":"2025-04-25T22:46:59.000Z","dependencies_parsed_at":"2023-10-03T01:24:43.668Z","dependency_job_id":"c1b53837-fac5-4d04-bec1-42f821996c72","html_url":"https://github.com/chevere/workflow","commit_stats":{"total_commits":118,"total_committers":1,"mean_commits":118.0,"dds":0.0,"last_synced_commit":"25dc1ae9e60d725c9febd586ae76eb6b88c47efe"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":"chevere/package-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chevere%2Fworkflow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chevere%2Fworkflow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chevere%2Fworkflow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chevere%2Fworkflow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chevere","download_url":"https://codeload.github.com/chevere/workflow/tar.gz/refs/heads/1.0","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253133116,"owners_count":21859112,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chevere","php","workflow","workflow-engine"],"created_at":"2025-05-08T19:12:57.716Z","updated_at":"2025-05-08T19:12:58.344Z","avatar_url":"https://github.com/chevere.png","language":"PHP","readme":"# Workflow\n\n![Chevere](chevere.svg)\n\n[![Build](https://img.shields.io/github/actions/workflow/status/chevere/workflow/test.yml?branch=1.0\u0026style=flat-square)](https://github.com/chevere/workflow/actions)\n![Code size](https://img.shields.io/github/languages/code-size/chevere/workflow?style=flat-square)\n[![Apache-2.0](https://img.shields.io/github/license/chevere/workflow?style=flat-square)](LICENSE)\n[![PHPStan](https://img.shields.io/badge/PHPStan-level%209-blueviolet?style=flat-square)](https://phpstan.org/)\n[![Mutation testing badge](https://img.shields.io/endpoint?style=flat-square\u0026url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fchevere%2Fworkflow%2F1.0)](https://dashboard.stryker-mutator.io/reports/github.com/chevere/workflow/1.0)\n\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=chevere_workflow\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=chevere_workflow)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=chevere_workflow\u0026metric=sqale_rating)](https://sonarcloud.io/dashboard?id=chevere_workflow)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=chevere_workflow\u0026metric=reliability_rating)](https://sonarcloud.io/dashboard?id=chevere_workflow)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=chevere_workflow\u0026metric=security_rating)](https://sonarcloud.io/dashboard?id=chevere_workflow)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=chevere_workflow\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=chevere_workflow)\n[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=chevere_workflow\u0026metric=sqale_index)](https://sonarcloud.io/dashboard?id=chevere_workflow)\n[![CodeFactor](https://www.codefactor.io/repository/github/chevere/workflow/badge)](https://www.codefactor.io/repository/github/chevere/workflow)\n\n## Summary\n\nA 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.\n\n## Installing\n\nWorkflow is available through [Packagist](https://packagist.org/packages/chevere/workflow) and the repository source is at [chevere/workflow](https://github.com/chevere/workflow).\n\n```sh\ncomposer require chevere/workflow\n```\n\n## What it does?\n\nThe Workflow package provides a robust system for defining and executing structured procedures based on the [workflow pattern](https://en.wikipedia.org/wiki/Workflow_pattern). It enables to organize complex logic into a series of interconnected, independent jobs that can be executed in a controlled manner.\n\nBy breaking down monolithic procedures into modular workflow jobs, developers gain several advantages:\n\n* Improved testability of individual components\n* Better code organization and maintainability\n* Reusable job definitions across different workflows\n* Clear visualization of process flows\n* Flexible execution patterns (sync/async)\n\n::: tip 💡 Workflow introduction\n Read [Workflow for PHP](https://rodolfoberrios.com/2022/04/09/workflow-php/) at Rodolfo's blog for a compressive introduction to this package.\n:::\n\n## How to use\n\nThe 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.\n\n### Functions\n\n| Function | Purpose                                                    |\n| -------- | :--------------------------------------------------------- |\n| workflow | Creates a new workflow container for organizing named jobs |\n| sync     | Defines a synchronous job that blocks until completion     |\n| async    | Defines an asynchronous job that runs non-blocking         |\n| variable | Declares a workflow-level variable for job inputs          |\n| response | Creates a reference to access previous job outputs         |\n\n### Key concepts\n\n* [Job](#job): Self-contained unit of work defined by [Action](https://chevere.org/packages/action)\n* [Variable](#variable): Shared workflow-level inputs accessed by multiple jobs\n* [Response](#response): Links between job outputs (`response()`) and inputs\n\n## Workflow example\n\n`php demo/chevere.php`\n\nCreate `MyAction` action by extending `Chevere\\Action\\Action`. You can also `use ActionTrait`.\n\n```php\nuse Chevere\\Action\\Action;\n\nclass MyAction extends Action\n{\n    protected function main(string $foo): string\n    {\n        return \"Hello, {$foo}\";\n    }\n}\n```\n\nCreate Workflow with your `MyAction` Job:\n\n```php\nuse function Chevere\\Workflow\\{workflow,sync,variable,response};\n\n$workflow = workflow(\n    greet: sync(\n        new MyAction(),\n        foo: variable('super'),\n    ),\n    capo: sync(\n        new MyAction(),\n        foo: response('greet'),\n    ),\n);\n```\n\nRun the Workflow:\n\n```php\nuse function Chevere\\Workflow\\run;\n\n$hello = run(\n    $workflow,\n    super: 'Chevere',\n);\necho $hello-\u003eresponse('greet')-\u003estring() . PHP_EOL;\n// Hello, Chevere\necho $hello-\u003eresponse('capo')-\u003estring() . PHP_EOL;\n// Hello, Hello, Chevere\n```\n\n## Variable\n\nUse 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.\n\n```php\nuse function Chevere\\Workflow\\variable;\n\n// Basic variable declaration\nvariable('myVar');\n\n// Usage in a job\nsync(\n    new MyAction(),\n    parameter: variable('myVar')\n);\n```\n\nWhen running the workflow, you must provide values for all declared variables:\n\n```php\nuse function Chevere\\Workflow\\run;\n\nrun($workflow, myVar: 'some value');\n```\n\n## Response\n\nUse 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.\n\n🪄 When using a response it will **auto declare** the referenced Job as a [dependency](#dependencies), ensuring proper execution order.\n\n```php\nuse function Chevere\\Workflow\\response;\n\n// Basic response declaration\nresponse('job1');\n\n// Usage in a Workflow\nworkflow(\n    job1: sync(\n        new SomeAction(),\n    ),\n    job2: sync(\n        new MyAction(),\n        parameter: response('job1')\n    );\n);\n```\n\nReferences can be also made on a response member identified by `key`.\n\n```php\nuse function Chevere\\Workflow\\response;\n\nresponse('job1', 'id');\n```\n\n## Job\n\nThe `Job` class defines an [Action](https://chevere.org/packages/action) that can be executed as part of a workflow.\n\n### Arguments\n\nJob arguments can be passed in three ways:\n\n* **As-is values**: Direct values passed to the Action\n* **Variables**: Workflow-level inputs declared using the `variable` function\n* **Responses**: References to previous job outputs declared using the `response` function\n\n```php\nclass SomeAction extends Action\n{\n    protected function main(\n        string $context,\n        int $userId,\n        mixed ...$bag,\n    ): void\n    {\n        // On runtime:\n        // $context = 'public'\n        // $userId = (( user.id response ))\n        // $bag = ['group' =\u003e 'admin', 'mask' =\u003e 1024]\n    }\n}\n\n$workflow = workflow(\n    user: sync(\n        new GetUser(),\n        request: variable('userId')\n    ),\n    job1: sync(\n        new SomeAction(),\n        context: 'public',               // As-is value\n        userId: variable('userId'),      // Variable\n        group: response('user', 'group'),// Response\n        mask: 1024,                      // As-is value\n    );\n);\n\nrun($workflow, userId: 123);\n```\n\nIn the example above:\n\n* The `context` argument is passed as-is with the value `public`.\n* The `userId` argument is dynamically provided as a variable.\n* The `group` argument is dynamically provided as a response from a previous job.\n* The `mask` argument is passed as-is with the value `1024`.\n\nWhen running the Workflow, these arguments will be matched against the parameters defined in the [main method](https://chevere.org/packages/action.html#main-method) of `SomeAction`.\n\n### Asynchronous\n\nUse function `async` to create an asynchronous job, which runs non-blocking.\n\n**Important:** When using `async` jobs, your Actions must support [serialization](https://www.php.net/manual/en/function.serialize.php). For Actions that work with non-serializable resources like:\n\n* Database connections\n* File handles\n* Stream resources\n* Network sockets\n\nYou must use `sync` jobs instead.\n\nIn the example below a Workflow describes an image creation procedure for multiple image sizes.\n\n```php\nuse function Chevere\\Workflow\\{sync,async,response,variable,workflow};\n\nworkflow(\n    thumb: async(\n        new ImageResize(),\n        image: variable('image'),\n        width: 100,\n        height: 100,\n        fit: 'thumb'\n    ),\n    medium: async(\n        new ImageResize(),\n        image: variable('image'),\n        width: 500,\n        fit: 'resizeByW'\n    ),\n    store: sync(\n        new StoreFiles(),\n        response('thumb', 'filename'),\n        response('medium', 'filename'),\n    ),\n);\n```\n\n* `variable('image')` declares a [Variable](#variable).\n* `response('thumb', 'filename')` and `response('medium', 'filename')` declares a [Response](#response) reference.\n\nThe graph for this Workflow says that `thumb`, `medium` and `poster` run non-blocking in parallel. Job `store` runs blocking (another node).\n\n```mermaid\ngraph TD;\n    thumb--\u003estore;\n    medium--\u003estore;\n    poster--\u003estore;\n```\n\n```php\n$workflow-\u003ejobs()-\u003egraph()-\u003etoArray();\n// contains\n[\n    ['thumb', 'medium', 'poster'],\n    ['store']\n];\n```\n\nTo complete the example, here's how to [Run](#running) the Workflow previously defined:\n\n```php\nuse function Chevere\\Workflow\\run;\n\nrun(\n    workflow: $workflow,\n    arguments: [\n        'image' =\u003e '/path/to/file',\n    ]\n);\n```\n\n### Synchronous\n\nUse function `sync` to create a synchronous job, which block execution until it gets resolved.\n\nIn the example below a Workflow describes an image uploading procedure.\n\n```php\nuse function Chevere\\Workflow\\{sync,response,variable,workflow};\n\nworkflow(\n    user: sync(\n        new GetUser(),\n        request: variable('payload')\n    ),\n    validate: sync(\n        new ValidateImage(),\n        mime: 'image/png',\n        file: variable('file')\n    ),\n    meta: sync(\n        new GetMeta(),\n        file: variable('file'),\n    ),\n    store: sync(\n        new StoreFile(),\n        file: variable('file'),\n        name: response('meta', 'name'),\n        user: response('user')\n    ),\n);\n```\n\n* `variable('payload')` and `variable('file')` declares a [Variable](#variable).\n* `response('meta', 'name')` and `response('user')` declares a [Response](#response) reference.\n\nThe graph for this Workflow says that all jobs run one after each other as all jobs are defined using `sync`.\n\n```mermaid\ngraph TD;\n    user--\u003evalidate--\u003emeta--\u003estore;\n```\n\n```php\n$workflow-\u003ejobs()-\u003egraph()-\u003etoArray();\n// contains\n[\n    ['user'],\n    ['validate'],\n    ['meta'],\n    ['store']\n];\n```\n\nTo complete the example, here's how to [Run](#running) the Workflow previously defined:\n\n```php\nuse function Chevere\\Workflow\\run;\n\nrun(\n    $workflow,\n    payload: $_REQUEST,\n    file: '/path/to/file',\n);\n```\n\n### Conditional running\n\nMethod `withRunIf` enables to pass arguments of type [Variable](#variable) or [Response](#response) for conditionally running a Job.\n\n```php\nsync(\n    new CompressImage(),\n    file: variable('file')\n)\n    -\u003ewithRunIf(\n        variable('compressImage'),\n        response('SomeAction', 'doImageCompress')\n    )\n```\n\nFor 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.\n\n### Dependencies\n\nUse `withDepends` method to explicit declare previous jobs as dependencies. The dependent Job won't run until the dependencies are resolved.\n\n```php\njob(new SomeAction())\n    -\u003ewithDepends('myJob');\n```\n\n## Running\n\nTo run a Workflow use the `run` function by passing a Workflow and its variables (if any).\n\n```php\nuse function Chevere\\Workflow\\run;\n\n$run = run($workflow, ...$variables);\n```\n\n### Access Job response\n\nUse `response` to retrieve a job response as a `CastArgument` object which can be used to get a typed response.\n\n```php\n$thumbFile = $run-\u003eresponse('thumb')-\u003estring();\n```\n\n🪄 If the response is of type `array` you can shortcut key access casting.\n\n```php\nuse function Chevere\\Parameter\\cast;\n\n$id = $run-\u003eresponse('user', 'id')-\u003eint();\n```\n\n### WorkflowTrait\n\nThe `WorkflowTrait` provides methods `execute`  and `run` for easing handling a Workflow within a class.\n\n```php\nuse Chevere\\Workflow\\WorkflowTrait;\n\nclass Something\n{\n    use WorkflowTrait;\n\n    public function __construct()\n    {\n        $workflow = workflow(\n            job1: sync(\n                new MyAction(),\n                foo: variable('bar')\n            )\n        );\n        // Use execute to run the Workflow\n        $this-\u003eexecute($workflow, bar: 'baz');\n    }\n}\n\n$some = new Something();\n$bar = $some-\u003erun()-\u003eresponse('job1')-\u003estring();\n```\n\n### Exception handling\n\nWhen running a Workflow, if a Job fails a `WorkflowException` will be thrown. This is an exception wrapper for the job that thrown the exception.\n\n```php\ntry {\n    $run = run($workflow, ...$variables);\n} catch (WorkflowException $e) {\n    // Job that thrown the exception\n    $e-\u003ename;\n    // Job instance that thrown the exception\n    $e-\u003ejob;\n    // The exception thrown by the Job\n    $e-\u003ethrowable;\n}\n\n// If using WorkflowTrait\ntry {\n    $this-\u003eexecute($workflow, ...$variables);\n    $run = $this-\u003erun();\n} catch (WorkflowException $e) {\n    // ...\n}\n```\n\n## Demo\n\nSee the [demo](demo) directory for a set of examples.\n\n## Testing\n\nWorkflow provides several approaches for testing your implementations. While the Workflow itself doesn't need testing (it's a configuration), you should test:\n\n1. Job actions (unit tests)\n2. Workflow execution order (graph)\n3. Job responses\n4. Exception handling\n\n### Testing Job Actions\n\nThe primary testing focus should be on your Action implementations:\n\n```php\nuse PHPUnit\\Framework\\TestCase;\n\nclass MyActionTest extends TestCase\n{\n    public function testAction(): void\n    {\n        $action = new MyAction();\n        // 🪄 Chevere automatically validates Action I/O\n        $response = $action(foo: 'bar');\n        $this-\u003eassertSame('expected', $response);\n    }\n}\n```\n\n### Testing Workflow Graph\n\nVerify the execution order by testing the Workflow graph:\n\n```php\npublic function testWorkflowOrder(): void\n{\n    $expectedGraph = [\n        ['job1', 'job2'], // parallel jobs\n        ['job3'],         // depends on job1, job2\n    ];\n    $this-\u003eassertSame(\n        $expectedGraph,\n        $workflow-\u003ejobs()-\u003egraph()-\u003etoArray()\n    );\n}\n```\n\n### Testing Job Responses\n\nTest how jobs interact by checking their responses:\n\n```php\npublic function testJobResponses(): void\n{\n    $run = run($workflow, input: 'test');\n    // Access typed responses\n    $this-\u003eassertSame(\n        123,\n        $run-\u003eresponse('job1')-\u003eint()\n    );\n    $this-\u003eassertSame(\n        'test',\n        $run-\u003eresponse('job2')-\u003estring()\n    );\n    // Access array responses\n    $this-\u003eassertSame(\n        10.2,\n        $run-\u003eresponse('job3', 'rate')-\u003efloat()\n    );\n}\n```\n\n### Testing Exception Handling\n\nUse `ExpectWorkflowExceptionTrait` to test error scenarios:\n\n```php\nuse Chevere\\Workflow\\Traits\\ExpectWorkflowExceptionTrait;\nuse function Chevere\\Workflow\\run;\n\nclass WorkflowTest extends TestCase\n{\n    use ExpectWorkflowExceptionTrait;\n\n    public function testFailingJob(): void\n    {\n        $this-\u003eexpectWorkflowException(\n            closure: fn () =\u003e run($workflow, input: 'invalid'),\n            exception: LogicException::class,\n            job: 'validation',\n            message: 'Invalid input'\n        );\n    }\n}\n```\n\n## Architecture\n\nThe 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:\n\n```mermaid\ngraph TD\n    subgraph Client Application\n        WF[Workflow Definition]\n        Run[run Function]\n    end\n\n    subgraph Core Components\n        Jobs[Jobs]\n        Graph[Graph]\n        Job[Job]\n        Action[Action]\n    end\n\n    subgraph References\n        Var[Variables]\n        Resp[Responses]\n    end\n\n    subgraph Execution\n        Runner[Workflow Runner]\n        Sync[Sync Executor]\n        Async[Async Executor]\n    end\n\n    WF --\u003e Jobs\n    Jobs --\u003e |define| Graph\n    Jobs --\u003e |manages| Job\n    Job --\u003e |executes| Action\n    Job --\u003e |depends on| Var\n    Job --\u003e |depends on| Resp\n    Run --\u003e Runner\n    Runner --\u003e |uses| Jobs\n    Runner --\u003e |resolves| Graph\n    Runner --\u003e |executes via| Sync\n    Runner --\u003e |executes via| Async\n```\n\n## Documentation\n\nDocumentation is available at [chevere.org/packages/workflow](https://chevere.org/packages/workflow).\n\n## License\n\nCopyright [Rodolfo Berrios A.](https://rodolfoberrios.com/)\n\nThis software is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text.\n\nUnless 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.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchevere%2Fworkflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchevere%2Fworkflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchevere%2Fworkflow/lists"}