New in Symfony 8.2 (in development). A workflow used to be described in YAML: its places, its transitions, the object it follows. Its guards and listeners lived elsewhere, in classes that referred to it by name, and the code that moved it along repeated the transition names as strings. Symfony 8.2 brings it all together in one class.
Before
framework: workflows: ticket: type: state_machine supports: [App\Model\Ticket] marking_store: { type: method, property: marking } initial_marking: open transitions: start: { from: open, to: in_progress } # …
public function history(#[Target('ticket')] WorkflowInterface $workflow): Response { $workflow->apply($ticket, 'start');
Since Symfony 8.2
#[AsWorkflow(supports: Ticket::class)] final class TicketWorkflow { use WorkflowTrait; #[Transition(from: 'open', to: 'in_progress')] public const START = 'start'; #[AsGuardListener(transition: self::START)] public function check(GuardEvent $event): void { /* … */ } }
public function history(TicketWorkflow $workflow): Response { $workflow->apply($ticket, TicketWorkflow::START);
#[AsWorkflow] turns the class into a workflow, named after it: TicketWorkflow gives ticket. It is a state machine by default (type: WorkflowType::Workflow makes it a workflow, where an object can be in several places at once), and the marking is stored in the subject's marking property. Each constant marked #[Transition] is a transition; the places are inferred from them, and the input place (from) of the first transition becomes the initial place. An enum can also provide the places, with places: and #[Place] for their metadata.
The class remains an ordinary service: it receives its dependencies and carries its own listeners, which no longer need to name the workflow. With WorkflowTrait, it also exposes the methods of a workflow: you inject it, and its constants replace the strings.
Your task
Customer support tracks its tickets with a state machine described in config/packages/workflow.yaml, and its guard, which forbids resolving a ticket without an answer, already lives in TicketWorkflow. The /ticket preview walks through a ticket's lifecycle and shows, at each step, the enabled transitions.
- Describe the
ticketworkflow inTicketWorkflow, one constant per transition, and remove it from the YAML. - Let the guard apply to the workflow of its class, without naming it.
- In
TicketController, receiveTicketWorkflowand replace the transition names with its constants.
The preview must show exactly the same path as before.