Durable workflow engine that allows users to write long running persistent distributed workflows in PHP powered by Laravel queues

Overview

Laravel Workflow PHP Composer

Durable workflow engine that allows users to write long running persistent distributed workflows (orchestrations) in PHP powered by Laravel queues.

Installation

This library is installable via Composer. You must also publish the migrations for the workflows table.

composer require laravel-workflow/laravel-workflow
php artisan vendor:publish --provider="Workflow\Providers\WorkflowServiceProvider" --tag="migrations"

Requirements

You can use any queue driver that Laravel supports but this is heavily tested against Redis.

Usage

1. Create a workflow.

class MyWorkflow extends Workflow
{
    public function execute()
    {
        $result = yield ActivityStub::make(MyActivity::class);
        return $result;
    }
}

2. Create an activity.

class MyActivity extends Activity
{
    public function execute()
    {
        return 'activity';
    }
}

3. Run the workflow.

$workflow = WorkflowStub::make(MyWorkflow::class);
$workflow->start();
while ($workflow->running());
$workflow->output();
=> 'activity'

Failed Workflows

If a workflow fails or crashes at any point then it can be resumed from that point. Any activities that were successfully completed during the previous execution of the workflow will not be ran again.

$workflow = WorkflowStub::load(1);
$workflow->resume();
while ($workflow->running());
$workflow->output();
=> 'activity'

Retries

A workflow will only fail when the retries on the workflow or a failing activity have been exhausted.

Workflows and activies are based on Laravel jobs so you can use any options you normally would.

public $tries = 3;

