moffhub/flow
Database-driven state machine & workflow engine for Laravel. Build multi-step approval gates with role/permission guards, auditable transitions, actions, parallel states, scheduled transitions, and a visual builder + workflow visualization for complex business processes.
Installation:
composer require moffhub/flow
php artisan vendor:publish --provider="Moffhub\Flow\FlowServiceProvider" --tag="config"
php artisan migrate
Define a Workflow: Create a workflow definition in a migration or seed:
use Moffhub\Flow\Workflow;
Workflow::create('tax_submission', [
'states' => ['draft', 'submitted', 'under_review', 'approved', 'rejected'],
'transitions' => [
['from' => 'draft', 'to' => 'submitted', 'action' => 'submit'],
['from' => 'submitted', 'to' => 'under_review', 'action' => 'review'],
// ... other transitions
],
]);
Attach to a Model:
Use the HasFlow trait in your Eloquent model:
use Moffhub\Flow\Concerns\HasFlow;
class TaxSubmission extends Model
{
use HasFlow;
protected $flowName = 'tax_submission';
}
First Transition: Trigger a transition via the model instance:
$submission = TaxSubmission::find(1);
$submission->flow()->transition('submit');
config/flow.php for default settings (e.g., audit trail retention, guard defaults).database/migrations/xxxx_create_flow_tables.php to understand the schema.Flow:: for global workflow operations (e.g., Flow::getWorkflow('tax_submission')).HasFlow trait and its methods (transition(), canTransition(), etc.).Government Revenue Submission Workflow:
tax_submission workflow with states like draft, submitted, approved.TaxSubmission model.transition('submit') to move from draft to submitted.approve transitions to tax_officers.Linear Approval Chains:
// Define in migration:
Workflow::create('loan_application', [
'states' => ['applied', 'reviewed', 'approved', 'rejected'],
'transitions' => [
['from' => 'applied', 'to' => 'reviewed', 'action' => 'initiate_review'],
['from' => 'reviewed', 'to' => 'approved', 'action' => 'approve'],
['from' => 'reviewed', 'to' => 'rejected', 'action' => 'reject'],
],
]);
transition('initiate_review') to kick off the process.Parallel States (Split/Join):
Workflow::create('inspection', [
'states' => ['pending', 'legal_review', 'technical_review', 'approved'],
'transitions' => [
['from' => 'pending', 'to' => ['legal_review', 'technical_review'], 'action' => 'split'],
['from' => ['legal_review', 'technical_review'], 'to' => 'approved', 'action' => 'join'],
],
]);
split to parallelize, then join to converge.Scheduled Transitions:
$submission->flow()->transition('escalate', ['scheduled_at' => now()->addHours(24)]);
flow.transitioning and flow.transitioned events for side effects:
event(new TaxSubmissionSubmitted($submission));
transition() with notify option:
$submission->flow()->transition('approve', [
'notify' => ['taxpayer', 'auditor'],
]);
Route::post('/submissions/{id}/approve', function ($id) {
$submission = TaxSubmission::findOrFail($id);
if ($submission->flow()->canTransition('approve')) {
$submission->flow()->transition('approve');
return response()->json(['status' => 'approved']);
}
abort(403);
})->middleware('can:approve-submissions');
Role-Based Guards:
Workflow::transition('approve')
->guard('role:tax_officer');
auth()->user()->hasRole('tax_officer').Conditional Guards:
Workflow::transition('approve')
->guard(function ($model, $transition) {
return $model->amount <= 1000;
});
Permission Guards:
Workflow::transition('reject')
->guard('permission:reject-tax-submissions');
Built-in Actions:
notify: Send notifications via Laravel Notifications.log: Append to the audit trail.update_attributes: Modify model attributes on transition.Custom Actions:
class SendEmailAction implements ActionContract
{
public function handle($model, $transition, $data)
{
Mail::to($model->taxpayer_email)->send(new ApprovalEmail());
}
}
Register in config:
'actions' => [
'send_email' => \App\Actions\SendEmailAction::class,
],
Use in workflow:
Workflow::transition('approve')
->action('send_email');
Circular References:
A → B → A). The package validates this but may throw cryptic errors if misconfigured.Flow::validateWorkflow('workflow_name') in a migration.Guard Short-Circuiting:
->guard(...)->guard(...) to chain multiple guards explicitly.Audit Trail Bloat:
flow_audit_trails. For high-volume systems, set audit_retention_days in config to auto-prune old entries.Parallel State Deadlocks:
split/join, ensure all parallel branches can eventually transition to the join state. Orphaned branches will block the workflow.timeout transition to force-complete stuck branches.Model Attribute Conflicts:
flow_name or flow_state attributes, they’ll conflict with the package’s reserved names. Rename them or exclude from mass assignment:protected $guarded = ['flow_name', 'flow_state'];
Transition Validation Errors:
flow_transitions table for valid from/to pairs. Use:dd(Flow::getWorkflow('tax_submission')->getValidTransitions());
Guard Failures:
'debug' => [
'guard_errors' => true,
],
storage/logs/flow.log.Visualization Issues:
Flow::visualize('workflow_name')) returns malformed JSON, clear compiled views:php artisan view:clear
Custom Guard Classes:
class MinimumBalanceGuard implements GuardContract
{
public function check($model, $transition)
{
return $model->balance >= 1000;
}
}
Register in config:
'guards' => [
'minimum_balance' => \App\Guards\MinimumBalanceGuard::class,
],
Use in workflow:
Workflow::transition('withdraw')
->guard('minimum_balance');
Action Data Serialization:
data option:$submission->flow()->transition('approve', [
'data' => ['reason' => '
How can I help you explore Laravel packages today?