mrnewport / laravel-flow
Production-ready, domain-agnostic flow system for Laravel with multi-step, assignment, events, and notifications.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 3
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/mrnewport/laravel-flow
Requires
- php: >=8.1
- illuminate/database: ^11.0
- illuminate/events: ^11.0
- illuminate/notifications: ^11.0
- illuminate/support: ^11.0
- pestphp/pest: ^3.7
- pestphp/pest-plugin-laravel: ^3.0
- spatie/laravel-package-tools: ^1.9
Requires (Dev)
- orchestra/testbench: ^9.9
README
A domain-agnostic flow/workflow package for Laravel. It allows:
- FlowStep definitions with optional assignment strategies and notifications.
- FlowTransition actions that link one step to the next.
- FlowInstance tracking each entity’s position in the flow.
- FlowInstanceStep logging each step taken by that entity.
- A pivot-based assignment table (
flow_step_assignees), so you can assign one or many users or external emails. - Reassign logic if someone else needs to handle the step.
- Multi-step transitions (one action can lead to multiple next steps).
- Laravel events and notifications for each action.
Everything is infinitely expandable—with no forced domain or role logic. Perfect for multi-approval flows, complex multi-step processes, or advanced e-sign style flows.
Table of Contents
- Requirements
- Installation
- Configuration
- Database Structure
- Core Concepts
- Assignment Strategies
- Using the FlowManager
- Console Commands
- Flowable Trait
- Advanced Customization
- Testing
- License
Requirements
- Laravel ^11.0
- PHP ^8.1
- Illuminate (events, notifications, database)
- spatie/laravel-package-tools ^1.9
Installation
-
Install via Composer:
composer require mrnewport/laravel-flow
-
(Optional) Publish config & stubs:
php artisan vendor:publish --provider="MrNewport\\LaravelFlow\\FlowServiceProvider" --tag=flow-config php artisan vendor:publish --provider="MrNewport\\LaravelFlow\\FlowServiceProvider" --tag=flow-stubs
-
Migrate:
php artisan migrate
This creates
flow_steps,flow_transitions,flow_instances,flow_instance_steps, andflow_step_assignees.
Configuration
After publishing, open config/flow.php:
return [ 'user_model' => \App\Models\User::class, 'rejection_actions' => ['reject','cancel'], ];
user_model: If your assignment strategies reference Eloquent users, update here.rejection_actions: Flow actions that count as rejections/cancellations.
Database Structure
-
flow_steps: Each distinct step.id: The string identifier.name: A descriptive title.notify: If true, the system can automatically handle notifications.assignment_strategy: e.g.'single_user','multi_user','email_list'assignment_params: JSON object for that strategy.
-
flow_transitions: Action-based transitions.step_id: The current step’s ID.action: e.g.'approve','reject','submit'.next_step_id: The next step triggered by that action.
-
flow_instances: Tracks which step a particular model is on.current_step_id: The step the entity is currently at.model_type,model_id: Polymorphic references to your Eloquent model (e.g.App\Models\Post).
-
flow_instance_steps: Log of each step in a particular instance.flow_instance_id: references theflow_instancesrow.step_id: referencesflow_steps.action_taken: the action used to complete the step, if any.started_at,finished_at: timestamps.
-
flow_step_assignees: Pivot storing who must do a step.flow_instance_step_id: The instance step that’s assigned.assignee_type:'user','email','token', etc.assignee_value: The user ID, email address, or external reference.
Core Concepts
FlowStep
An individual step in your flow. Example IDs: 'draft_review', 'supplier_upload', 'marketing_approval'. Each step can specify:
notify: Iftrue, the system or an event listener can trigger notifications automatically.assignment_strategy,assignment_params: e.g.'single_user'with{"user_id":8}.
FlowTransition
Defines an action from step_id → next_step_id. For example, (draft_review, 'approve', marketing_approval) means if user approves at draft_review, the next step is marketing_approval.
FlowInstance
Records which step a specific entity (like a post, application, or user-submitted form) is currently at. Also references all steps that have been completed in flow_instance_steps.
FlowInstanceStep
A record of a step within a FlowInstance. We store the timestamps and the action that completed it. If the step has an assignment strategy, it’s also assigned to users/emails in flow_step_assignees.
FlowStepAssignee
Pivot that stores exactly who’s assigned to a step. For instance, 'assignee_type=user', 'assignee_value=3' means user #3 must handle it.
Assignment Strategies
The package includes SingleUserStrategy, MultiUserStrategy, and EmailListStrategy. They all implement AssignmentStrategyInterface, auto-populating flow_step_assignees whenever a new step is created.
SingleUserStrategy
assignment_params={"user_id":7}- Creates a single pivot row:
(flow_instance_step_id, 'user', '7').
MultiUserStrategy
assignment_params={"user_ids":[7,8]}- Creates multiple pivot rows for each user ID.
EmailListStrategy
assignment_params={"emails":["foo@bar.com","hello@domain.io"]}- Creates pivot rows with
'assignee_type=email','assignee_value="foo@bar.com"etc.
Reassigning a Step
Use:
FlowManager::reassignStep($stepInstance, $currentUser, $newAssignees);
- Checks if the strategy’s
canReassign($step, $currentUser)is true, then callsreassign($step, $newAssignees). - If it’s single-user strategy, we remove the old assignment, add the new. If multi-user, we remove or update accordingly.
Using the FlowManager
Below are typical calls to FlowManager.
Multi-Step Example with More Steps
Let’s define these steps:
start_requestmanager_reviewdirector_approvalsupplier_uploadmarketing_checkquality_assuranceEND
We can have transitions like:
(start_request, 'submit', manager_review)(manager_review, 'reject', start_request)(manager_review, 'approve', director_approval)(director_approval, 'approve', supplier_upload)(director_approval, 'reject', manager_review)(supplier_upload, 'upload_done', marketing_check)(marketing_check, 'changes_needed', supplier_upload)(marketing_check, 'approve', quality_assurance)(quality_assurance, 'approve', END)(quality_assurance, 'reject', supplier_upload)
This means your flow can bounce around multiple times if rejections occur.
Starting a Flow
- Define your step with
flow:define-stepor code. For example:php artisan flow:define-step start_request "Start Request" --strategy=single_user --params='{"user_id":3}'
Next define transitions:php artisan flow:define-step start_request --actions="submit:manager_review" - In your code:
$entity = new SomeModel(...); $flowInstance = FlowManager::startFlow('start_request',$entity); // This creates a FlowInstance row + FlowInstanceStep row // If step "start_request" has single_user with user_id=3, it is assigned to user 3.
Completing a Step with an Action
$currentStep = $flowInstance->steps() ->whereNull('finished_at') ->first(); FlowManager::actionStep($currentStep,'submit');
- If
(start_request, 'submit') => manager_reviewtransition exists, a newFlowInstanceStepis created formanager_review, assigned if needed, andcurrent_step_idupdates to'manager_review'.
Multiple Transitions
One (step_id, action) can lead to multiple next_step_id:
flow:define-step multi_out --actions="done:stepX,done:stepY,done:stepZ"
Now calling:
FlowManager::actionStep($multiOutStep, 'done');
Creates three new steps: stepX, stepY, stepZ.
End Step
If a transition’s next_step_id='END', that signals the flow is finished. FlowInstance is updated to current_step_id='END', no further steps.
Events
When you call:
FlowManager::actionStep($oldStep,'approve');
a FlowActionEvent($oldStep, 'approve', $newStepOrNull) is fired. Use standard Laravel event listeners to add custom logic or extra notifications.
Notifications
If flow_steps.notify==true, you can have your listeners automatically send out FlowBaseNotification or custom notifications to the assigned users/emails. Typically:
- Check
flow_step_assigneesfor that step. - If
'assignee_type=user', load the user’s Eloquent record fromconfig('flow.user_model'). - If
'assignee_type=email', route a mail-based notification.
Console Commands
DefineStepCommand
Use this command to create or update a step and define transitions in one shot:
php artisan flow:define-step manager_review "Manager Review" \ --notify=true \ --strategy=multi_user \ --params='{"user_ids":[3,5,8]}' \ --actions="approve:director_approval,reject:start_request"
Prints lines for each transition, then:
Step [manager_review] defined/updated successfully.
Flowable Trait
If you want to manage flows directly from an Eloquent model, you can:
use MrNewport\LaravelFlow\Traits\Flowable; class Document extends Model { use Flowable; }
Then:
$doc = Document::create([...]); $doc->startFlow('start_request'); $current = $doc->currentFlowStep(); if($current) { $doc->flowAction('submit'); }
No direct calls to FlowManager.
Advanced Customization
Below are expansion ideas purely through your own code (events, custom classes).
1. Custom Assignment Strategies
If your domain requires specialized logic—like a Token or Slack approach—create a class implementing AssignmentStrategyInterface:
namespace App\FlowAssignments; use MrNewport\LaravelFlow\Models\FlowInstanceStep; use MrNewport\LaravelFlow\Models\FlowStepAssignee; use MrNewport\LaravelFlow\Assignments\AssignmentStrategyInterface; class SlackChannelStrategy implements AssignmentStrategyInterface { public function __construct(protected array $params=[]) { // e.g. $params['channel'] => '#general' } public function assign(FlowInstanceStep $instanceStep): void { // store the Slack channel FlowStepAssignee::create([ 'flow_instance_step_id'=>$instanceStep->id, 'assignee_type'=>'slack_channel', 'assignee_value'=>$this->params['channel'] ]); } public function canReassign(FlowInstanceStep $instanceStep, $currentUser): bool { // If you have logic for reassigning Slack channels, define here return false; } public function reassign(FlowInstanceStep $instanceStep, array $newAssignees): void { // Example: update the Slack channel $instanceStep->assignees()->delete(); foreach($newAssignees as $chan){ FlowStepAssignee::create([ 'flow_instance_step_id'=>$instanceStep->id, 'assignee_type'=>'slack_channel', 'assignee_value'=>$chan ]); } } }
Then reference 'slack_channel' in your step’s assignment_strategy and 'assignment_params'=>{"channel":"#my-team"}.
2. Event Listeners for Step+Action Logic
Whenever FlowManager::actionStep($oldStep, 'approve') is called, a FlowActionEvent fires. You can define a listener:
use MrNewport\LaravelFlow\Events\FlowActionEvent; Event::listen(FlowActionEvent::class, function(FlowActionEvent $e){ if($e->oldStep->step_id==='manager_review' && $e->action==='approve') { // E.g. log something or call external API } });
No need to modify package code—you handle domain logic in your event listener.
3. Larger Example: 6+ Steps, Complex Branching
Suppose you define these steps: stepA, stepB, stepC, stepD, stepE, stepF, and END. You can do:
flow:define-step stepA "Step A" --actions="go:stepB,skip:stepC"
flow:define-step stepB "Step B" --actions="approve:stepD,reject:END"
flow:define-step stepC "Step C" --actions="done:stepD"
flow:define-step stepD "Step D" --actions="approve:stepE, reject:stepB"
flow:define-step stepE "Step E" --actions="all_good:stepF, revision:stepC"
flow:define-step stepF "Step F" --actions="finalize:END"
flow:define-step END "Flow End"
Now you have a multi-branch flow with looping between stepB/stepD if rejections happen, or skipping stepB by going stepA → skip → stepC. No domain constraints, purely structured in your transitions.
4. Rejection Actions and External Approvals
If you set config('flow.rejection_actions')=['reject','cancel'], any step completed with 'reject' or 'cancel' is considered a rejection. You can add a step for 'supplier_upload' with transitions 'action=reject => rework_assets'. That flow logic is fully under your control. If you want external e-sign or approval, store the token or link in assignment_params and let the user finalize externally.
5. Integrating with External Services
Each time a new step is created, you could:
- Post to Slack with the assigned user info.
- Call an external DocuSign or HelloSign API to request a signature.
- Send events to a microservice queue.
All done in your listeners or custom code that runs upon step creation.
6. Additional Notifications & Channels
FlowBaseNotification is a mail-based example. You can define:
FlowSlackNotification:via()returns[SlackChannel::class], posting a Slack message.FlowSmsNotification: using Twilio or Nexmo.- No changes in the package are required. You just create these in your app code and send them from an event listener or a custom approach.
Testing
A comprehensive test suite lives in tests/. Just run:
composer test
It covers:
DefineStepCommandTest: verifying creation of steps & transitions with exact console output.FlowManagerBasicTest: testsstartFlow& single-step transitions.FlowManagerAssignmentTest: ensures assignment & reassign logic works.FlowManagerMultiTransitionsTest: multiple next steps for one action.FlowEventTest: verifiesFlowActionEventis fired.FlowNotificationTest: demonstrates how notifications can be triggered for assigned users/emails.
License
This package is open-sourced software licensed under the MIT license. Enjoy building fully domain-agnostic flows for your Laravel application!