11
11
12
12
namespace Symfony \Component \Workflow \DataCollector ;
13
13
14
+ use Symfony \Component \EventDispatcher \EventDispatcherInterface ;
14
15
use Symfony \Component \HttpFoundation \Request ;
15
16
use Symfony \Component \HttpFoundation \Response ;
16
17
use Symfony \Component \HttpKernel \DataCollector \DataCollector ;
17
18
use Symfony \Component \HttpKernel \DataCollector \LateDataCollectorInterface ;
19
+ use Symfony \Component \HttpKernel \Debug \FileLinkFormatter ;
18
20
use Symfony \Component \Workflow \Dumper \MermaidDumper ;
21
+ use Symfony \Component \Workflow \EventListener \GuardExpression ;
22
+ use Symfony \Component \Workflow \EventListener \GuardListener ;
19
23
use Symfony \Component \Workflow \StateMachine ;
24
+ use Symfony \Component \Workflow \Transition ;
25
+ use Symfony \Component \Workflow \WorkflowInterface ;
20
26
21
27
/**
22
28
* @author Grégoire Pineau <lyrixx@lyrixx.info>
@@ -25,6 +31,8 @@ final class WorkflowDataCollector extends DataCollector implements LateDataColle
25
31
{
26
32
public function __construct (
27
33
private readonly iterable $ workflows ,
34
+ private readonly EventDispatcherInterface $ eventDispatcher ,
35
+ private FileLinkFormatter $ fileLinkFormatter ,
28
36
) {
29
37
}
30
38
@@ -39,6 +47,7 @@ public function lateCollect(): void
39
47
$ dumper = new MermaidDumper ($ type );
40
48
$ this ->data ['workflows ' ][$ workflow ->getName ()] = [
41
49
'dump ' => $ dumper ->dump ($ workflow ->getDefinition ()),
50
+ 'listeners ' => $ this ->getEventListeners ($ workflow ),
42
51
];
43
52
}
44
53
}
@@ -57,4 +66,113 @@ public function getWorkflows(): array
57
66
{
58
67
return $ this ->data ['workflows ' ] ?? [];
59
68
}
69
+
70
+ public function hash (string $ string ): string
71
+ {
72
+ return hash ('xxh128 ' , $ string );
73
+ }
74
+
75
+ private function getEventListeners (WorkflowInterface $ workflow ): array
76
+ {
77
+ $ listeners = [];
78
+ $ placeId = 0 ;
79
+ foreach ($ workflow ->getDefinition ()->getPlaces () as $ place ) {
80
+ $ eventNames = [];
81
+ $ subEventNames = [
82
+ 'leave ' ,
83
+ 'enter ' ,
84
+ 'entered ' ,
85
+ ];
86
+ foreach ($ subEventNames as $ subEventName ) {
87
+ $ eventNames [] = sprintf ('workflow.%s ' , $ subEventName );
88
+ $ eventNames [] = sprintf ('workflow.%s.%s ' , $ workflow ->getName (), $ subEventName );
89
+ $ eventNames [] = sprintf ('workflow.%s.%s.%s ' , $ workflow ->getName (), $ subEventName , $ place );
90
+ }
91
+ foreach ($ eventNames as $ eventName ) {
92
+ // @phpstan-ignore-next-line
93
+ foreach ($ this ->eventDispatcher ->getListeners ($ eventName ) as $ listener ) {
94
+ $ listeners ["place {$ placeId }" ][$ eventName ][] = $ this ->summarizeListener ($ listener );
95
+ }
96
+ }
97
+
98
+ ++$ placeId ;
99
+ }
100
+
101
+ foreach ($ workflow ->getDefinition ()->getTransitions () as $ transitionId => $ transition ) {
102
+ $ eventNames = [];
103
+ $ subEventNames = [
104
+ 'guard ' ,
105
+ 'transition ' ,
106
+ 'completed ' ,
107
+ 'announce ' ,
108
+ ];
109
+ foreach ($ subEventNames as $ subEventName ) {
110
+ $ eventNames [] = sprintf ('workflow.%s ' , $ subEventName );
111
+ $ eventNames [] = sprintf ('workflow.%s.%s ' , $ workflow ->getName (), $ subEventName );
112
+ $ eventNames [] = sprintf ('workflow.%s.%s.%s ' , $ workflow ->getName (), $ subEventName , $ transition ->getName ());
113
+ }
114
+ foreach ($ eventNames as $ eventName ) {
115
+ // @phpstan-ignore-next-line
116
+ foreach ($ this ->eventDispatcher ->getListeners ($ eventName ) as $ listener ) {
117
+ $ listeners ["transition {$ transitionId }" ][$ eventName ][] = $ this ->summarizeListener ($ listener , $ eventName , $ transition );
118
+ }
119
+ }
120
+ }
121
+
122
+ return $ listeners ;
123
+ }
124
+
125
+ private function summarizeListener (callable $ callable , string $ eventName = null , Transition $ transition = null ): array
126
+ {
127
+ $ extra = [];
128
+
129
+ if ($ callable instanceof \Closure || \is_string ($ callable )) {
130
+ $ r = new \ReflectionFunction ($ callable );
131
+ $ title = (string ) $ r ;
132
+ } elseif (\is_object ($ callable ) && method_exists ($ callable , '__invoke ' )) {
133
+ $ r = new \ReflectionMethod ($ callable , '__invoke ' );
134
+ $ title = $ callable ::class.'::__invoke() ' ;
135
+ } elseif (\is_array ($ callable )) {
136
+ if ($ callable [0 ] instanceof GuardListener) {
137
+ if (null === $ eventName || null === $ transition ) {
138
+ throw new \LogicException ('Missing event name or transition. ' );
139
+ }
140
+ $ extra ['guardExpressions ' ] = $ this ->extractGuardExpressions ($ callable [0 ], $ eventName , $ transition );
141
+ }
142
+ $ r = new \ReflectionMethod ($ callable [0 ], $ callable [1 ]);
143
+ $ title = \get_class ($ callable [0 ]).':: ' .$ callable [1 ].'() ' ;
144
+ } else {
145
+ throw new \RuntimeException ('Unknown callable type. ' );
146
+ }
147
+
148
+ $ file = null ;
149
+ if ($ r ->isUserDefined ()) {
150
+ $ file = $ this ->fileLinkFormatter ?->format($ r ->getFileName (), $ r ->getStartLine ());
151
+ }
152
+
153
+ return [
154
+ 'title ' => $ title ,
155
+ 'file ' => $ file ,
156
+ ...$ extra ,
157
+ ];
158
+ }
159
+
160
+ private function extractGuardExpressions (GuardListener $ listener , string $ eventName , Transition $ transition ): array
161
+ {
162
+ $ configuration = (new \ReflectionProperty (GuardListener::class, 'configuration ' ))->getValue ($ listener );
163
+
164
+ $ expressions = [];
165
+ foreach ($ configuration [$ eventName ] as $ guard ) {
166
+ if ($ guard instanceof GuardExpression) {
167
+ if ($ guard ->getTransition () !== $ transition ) {
168
+ continue ;
169
+ }
170
+ $ expressions [] = $ guard ->getExpression ();
171
+ } else {
172
+ $ expressions [] = $ guard ;
173
+ }
174
+ }
175
+
176
+ return $ expressions ;
177
+ }
60
178
}
0 commit comments