public $maxExceptions = 3;
Comments
  • Package state & plans?

    Package state & plans?

    Hello @rmcdaniel!

    I love the idea of implementing the workflow pattern in Laravel! Tried to use this package in my sandbox environment and had a quick look at the code as well.

    A few things I've discovered:

    • Content of serialized exception is broken when using PostgreSQL (text column exception), and that's probably because of https://stackoverflow.com/questions/28813409/are-null-bytes-allowed-in-unicode-strings-in-postgresql-via-python/28816294#28816294
    • Codebase is weakly typed, with no coverage, backward compatibility, or mutation testing results available.
    • I'm not sure how this project fits in long term. I saw some messages on Twitter around the topic of Enterprise Laravel, but the solution itself seems too raw to me at the moment.

    If I decide to use this package in production, I'm looking to become a stable contributor to improve the solution.

    Let me know what you think, Sincerely, Michael


    opened by michael-rubel 25
  • Value object serialization

    Value object serialization

    I've tried to wrap a complex process into the workflow. The package indeed has a hard time restoring activities with DTO, Value Objects, or Enums inside.

    It's of course possible to create all the code using only primitive types, but in enterprise scenarios that would be a massive productivity killer. It would be nice to make it work at least as native jobs as they can hold objects with no problem even though that is not a best practice for performance.

    Example stacktrace of failing activity (Eloquent Enum Cast):

    Cannot instantiate enum App\Enums\TransactionType {"exception":"[object] (Error(code: 0): Cannot instantiate enum App\\Enums\\TransactionType at /var/www/html/vendor/opis/closure/src/SerializableClosure.php:411)
    [stacktrace]
    #0 /var/www/html/vendor/opis/closure/src/SerializableClosure.php(411): ReflectionClass->newInstanceWithoutConstructor()
    #1 /var/www/html/vendor/opis/closure/src/SerializableClosure.php(427): Opis\\Closure\\SerializableClosure::wrapClosures()
    #2 /var/www/html/vendor/opis/closure/src/SerializableClosure.php(427): Opis\\Closure\\SerializableClosure::wrapClosures()
    #3 /var/www/html/vendor/opis/closure/functions.php(19): Opis\\Closure\\SerializableClosure::wrapClosures()
    #4 /var/www/html/vendor/laravel-workflow/laravel-workflow/src/Serializers/Y.php(45): Opis\\Closure\\serialize()
    #5 /var/www/html/vendor/laravel-workflow/laravel-workflow/src/WorkflowStub.php(200): Workflow\\Serializers\\Y::serialize()
    #6 /var/www/html/vendor/laravel-workflow/laravel-workflow/src/Middleware/WorkflowMiddleware.php(15): Workflow\\WorkflowStub->next()
    #7 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Workflow\\Middleware\\WorkflowMiddleware->handle()
    #8 /var/www/html/vendor/laravel-workflow/laravel-workflow/src/Middleware/WithoutOverlappingMiddleware.php(75): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
    #9 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Workflow\\Middleware\\WithoutOverlappingMiddleware->handle()
    #10 /var/www/html/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
    #11 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(126): Illuminate\\Pipeline\\Pipeline->then()
    #12 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(70): Illuminate\\Queue\\CallQueuedHandler->dispatchThroughMiddleware()
    #13 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(98): Illuminate\\Queue\\CallQueuedHandler->call()
    #14 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(425): Illuminate\\Queue\\Jobs\\Job->fire()
    #15 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(375): Illuminate\\Queue\\Worker->process()
    #16 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Worker.php(173): Illuminate\\Queue\\Worker->runJob()
    #17 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(147): Illuminate\\Queue\\Worker->daemon()
    #18 /var/www/html/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(130): Illuminate\\Queue\\Console\\WorkCommand->runWorker()
    #19 /var/www/html/vendor/laravel/horizon/src/Console/WorkCommand.php(51): Illuminate\\Queue\\Console\\WorkCommand->handle()
    
    opened by michael-rubel 4
  • Proper way to pass arguments?

    Proper way to pass arguments?

    Hi @rmcdaniel

    I was trying the package again and came to the issue of passing parameters to the workflow. For example, I want to pass validated input from form-request to the workflow and action.

    arguments

    This isn't documented, and I needed to dig deeper. It looks like you pass arguments in the raw format (serialized). Even more, there are required parameters passed in case of activity (index, now, etc) which is a bit unintuitive from the DX point of view (I tried to type-hint all the stuff in the constructor first).

    Maybe there's a way to avoid unserializing arguments manually? Or I'm approaching this wrong?


    opened by michael-rubel 4
  • Duplicate Exception

    Duplicate Exception

    I use PostgreSQL as the database and PHP 8.2.

    The PostgreSQL duplicate key exception differs from the MySQL SQL error, which leads to an exception in the code because it uses the case-sensitive str_contains function. See below:

        public function next($index, $now, $class, $result): void
        {
            try {
                $this->storedWorkflow->logs()
                    ->create([
                        'index' => $index,
                        'now' => $now,
                        'class' => $class,
                        'result' => serialize($result),
                    ]);
            } catch (QueryException $exception) {
                if (! str_contains($exception->getMessage(), 'Duplicate')) { // <---- HERE
                    throw $exception;
                }
            }
    
            $this->dispatch();
        }
    

    The SQL error from a PostgreSQL looks like this:

    SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint 
    

    And here, as an example, the output of str_contains using the uppercase and lowercase variation.

    > echo var_dump(str_contains('Unique violation: 7 ERROR:  duplicate key', 'Duplicate'));
    bool(false)
    > echo var_dump(str_contains('Unique violation: 7 ERROR:  duplicate key', 'duplicate'));
    bool(true) 
    
    opened by KaulSe 3
  • Workflow status doesn't changed to completed when contains signal methods

    Workflow status doesn't changed to completed when contains signal methods

    Hi,

    Thanks for creating this simple yet powerful workflow package for Laravel community.

    However, I follow the document to setup the Workflow, found the status won't change to "Completed" when contains a signal method.

    The steps to re-produce the issue...

    Workflow file

    <?php
    
    namespace App\Workflows\Demo;
    
    use Workflow\ActivityStub;
    use Workflow\SignalMethod;
    use Workflow\Workflow;
    use WOrkflow\WorkflowStub;
    
    class DemoWorkflow extends Workflow
    {
        private bool $isReady = false;
    
        #[SignalMethod]
        public function ready()
        {
            $this->isReady = true;
        }
    
        public function execute()
        {
            $resultOne = yield ActivityStub::make(StepOneActivity::class);
    
            yield WorkflowStub::await(fn () => $this->isReady);
    //        yield WorkflowStub::timer(5);
    //        $resultApprove = yield WorkflowStub::awaitWithTimeout('5 minutes', fn() => $this->approve());
    
            $resultTwo = yield ActivityStub::make(StepTwoActivity::class);
    
            return $resultOne . $resultTwo;
        }
    }
    

    Step 1 activity

    <?php
    
    namespace App\Workflows\Demo;
    
    use Workflow\Activity;
    
    class StepOneActivity extends Activity
    {
        public function execute()
        {
            // Executing step 1
            return 'Step 1 completed';
        }
    }
    

    Step 2 activity

    <?php
    
    namespace App\Workflows\Demo;
    
    use Workflow\Activity;
    
    class StepTwoActivity extends Activity
    {
        public function execute()
        {
            // Executing step 2
            return 'Step 2 completed';
        }
    }
    

    Controller

    <?php
    
    namespace App\Controllers;
    
    use App\Http\Controllers\Controller;
    use App\Workflows\Demo\DemoWorkflow;
    use Workflow\WorkflowStub;
    
    class WorkflowController extends Controller
    {
        public function index()
        {
            $workflow = WorkflowStub::make(DemoWorkflow::class);
            $workflow->start();
    
            return $workflow->id();
        }
    
        public function ready(int $workflow_id)
        {
            $workflow = WorkflowStub::load($workflow_id);
            $workflow->ready();
    
            return true;
        }
    
        public function status(int $workflow_id)
        {
            $workflow = WorkflowStub::load($workflow_id);
    
            return $workflow->status();
        }
    }
    

    First I call index method to create the workflow, StepOneActivity executed, workflow pending wait for ready signal, next call ready method from controller to set the isReady property to true, Horizon shows the StepTwoActivity been executed, but from Waterline dashboard, the workflow still on running.

    image image

    Here below is the screenshot of related records in database. Although the status not changed to completed, but the updated_at column is being updated.

    image image image

    StepOneActivity and StepTwoActivity are both executed, only the Workflow status not being updated to completed.

    If comment the line

    yield WorkflowStub::await(fn () => $this->isReady);
    

    Then the workflow can changed to completed. image

    Please kindly advise if any steps missed, many thanks!

    opened by nikolee 3
  • Invalid default value for 'created_at'

    Invalid default value for 'created_at'

    hi richard,

    found this:

    image

    SQLSTATE[42000]: Syntax error or access violation: 1067 Invalid default value for 'created_at' (SQL: create table `workflow_logs` (`id` bigint unsigned not null auto_increment primary key, `stored_workflow_id` bigint unsigned not null, `index` bigint unsigned not null, `now` timestamp not null, `class` text not null, `result` text null, `created_at` timestamp(6) not null) default character set utf8mb4 collate 'utf8mb4_unicode_ci')
    

    version: 0.0.10 SQL server: 10.7.4-MariaDB-1:10.7.4+maria~focal

    opened by michabbb 2
  • Deprecations in `Opis\Closure\SerializableClosure`

    Deprecations in `Opis\Closure\SerializableClosure`

    • Opis\Closure\SerializableClosure implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /var/www/html/vendor/opis/closure/src/SerializableClosure.php on line 18
    opened by michael-rubel 1
  • Use config to specify stored workflow models

    Use config to specify stored workflow models

    This pull request updates the StoredWorkflow, StoredWorkflowException, StoredWorkflowLog, and StoredWorkflowSignal classes to use the values returned by config('workflows.stored_workflow_model'), config('workflows.stored_workflow_exception_model'), config('workflows.stored_workflow_log_model'), and config('workflows.stored_workflow_signal_model'), respectively, rather than hardcoding the class names. This allows developers to specify their own custom classes that extend these classes and customize their behavior. The main reason would be to customize the $connection options.

    opened by rmcdaniel 1
  • Add Serializable Closures

    Add Serializable Closures

    This adds the excellent https://github.com/opis/closure library to add some better compatibility plus cryptographic signatures when serializing/unserializing exceptions.

    opened by rmcdaniel 0
  • Fix exception logging

    Fix exception logging

    Fix exception logging when the exception happens inside the workflow. Also add support for carbon intervals like 5 minutes instead of having to specify in seconds 300.

    opened by rmcdaniel 0
  • Add parallel activities

    Add parallel activities

    This PR adds a new middleware that was partially written by ChatGPT. The built-in Laravel middleware WithoutOverlapping wouldn't support the ability for parallel activities so I wrote one with the help of ChatGPT.

    Here's an example of how you would use it:

    $promise1 = ActivityStub::make(MyActivity::class);
    $promise2 = ActivityStub::make(MyActivity::class);
    
    yield ActivityStub::all([$promise1, $promise2]);
    

    This is still a work in progress...

    opened by rmcdaniel 0
  • Serialization of 'Closure' is not allowed

    Serialization of 'Closure' is not allowed

    [2023-01-02 15:18:32] local.ERROR: Serialization of 'Closure' is not allowed {"exception":"[object] (Exception(code: 0): Serialization of 'Closure' is not allowed at /Users/rabol/code/web/wftest/vendor/laravel/serializable-closure/src/Serializers/Signed.php:68)
    

    This looks like a regression from a previous issue with storing exception messages.

    opened by rmcdaniel 2
Releases(0.0.24)
Owner
null
Projeto feito em Laravel/Framework com o intuito de aprender usar as Queues para disparo de e-mails.

Filas do Laravel Projeto feito em Laravel/Framework com o intuito de aprender usar as Queues para disparo de e-mails. Bibliotecas usadas: Laravel pt-B

Junior Rodrigues 0 Dec 24, 2021
Persistent settings in Laravel

Laravel Settings Persistent, application-wide settings for Laravel. Despite the package name, this package works with Laravel 4, 5, 6, 7 and 8! Common

Andreas Lutro 855 Dec 27, 2022
Persistent Fakes for Laravel Dusk

Laravel Dusk Fakes Support this package! ❤️ We proudly support the community by developing Laravel packages and giving them away for free. If this pac

Protone Media 3 Sep 5, 2022
Manage your own workflows with filament

Filament Workflow Manager This package provides a Filament resource where you can configure and manager your workflows, and also provides some functio

EL OUFIR Hatim 41 Jan 2, 2023
Laravel Users | A Laravel Users CRUD Management Package

A Users Management Package that includes all necessary routes, views, models, and controllers for a user management dashboard and associated pages for managing Laravels built in user scaffolding. Built for Laravel 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 6.0, 7.0 and 8.0.

Jeremy Kenedy 393 Nov 28, 2022
Disque is a distributed message broker

Disque, an in-memory, distributed job queue Disque is an ongoing experiment to build a distributed, in-memory, message broker. Its goal is to capture

Salvatore Sanfilippo 7.9k Jan 7, 2023
Laravel Impersonate is a plugin that allows you to authenticate as your users.

Laravel Impersonate Laravel Impersonate makes it easy to authenticate as your users. Add a simple trait to your user model and impersonate as one of y

404lab 1.6k Dec 30, 2022
A simple Content Moderation System for Laravel 5.* that allows you to Approve or Reject resources like posts, comments, users, etc.

Laravel Moderation A simple Moderation System for Laravel 5.* that allows you to Approve or Reject resources like posts, comments, users, etc. Keep yo

Alex Kyriakidis 509 Dec 30, 2022
Llum illuminates your Laravel projects speeding up your Github/Laravel development workflow

Llum illuminates your Laravel projects speeding up your Github/Laravel development workflow

Sergi Tur Badenas 110 Dec 25, 2022
Rapidly speed up your Laravel workflow with generators

Fast Workflow in Laravel With Custom Generators This Laravel package provides a variety of generators to speed up your development process. These gene

Jeffrey Way 22 Oct 28, 2022
symfony workflow component for laravel7 and 8 ,php 7 and 8

Laravel workflow Use the Symfony Workflow component in Laravel8,PHP7,PHP8 This repository based on @brexis,his project since 2019-09 No longer maintai

null 3 Jul 21, 2022
Fullstack komponents to write Forms, Queries and Menus in Laravel

kompo.io • Documentation • Demos • Twitter kompo/kompo kompo/kompo is a library of Fullstack Komponents to help you write forms, queries and menus in

Bass El Hachem 107 Dec 5, 2022
Testbench Component is the de-facto package that has been designed to help you write tests for your Laravel package

Laravel Testing Helper for Packages Development Testbench Component is the de-facto package that has been designed to help you write tests for your La

Orchestra Platform 1.9k Dec 29, 2022
Adds a way to write php and run it directly in Laravels' Artisan Tinker.

Adds a way to write php in PhpStorm/IDEA and run it directly as if through laravel artisan tinker - allowing you to quickly run a piece of code with a

Robbin 120 Jan 2, 2023
It's like React for PHP. Powered by Laravel, Livewire, Tailwind, & Alpine.

Tailwire It's like React for PHP. Powered by Laravel, Livewire, Tailwind, & Alpine. Features: Use a custom view component class namespace Declare rout

null 5 Dec 12, 2021
🧱 A Roblox clone written in PHP powered by the Laravel framework.

Laravel Roblox Clone Originally written in June-July 2021 to get better with Laravel and to have something fun to work on. If you need any help, feel

Snoot 6 Dec 19, 2022
Decorate Your Models and Write Clean/Reusable Code with Presenters.

Laravel Presenter A clean way to present your model attributes without putting them in the wrong file. Installation You can install the package via co

Coderflex 14 Dec 19, 2022
⛽ Set of utilities to test Laravel applications powered by Octane.

⛽ Octane Testbench Set of utilities to test Laravel applications powered by Octane. Install Via Composer: composer require --dev cerbero/octane-testbe

Andrea Marco Sartori 35 Dec 7, 2022
Builds nice, normalized and easy to consume REST JSON responses for Laravel powered APIs.

REST API Response Builder for Laravel Master branch: Development branch: Table of contents Introduction Why should I use it? Usage examples Features E

Marcin Orlowski 614 Dec 26, 2022