Installation Add the package via Composer:
composer require cvek/workflow
For Laravel (Symfony-based), ensure compatibility with Symfony components (e.g., symfony/workflow).
First Use Case: Basic Workflow Definition Define a workflow in a Laravel service provider or config file:
use Cvek\Workflow\Workflow;
$workflow = new Workflow('order_workflow', [
'draft' => ['allowed_to' => ['submit']],
'submitted' => ['allowed_to' => ['approve', 'reject']],
'approved' => ['allowed_to' => []],
'rejected' => ['allowed_to' => []],
]);
Register Workflow Bind the workflow to Laravel’s container in a service provider:
$this->app->singleton('order_workflow', fn() => $workflow);
Apply to a Model
Use the HasWorkflow trait on a model (e.g., Order):
use Cvek\Workflow\Traits\HasWorkflow;
class Order extends Model
{
use HasWorkflow;
protected $workflowKey = 'order_workflow';
}
Trigger a Transition Transition an order state in a controller or command:
$order = Order::find(1);
$order->apply('submit'); // Transitions from 'draft' to 'submitted'
State Persistence
Store the current state in a database column (e.g., status):
protected $workflowStateColumn = 'status';
Validation Before Transition
Override canApply() to add custom logic:
public function canApply($transition)
{
if ($transition === 'approve' && $this->amount > 1000) {
return false;
}
return parent::canApply($transition);
}
Event Listeners
Attach workflow events (e.g., transitioning, transitioned) via Laravel’s event system:
$order->onTransitioning(fn($transition) => Log::info("Transitioning to $transition"));
API Endpoints Create a controller to handle transitions:
public function transition(Order $order, $transition)
{
if ($order->canApply($transition)) {
$order->apply($transition);
return response()->json(['success' => true]);
}
return response()->json(['error' => 'Invalid transition'], 400);
}
Artisan Commands Build a command for bulk transitions:
public function handle()
{
Order::where('status', 'draft')->each(fn($order) => $order->apply('submit'));
}
Dynamic Workflows Load workflows from a database or config:
$workflow = new Workflow('dynamic_workflow', config('workflows.dynamic'));
Guard Clauses
Extend the WorkflowGuard trait to enforce rules:
class CustomGuard extends WorkflowGuard
{
public function canApply($transition, $object)
{
if ($object->isAdmin() && $transition === 'force_approve') {
return true;
}
return parent::canApply($transition, $object);
}
}
State Initialization
Ensure the model’s initial state matches the workflow’s starting place (e.g., draft). Use boot() to set defaults:
protected static function boot()
{
parent::boot();
static::creating(fn($model) => $model->status = 'draft');
}
Circular Dependencies
Avoid recursive transitions (e.g., A → B → A). Validate transitions explicitly:
if ($order->status === 'approved' && $transition === 'reject') {
throw new \RuntimeException('Cannot reject an approved order.');
}
Race Conditions Use database transactions for critical transitions:
DB::transaction(fn() => $order->apply('submit'));
Log Transitions Enable debug mode in the workflow:
$workflow->setDebug(true); // Logs transitions to Laravel logs.
Check Allowed Transitions
Use getAllowedTransitions() to inspect valid moves:
dd($order->getAllowedTransitions()); // ['submit']
Custom Transition Logic
Override apply() to add pre/post hooks:
public function apply($transition)
{
$this->fireTransitioning($transition);
$this->updateState($transition);
$this->fireTransitioned($transition);
}
Workflow Events Listen for transitions globally:
event(new OrderTransitioning($order, 'submit'));
Testing Mock workflows in tests:
$workflow = Mockery::mock(Workflow::class);
$workflow->shouldReceive('canApply')->andReturn(true);
$order->setWorkflow($workflow);
How can I help you explore Laravel packages today?