letots / workflow-extension-bundle
Configurations to allow defining Symfony Workflows as PHP classes easily and little helpers functions to work with them.
Package info
github.com/letots/workflow-extension-bundle
pkg:composer/letots/workflow-extension-bundle
Requires
- php: >=8.3
- symfony/event-dispatcher-contracts: ^2.5|^3.0
- symfony/workflow: ^6.4|^7.0
Requires (Dev)
- psr/log: ^1|^2|^3
- symfony/dependency-injection: ^6.4|^7.0
- symfony/error-handler: ^6.4|^7.0
- symfony/event-dispatcher: ^6.4|^7.0
- symfony/expression-language: ^6.4|^7.0
- symfony/http-kernel: ^6.4|^7.0
- symfony/stopwatch: ^6.4|^7.0
- symfony/validator: ^6.4|^7.0
README
Define Symfony Workflows as PHP classes using attributes, with helpers to work with them.
Workflow class
#[AsWorkflow(name: 'order', type: AsWorkflow::TYPE_STATE_MACHINE)] class OrderWorkflow extends AbstractWorkflow { public const PLACE_NEW = 'new'; #[Place(initial: true)] public const PLACE_DRAFT = 'draft'; #[Transition(from: self::PLACE_DRAFT, to: self::PLACE_NEW)] public const TRANSITION_PUBLISH = 'publish'; }
Inject the workflow by name:
public function __construct( private WorkflowInterface $order, ) {}
Or use the alias workflow.order.
Transitions
#[Transition]must be placed on a class constant. The constant value is the transition name.- In
TYPE_STATE_MACHINE, each constant declares a single arc: onefromplace and onetoplace. - To reach the same destination from several places, declare several constants with the same transition name (same constant value), each with its own
fromplace.
#[Transition(from: self::PLACE_DRAFT, to: self::PLACE_NEW)] public const TRANSITION_PUBLISH = 'publish'; #[Transition(from: self::PLACE_REVIEW, to: self::PLACE_NEW)] public const TRANSITION_PUBLISH_FROM_REVIEW = 'publish';
In TYPE_WORKFLOW, from and to may be arrays for multi-place transitions.
Metadata
Metadata can be attached to the workflow, places, and transitions. Retrieve it via $workflow->getMetadataStore() (same API as Symfony YAML workflows).
#[AsWorkflow(name: 'order', metadata: ['title' => 'Order workflow'])] class OrderWorkflow extends AbstractWorkflow { #[Place(initial: true, metadata: ['title' => 'Draft'])] public const PLACE_DRAFT = 'draft'; #[Transition(from: self::PLACE_DRAFT, to: self::PLACE_NEW, metadata: ['title' => 'Publish'])] public const TRANSITION_PUBLISH = 'publish'; }
Audit trail
Enable workflow activity logging (requires the logger service, channel workflow):
#[AsWorkflow(name: 'order', auditTrail: true)] class OrderWorkflow extends AbstractWorkflow { }
Guards
Guards are standard Symfony workflow guard listeners. The bundle injects the application event_dispatcher into each workflow service so guard events are dispatched.
#[AsEventListener(event: 'workflow.order.guard')] public function onGuard(GuardEvent $event): void { if ($event->getTransitionName() === 'publish' && !$this->authorizationChecker->isGranted('ROLE_ADMIN')) { $event->setBlocked(true, 'Not allowed.'); } }
Replace order with the workflow name from #[AsWorkflow(name: '...')].
Registry
When using supportStrategy with a class name string, the bundle registers an InstanceOfSupportStrategy automatically:
#[AsWorkflow(name: 'order', supportStrategy: Order::class)] class OrderWorkflow extends AbstractWorkflow { }