Description
Description
Hello,
I really like the Workflow component, but not his yaml configuration. Constants in yaml are too verbose, as is passing config to php format. So I tried to create three attributes: [AsWorkflow], to be set on a class (which should extend AbstractWorkflow), [Place], to be set on a constant, and [Transition], also to be set on a constant. With these three attributes, and a few configurations that I've put in a dedicated bundle for now, it's possible to add to Symfony's Workflow component the ability to simply declare a Workflow via a PHP class, autowire it directly from that class (you can use can(), apply(), .. functions directly from the class) and, if need be, mix it with the classic yaml configuration without any hassle or BC Break.
In the bundle, metadata management, testing and documentation still need to be completed, but the code works well, it's being used in real projects and everything's running smoothly. Before going any further, I'd like to hear your opinion on whether this could be integrated directly into the Workflow component? Is this the right approach? I could do a PR if I get positive feedback.
The code is available here => https://packagist.org/packages/letots/workflow-extension-bundle
To test, simply run the compose require letots/workflow-extension-bundle command in a recent Symfony project (attribute support is required) and create a new class (see example below). You can then debug the workflow, use it in any other class with autowire (you can still use the WorkflowInterface with Target attribute but you can also inject directly your class), ...
Is it really useful? Apart from the syntax, which I personally find clearer in PHP, we have the advantage of not having to configure anything for this to work, of having a separate PHP file for each Workflow and therefore of having autowiring with autocompletion of the class name, the advantages of using constants for autocompletion and validation to define the names of Places and Transitions (no need to go back 10 times in the workflow.yaml file to find out how a particular status was named), ... Last argument, even if this only applies to a few very specific cases, it is possible to create a dynamic workflow by rewriting the getPlaces() and getTransitions() functions of the AbstractWorkflow file, in order to add Places dynamically, for example, depending on the properties of the target object, all grouped together in the dedicated workflow class.
Example
<?php
// src/Workflow/InterventionWorkflow.php (but can be placed anywhere, just need AsWorkflow attribute and extend AbstractWorkflow
namespace App\Workflow;
use App\Entity\Intervention;
use LeTots\WorkflowExtension\AbstractWorkflow;
use LeTots\WorkflowExtension\Attribute\AsWorkflow;
use LeTots\WorkflowExtension\Attribute\Place;
use LeTots\WorkflowExtension\Attribute\Transition;
#[AsWorkflow(name: 'intervention_status', type: AsWorkflow::TYPE_STATE_MACHINE, supportStrategy: Intervention::class)]
class InterventionWorkflow extends AbstractWorkflow
{
// Places
#[Place(initial: true)]
public const string STATE_QUALIFY = 'state_qualify';
#[Place]
public const string STATE_EXCLUSIONS = 'state_exclusions';
#[Place]
public const string STATE_CONSTANTS = 'state_constants';
#[Place]
public const string STATE_TEST = 'state_test';
#[Place]
public const string STATE_RESULT = 'state_result';
#[Place]
public const string STATE_FINISHED = 'state_finished';
// Transitions
#[Transition(from: self::STATE_QUALIFY, to: self::STATE_EXCLUSIONS)]
public const string TRANSITION_TO_EXCLUSIONS = 'transition_to_exclusions';
#[Transition(from: self::STATE_EXCLUSIONS, to: self::STATE_CONSTANTS)]
public const string TRANSITION_TO_CONSTANTS = 'transition_to_constants';
#[Transition(from: self::STATE_CONSTANTS, to: self::STATE_TEST)]
public const string TRANSITION_TO_TEST = 'transition_to_test';
#[Transition(from: self::STATE_TEST, to: self::STATE_RESULT)]
public const string TRANSITION_TO_RESULT = 'transition_to_result';
#[Transition(from: self::STATE_RESULT, to: self::STATE_FINISHED)]
public const string TRANSITION_RESULT_TO_FINISHED = 'transition_result_to_finished';
}