Add DBFlow to an Eloquent Model
DBFlow is designed around a single principle: your Eloquent model is the workflow subject. A workflow is not a global pipeline — it is always bound to a specific record instance.
Model-first design
When a refund dispute is approved or a procurement request is signed off, the workflow belongs to that record. DBFlow stores workflow state in its own tables and references your model through workflowable_type and workflowable_id.
Your model stays clean. No workflow columns are required unless you choose to mirror business status locally.
Typical workflow subjects
| Model | Workflow |
|---|---|
RefundDispute |
Support lead → amount gate → risk review |
ProcurementRequest |
Manager → amount gate → finance review |
ExpenseClaim |
Manager → finance chain |
Vendor |
Compliance → legal → onboarding |
Order |
Auto-approve below threshold, manual review above |
Any model that needs multi-step human review, conditional branching, or a full audit trail is a good candidate.
Attaching DBFlow to a model
Add the HasWorkflow trait. Implement optional contracts when you need richer integration:
| Contract | Purpose |
|---|---|
Workflowable |
Business key and display name for admin UI |
WorkflowContextInterface |
Variables exposed to transition conditions |
WorkflowRouteResolvable |
Link from workflow UI back to your Filament resource |
<?php
namespace App\Models;
use DbflowLabs\Core\Contracts\Workflowable;
use DbflowLabs\Core\Contracts\WorkflowContextInterface;
use DbflowLabs\Core\Traits\HasWorkflow;
use Illuminate\Database\Eloquent\Model;
class ProcurementRequest extends Model implements Workflowable, WorkflowContextInterface
{
use HasWorkflow;
public function workflowBusinessKey(): ?string
{
return $this->reference_number;
}
public function workflowDisplayName(): string
{
return (string) $this->title;
}
public function getWorkflowVariables(): array
{
return ['amount' => (float) $this->amount];
}
}
No migration changes are required on your model's table for DBFlow itself. Publish and run DBFlow migrations during installation.
HasWorkflow helpers
$request->startWorkflow('procurement_request_approval');
$request->workflowInstances();
$request->latestWorkflowInstance('procurement_request_approval');
$request->runningWorkflowInstance('procurement_request_approval');
$request->completedWorkflowInstance('procurement_request_approval'); // latest terminal: approved | rejected | cancelled
$request->hasRunningWorkflow('procurement_request_approval');
$request->workflowLogs('procurement_request_approval');
startWorkflow($key) delegates to DBFlow::start($key, $this, DbflowAuth::currentUser()).
When config('dbflow.binding_mode') is ui, workflows may auto-start on model created only when the matching dbflow_workflows row has model_type equal to the workflowable morph class and a published version exists. The default alpha mode is code, which requires an explicit start call. SyncWorkflowDefinitions does not set model_type — configure it in seeders or admin tooling. See Host Integration. SyncWorkflowDefinitions does not set model_type automatically — configure it in seeders or admin tooling.
How state is stored
dbflow_workflows — workflow registry and draft metadata
dbflow_workflow_versions — versioned JSON definitions
dbflow_workflow_instances — one row per run (status, current_node_key, active_key)
dbflow_workflow_tasks — approval tasks per instance
dbflow_workflow_task_assignments — per-user assignments
dbflow_workflow_logs — audit trail
This keeps your procurement_requests table focused on business data.
Multiple workflows per model
A model can participate in multiple workflow definitions. Start the intended workflow explicitly by key:
$request->startWorkflow('procurement_request_approval');
$request->startWorkflow('urgent_procurement_approval');
Use runningWorkflowInstance($workflowKey) or hasRunningWorkflow($workflowKey) to scope queries per definition.
Status mapping (optional)
DBFlow tracks workflow status on dbflow_workflow_instances. To mirror status onto your model column, use workflow hooks — not Laravel event classes:
<?php
namespace App\DBFlow\ProcurementRequest;
use DbflowLabs\Core\Contracts\WorkflowHooks;
use DbflowLabs\Core\Models\WorkflowInstance;
final class ProcurementRequestWorkflowHooks implements WorkflowHooks
{
public function onStarted(WorkflowInstance $instance): void {}
public function onApproved(WorkflowInstance $instance): void
{
$instance->workflowable?->update(['status' => 'approved']);
}
public function onRejected(WorkflowInstance $instance): void
{
$instance->workflowable?->update(['status' => 'rejected']);
}
public function onCancelled(WorkflowInstance $instance): void {}
}
Register hooks during application boot:
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Services\WorkflowHooksRegistry;
DBFlow::registerWorkflowHooks(
app(WorkflowHooksRegistry::class),
'procurement_request_approval',
ProcurementRequestWorkflowHooks::class,
);
Keep models clean
Avoid embedding the full approval state machine inside the model:
// Avoid this pattern
public function approve(User $user): void
{
if ($this->status === 'pending_finance') {
$this->status = 'pending_manager';
// ...
}
}
Instead, resolve the pending task and call the runtime:
use DbflowLabs\Core\DBFlow;
use DbflowLabs\Core\Enums\WorkflowTaskStatus;
$task = $request->runningWorkflowInstance('procurement_request_approval')
?->tasks()
->where('status', WorkflowTaskStatus::Pending)
->first();
DBFlow::approve($task, $user);
Next step
Host Integration → — register, sync, assignees
Purchase Request Approval → — second demo scenario with status mapping
Or continue with Code-defined Workflows →