Reusable symfony/workflow configuration
This tutorial expects you to have a basic understanding of symfony and php in general!
If you use the symfony/workflow component you start by defining your workflow in the workflow.yaml config file.
To use it, you need to remember your various states and transitions to use them.
I’m not a friend of having this string values scattered around in my code base (or in templates).
So I take a different approach to use the workflows.
PHP side of things
Let’s start with a simple state machine. I just reuse the example given in the symfony documentation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| # config/packages/workflow.yaml
framework:
workflows:
pull_request:
type: 'state_machine'
marking_store:
type: 'method'
property: 'currentPlace'
supports:
- App\Entity\PullRequest
initial_marking: start
places:
- start
- coding
- test
- review
- merged
- closed
transitions:
submit:
from: start
to: test
update:
from: [coding, test, review]
to: test
wait_for_review:
from: test
to: review
request_change:
from: review
to: coding
accept:
from: review
to: merged
reject:
from: review
to: closed
reopen:
from: closed
to: review
|
That’s a lot of states and transitions to remember.
Let’s transfer this information to a reusable interface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
| namespace App\Entities;
interface PullRequestWorkflowInterface
{
// Start with defining all states
public const STATE_START = 'start';
public const STATE_CODING = 'coding';
public const STATE_TEST = 'test';
public const STATE_REVIEW = 'review';
public const STATE_MERGED = 'merged';
public const STATE_CLOSED = 'closed';
// Next we need all states as array
public const STATES = [
self::STATE_START,
self::STATE_CODING,
self::STATE_TEST,
self::STATE_REVIEW,
self::STATE_MERGED,
self::STATE_CLOSED,
];
// now lets define the transition names
public const TRANSITION_SUBMIT = 'submit';
public const TRANSITION_UPDATE = 'update';
public const TRANSITION_WAIT_FOR_REVIEW = 'wait_for_review';
public const TRANSITION_REQUEST_CHANGE = 'request_change';
public const TRANSITION_ACCEPT = 'accept';
public const TRANSITION_REJECT = 'reject';
public const TRANSITION_REOPEN = 'reopen';
// time for the transition rules
public CONST TRANSITIONS = [
self::TRANSITION_SUBMIT => [
'from' => [self::STATE_START],
'to' => self::STATE_TEST,
],
self::TRANSITION_UPDATE => [
'from' => [
self::STATE_CODING,
self::STATE_TEST,
self::STATE_REVIEW,
],
'to' => self::STATE_TEST,
],
self::TRANSITION_WAIT_FOR_REVIEW => [
'from' => [self::STATE_TEST],
'to' => self::STATE_REVIEW,
],
self::TRANSITION_REQUEST_CHANGE => [
'from' => [self::STATE_REVIEW],
'to' => self::STATE_CODING,
],
self::TRANSITION_ACCEPT => [
'from' => [self::STATE_REVIEW],
'to' => self::STATE_MERGED,
],
self::TRANSITION_REJECT => [
'from' => [self::STATE_REVIEW],
'to' => self::STATE_CLOSED,
],
self::TRANSITION_REOPEN => [
'from' => [self::STATE_CLOSED],
'to' => self::STATE_REVIEW,
],
];
// for completness - define a method for the marking_store
public function getCurrentState(): string;
|
Much better. We now can use this interface even in multiple classes if needed.
All left todo is using the interface:
1
2
3
4
5
6
7
8
9
10
11
12
| namespace App\Entities;
class PullRequest implements PullRequestWorkflowInterface
{
// [....]
public function getCurrentState(): string
{
// or however you called your field
return $this->currentState;
}
}
|
This would already work - but we dont want to have two places to maintain our config. So let’s make the workflow.yaml use our constants too.
All we need in workflow.yaml is:
1
2
3
4
5
6
7
8
9
10
11
12
| framework:
workflows:
pull_request:
type: 'state_machine'
marking_store:
type: 'method'
property: 'currentState'
supports:
- App\Entities\PullRequest
initial_marking: !php/const App\Entities\PullRequest::STATE_START
places: !php/const App\Entities\PullRequest::STATES
transitions: !php/const App\Entities\PullRequest::TRANSITIONS
|
That’s it. Tidy isn’t it?
But what now? Just use the public constants:
1
2
| if ($stateMachine->can($pullRequest, PullRequest::TRANSITION_REVIEW)) {
}
|
even in your Templates
1
2
3
| {% if workflow_transition(object, constant("App\\Entity\\PullRequest::TRANSITION_REJECT")) %}
{% endif %}
|
From now on, you can change any aspect of your state machine in one single place. Thanks to autocomplete you don’t have to remember each and every state or transition and you can’t make typing errors.
Dumping the workflow to a image still works of course. We just moved the definition from yaml to php.