symfony/workflow
Symfony Workflow component helps model and run workflows or finite state machines. Define places and transitions, guard rules, events, and marking stores to track state changes and integrate processes cleanly into your application.
Install the Package
composer require symfony/workflow
For Laravel, prefer using Symfony’s standalone component (no framework bundle needed).
Define a Workflow (YAML/Array)
Create a workflow definition (e.g., config/workflows/order_workflow.yaml):
# Example: Order Processing Workflow
order_workflow:
support:
- 'Symfony\Component\Workflow\WorkflowInterface'
- 'Symfony\Component\Workflow\MarkingStoreInterface'
marking_store:
type: 'method'
property: 'status'
getter: 'getStatus'
setter: 'setStatus'
initial_marking: 'draft'
places:
- name: 'draft'
- name: 'submitted'
- name: 'approved'
- name: 'rejected'
- name: 'shipped'
- name: 'delivered'
transitions:
submit:
from: 'draft'
to: 'submitted'
approve:
from: 'submitted'
to: 'approved'
reject:
from: 'submitted'
to: 'rejected'
ship:
from: 'approved'
to: 'shipped'
deliver:
from: 'shipped'
to: 'delivered'
Load the Workflow in Laravel Use a service provider or manually instantiate:
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;
use Symfony\Component\Workflow\Support\YamlWorkflowDefinition;
// In a service provider or bootstrap file
$definition = new YamlWorkflowDefinition(file_get_contents(__DIR__.'/config/workflows/order_workflow.yaml'));
$workflow = new Workflow($definition);
$markingStore = new MethodMarkingStore($order, 'status', 'getStatus', 'setStatus');
$workflow->apply($markingStore);
Apply Transitions in Code
// Transition an order from 'draft' to 'submitted'
$workflow->apply($order, 'submit');
// Check current state
if ($workflow->can($order, 'approve')) {
$workflow->apply($order, 'approve');
}
First Use Case: Order Status UI Display allowed transitions in a Blade view:
@foreach ($workflow->getEnabledTransitions($order) as $transition)
<button onclick="applyTransition('{{ $transition->getName() }}')">
{{ ucfirst($transition->getName()) }}
</button>
@endforeach
workflow:dump CLI or GraphvizDumper to visualize workflows).MethodMarkingStore, DoctrineMarkingStore, or custom stores).user.is_admin to transitions).config/workflows/user_activation.yaml):
user_activation:
initial_marking: 'pending'
places:
- name: 'pending'
- name: 'activated'
- name: 'suspended'
- name: 'banned'
transitions:
activate:
from: 'pending'
to: 'activated'
suspend:
from: ['activated', 'pending']
to: 'suspended'
ban:
from: ['activated', 'pending', 'suspended']
to: 'banned'
use Symfony\Component\Workflow\Workflow;
class User extends Model
{
protected $workflow;
public function boot()
{
$definition = new YamlWorkflowDefinition(file_get_contents(__DIR__.'/../../config/workflows/user_activation.yaml'));
$this->workflow = new Workflow($definition);
$this->workflow->apply($this);
}
public function activate()
{
$this->workflow->apply($this, 'activate');
$this->save();
}
}
public function activate(User $user)
{
$user->activate();
return redirect()->route('dashboard');
}
# config/workflows/hr_onboarding.yaml
hr_onboarding:
initial_marking: 'submitted'
places:
- name: 'submitted'
transitions: ['review', 'reject']
- name: 'under_review'
transitions: ['approve', 'request_changes']
- name: 'approved'
transitions: ['hire']
- name: 'rejected'
- name: 'on_hold'
transitions:
review:
from: 'submitted'
to: 'under_review'
approve:
from: 'under_review'
to: 'approved'
reject:
from: ['submitted', 'under_review']
to: 'rejected'
request_changes:
from: 'under_review'
to: 'submitted'
hire:
from: 'approved'
to: 'hired'
$definition = [
'initial_marking' => 'draft',
'places' => [
['name' => 'draft'],
['name' => 'published'],
['name' => 'archived'],
],
'transitions' => [
'publish' => ['from' => 'draft', 'to' => 'published'],
'archive' => ['from' => 'published', 'to' => 'archived'],
],
];
$workflow = new Workflow(new ArrayWorkflowDefinition($definition));
Use workflow factories for tenant-specific or role-based workflows:
class WorkflowFactory
{
public static function createForTenant(Tenant $tenant)
{
$definition = self::getDefinitionForTenant($tenant);
return new Workflow(new ArrayWorkflowDefinition($definition));
}
protected static function getDefinitionForTenant(Tenant $tenant): array
{
return match ($tenant->plan) {
'enterprise' => self::enterpriseWorkflow(),
default => self::standardWorkflow(),
};
}
}
$markingStore = new MethodMarkingStore(
$order, // Subject
'status', // Property name
'getStatus', // Getter method
'setStatus' // Setter method
);
$workflow->apply($markingStore);
use Symfony\Component\Workflow\MarkingStore\DoctrineMarkingStore;
$markingStore = new DoctrineMarkingStore(
$entityManager,
$order, // Subject
'order_status' // Table name
);
$workflow->apply($markingStore);
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
class RedisMarkingStore implements MarkingStoreInterface
{
public function __construct(private Redis $redis) {}
public function getMarking(string $name): ?string
{
return $this->redis->get("workflow:{$name}");
}
public function setMarking(string $name, ?string $marking): void
{
$this->redis->set("workflow:{$name}", $marking);
}
public function getMarkings(): array
{
return [$this->getMarking('status')];
}
}
Add logic to transitions (e.g., "only approve if budget > $X"):
transitions:
approve:
from: 'submitted'
to: 'approved'
guard: 'order_budget_guard'
How can I help you explore Laravel packages today?