|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of NodalFlow. |
|
5
|
|
|
* (c) Fabrice de Stefanis / https://github.com/fab2s/NodalFlow |
|
6
|
|
|
* This source file is licensed under the MIT license which you will |
|
7
|
|
|
* find in the LICENSE file or at https://opensource.org/licenses/MIT |
|
8
|
|
|
*/ |
|
9
|
|
|
|
|
10
|
|
|
namespace fab2s\NodalFlow\Flows; |
|
11
|
|
|
|
|
12
|
|
|
use fab2s\NodalFlow\NodalFlowException; |
|
13
|
|
|
use fab2s\NodalFlow\Nodes\AggregateNodeInterface; |
|
14
|
|
|
use fab2s\NodalFlow\Nodes\BranchNodeInterface; |
|
15
|
|
|
use fab2s\NodalFlow\Nodes\NodeInterface; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* class FlowMap |
|
19
|
|
|
*/ |
|
20
|
|
|
class FlowMap implements FlowMapInterface |
|
21
|
|
|
{ |
|
22
|
|
|
/** |
|
23
|
|
|
* Flow map |
|
24
|
|
|
* |
|
25
|
|
|
* @var array |
|
26
|
|
|
*/ |
|
27
|
|
|
protected $nodeMap = []; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* @var array |
|
31
|
|
|
*/ |
|
32
|
|
|
protected $reverseMap = []; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* The default Node Map values |
|
36
|
|
|
* |
|
37
|
|
|
* @var array |
|
38
|
|
|
*/ |
|
39
|
|
|
protected $nodeMapDefault = [ |
|
40
|
|
|
'class' => null, |
|
41
|
|
|
'flowId' => null, |
|
42
|
|
|
'hash' => null, |
|
43
|
|
|
'index' => null, |
|
44
|
|
|
'isATraversable' => null, |
|
45
|
|
|
'isAReturningVal' => null, |
|
46
|
|
|
'isAFlow' => null, |
|
47
|
|
|
'num_exec' => 0, |
|
48
|
|
|
'num_iterate' => 0, |
|
49
|
|
|
'num_break' => 0, |
|
50
|
|
|
'num_continue' => 0, |
|
51
|
|
|
]; |
|
52
|
|
|
|
|
53
|
|
|
/** |
|
54
|
|
|
* The default Node stats values |
|
55
|
|
|
* |
|
56
|
|
|
* @var array |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $incrementStatsDefault = [ |
|
59
|
|
|
'num_exec' => 0, |
|
60
|
|
|
'num_iterate' => 0, |
|
61
|
|
|
'num_break' => 0, |
|
62
|
|
|
'num_continue' => 0, |
|
63
|
|
|
]; |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* The Flow stats default values |
|
67
|
|
|
* |
|
68
|
|
|
* @var array |
|
69
|
|
|
*/ |
|
70
|
|
|
protected $flowStatsDefault = [ |
|
71
|
|
|
'class' => null, |
|
72
|
|
|
'id' => null, |
|
73
|
|
|
'start' => null, |
|
74
|
|
|
'end' => null, |
|
75
|
|
|
'elapsed' => null, |
|
76
|
|
|
'duration' => null, |
|
77
|
|
|
'mib' => null, |
|
78
|
|
|
]; |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* @var array |
|
82
|
|
|
*/ |
|
83
|
|
|
protected $defaultFlowStats; |
|
84
|
|
|
|
|
85
|
|
|
/** |
|
86
|
|
|
* @var array |
|
87
|
|
|
*/ |
|
88
|
|
|
protected $flowStats; |
|
89
|
|
|
|
|
90
|
|
|
/** |
|
91
|
|
|
* @var bool |
|
92
|
|
|
*/ |
|
93
|
|
|
protected $resetOnRestart = false; |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* @var FlowInterface |
|
97
|
|
|
*/ |
|
98
|
|
|
protected $flow; |
|
99
|
|
|
|
|
100
|
|
|
/** |
|
101
|
|
|
* @var bool |
|
102
|
|
|
*/ |
|
103
|
|
|
protected $hasRun = false; |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* Instantiate a Flow Status |
|
107
|
|
|
* |
|
108
|
|
|
* @param FlowInterface $flow |
|
109
|
|
|
* |
|
110
|
|
|
* @internal param string $status The flow status |
|
111
|
|
|
*/ |
|
112
|
|
|
public function __construct(FlowInterface $flow) |
|
113
|
|
|
{ |
|
114
|
|
|
$this->flow = $flow; |
|
115
|
|
|
$this->defaultFlowStats = array_replace($this->flowStatsDefault, $this->incrementStatsDefault, [ |
|
116
|
|
|
'class' => get_class($this->flow), |
|
117
|
|
|
'id' => $this->flow->getId(), |
|
118
|
|
|
]); |
|
119
|
|
|
$this->flowStats = $this->defaultFlowStats; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* @param array $flowIncrements |
|
124
|
|
|
* |
|
125
|
|
|
* @throws NodalFlowException |
|
126
|
|
|
* |
|
127
|
|
|
* @return $this |
|
128
|
|
|
*/ |
|
129
|
|
|
public function setFlowIncrement(array $flowIncrements) |
|
130
|
|
|
{ |
|
131
|
|
|
if ($this->hasRun) { |
|
132
|
|
|
throw new NodalFlowException('Cannot set custom increment after Flow started'); |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
$this->defaultFlowStats = array_replace($flowIncrements, $this->defaultFlowStats); |
|
136
|
|
|
$this->flowStats = $this->defaultFlowStats; |
|
137
|
|
|
|
|
138
|
|
|
return $this; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* @param NodeInterface $node |
|
143
|
|
|
* @param int $index |
|
144
|
|
|
* |
|
145
|
|
|
* @throws NodalFlowException |
|
146
|
|
|
*/ |
|
147
|
|
|
public function register(NodeInterface $node, $index) |
|
148
|
|
|
{ |
|
149
|
|
|
$this->enforceUniqueness($node); |
|
150
|
|
|
$nodeHash = $node->getNodeHash(); |
|
151
|
|
|
$this->nodeMap[$nodeHash] = array_replace($this->nodeMapDefault, [ |
|
152
|
|
|
'class' => get_class($node), |
|
153
|
|
|
'flowId' => $node->getCarrier()->getId(), |
|
154
|
|
|
'hash' => $nodeHash, |
|
155
|
|
|
'index' => $index, |
|
156
|
|
|
'isATraversable' => $node->isTraversable(), |
|
157
|
|
|
'isAReturningVal' => $node->isReturningVal(), |
|
158
|
|
|
'isAFlow' => $node->isFlow(), |
|
159
|
|
|
]); |
|
160
|
|
|
|
|
161
|
|
|
if (isset($this->reverseMap[$index])) { |
|
162
|
|
|
// replacing a note, maintain nodeMap accordingly |
|
163
|
|
|
unset($this->nodeMap[$this->reverseMap[$index]]); |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
$this->reverseMap[$index] = $nodeHash; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* Triggered right before the flow starts |
|
171
|
|
|
* |
|
172
|
|
|
* @return $this |
|
173
|
|
|
*/ |
|
174
|
|
|
public function flowStart() |
|
175
|
|
|
{ |
|
176
|
|
|
$this->flowStats['start'] = microtime(true); |
|
177
|
|
|
$this->hasRun = true; |
|
178
|
|
|
|
|
179
|
|
|
return $this; |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
/** |
|
183
|
|
|
* Triggered right after the flow stops |
|
184
|
|
|
* |
|
185
|
|
|
* @return $this |
|
186
|
|
|
*/ |
|
187
|
|
|
public function flowEnd() |
|
188
|
|
|
{ |
|
189
|
|
|
$this->flowStats['end'] = microtime(true); |
|
190
|
|
|
$this->flowStats['mib'] = memory_get_peak_usage(true) / 1048576; |
|
191
|
|
|
$this->flowStats['elapsed'] = $this->flowStats['end'] - $this->flowStats['start']; |
|
192
|
|
|
|
|
193
|
|
|
$this->flowStats = array_replace($this->flowStats, $this->duration($this->flowStats['elapsed'])); |
|
194
|
|
|
//dd($this->duration($this->flowStats['elapsed']),$this->flowStats); |
|
|
|
|
|
|
195
|
|
|
|
|
196
|
|
|
return $this; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
/** |
|
200
|
|
|
* Get/Generate Node Map |
|
201
|
|
|
* |
|
202
|
|
|
* @return array |
|
203
|
|
|
*/ |
|
204
|
|
|
public function getNodeMap() |
|
205
|
|
|
{ |
|
206
|
|
|
foreach ($this->flow->getNodes() as $node) { |
|
207
|
|
|
if ($node instanceof BranchNodeInterface) { |
|
208
|
|
|
$this->nodeMap[$node->getNodeHash()]['nodes'] = $node->getPayload()->getNodeMap(); |
|
209
|
|
|
continue; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
if ($node instanceof AggregateNodeInterface) { |
|
213
|
|
|
$flowId = $node->getCarrier()->getId(); |
|
214
|
|
|
foreach ($node->getNodeCollection() as $aggregatedNode) { |
|
215
|
|
|
$this->nodeMap[$node->getNodeHash()]['nodes'][$aggregatedNode->getNodeHash()] = array_replace($this->nodeMapDefault, [ |
|
216
|
|
|
'class' => get_class($aggregatedNode), |
|
217
|
|
|
'flowId' => $flowId, |
|
218
|
|
|
'hash' => $aggregatedNode->getNodeHash(), |
|
219
|
|
|
]); |
|
220
|
|
|
} |
|
221
|
|
|
continue; |
|
222
|
|
|
} |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
return $this->nodeMap; |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* Get the latest Node stats |
|
230
|
|
|
* |
|
231
|
|
|
* @return array |
|
232
|
|
|
*/ |
|
233
|
|
|
public function getStats() |
|
234
|
|
|
{ |
|
235
|
|
|
$result = array_intersect_key($this->flowStats, $this->defaultFlowStats); |
|
236
|
|
|
foreach ($this->flow->getNodes() as $node) { |
|
237
|
|
|
if ($node instanceof BranchNodeInterface) { |
|
238
|
|
|
$result['branches'][$node->getPayload()->getId()] = $node->getPayload()->getStats(); |
|
239
|
|
|
} |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
return $result; |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
/** |
|
246
|
|
|
* @param string $nodeHash |
|
247
|
|
|
* @param array $incrementAliases |
|
248
|
|
|
* |
|
249
|
|
|
* @throws NodalFlowException |
|
250
|
|
|
* |
|
251
|
|
|
* @return $this |
|
252
|
|
|
*/ |
|
253
|
|
|
public function setIncrementAlias($nodeHash, array $incrementAliases = []) |
|
254
|
|
|
{ |
|
255
|
|
|
foreach ($incrementAliases as $aliasKey => $incKey) { |
|
256
|
|
|
if (!isset($this->incrementStatsDefault[$incKey])) { |
|
257
|
|
|
throw new NodalFlowException('Tried to set an increment alias to an un-registered increment', 1, null, [ |
|
258
|
|
|
'aliasKey' => $aliasKey, |
|
259
|
|
|
'incrementKey' => $incKey, |
|
260
|
|
|
]); |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
$this->nodeMap[$nodeHash][$aliasKey] = &$this->nodeMap[$nodeHash][$incKey]; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
return $this; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
/** |
|
270
|
|
|
* @param string $nodeHash |
|
271
|
|
|
* @param string $key |
|
272
|
|
|
* |
|
273
|
|
|
* @return $this |
|
274
|
|
|
*/ |
|
275
|
|
|
public function increment($nodeHash, $key) |
|
276
|
|
|
{ |
|
277
|
|
|
++$this->nodeMap[$nodeHash][$key]; |
|
278
|
|
|
|
|
279
|
|
|
return $this; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
/** |
|
283
|
|
|
* @param string $key |
|
284
|
|
|
* |
|
285
|
|
|
* @return $this |
|
286
|
|
|
*/ |
|
287
|
|
|
public function incrementFlow($key) |
|
288
|
|
|
{ |
|
289
|
|
|
++$this->flowStats[$key]; |
|
290
|
|
|
|
|
291
|
|
|
return $this; |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
/** |
|
295
|
|
|
* Resets Nodes stats, can be used prior to Flow's re-exec |
|
296
|
|
|
* |
|
297
|
|
|
* @return $this |
|
298
|
|
|
*/ |
|
299
|
|
|
public function resetNodeStats() |
|
300
|
|
|
{ |
|
301
|
|
|
foreach ($this->nodeMap as &$nodeStat) { |
|
302
|
|
|
$nodeStat = array_replace($nodeStat, $this->incrementStatsDefault); |
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
return $this; |
|
306
|
|
|
} |
|
307
|
|
|
|
|
308
|
|
|
/** |
|
309
|
|
|
* Computes a human readable duration string from floating seconds |
|
310
|
|
|
* |
|
311
|
|
|
* @param float $seconds |
|
312
|
|
|
* |
|
313
|
|
|
* @return array<string,integer|string> |
|
314
|
|
|
*/ |
|
315
|
|
|
public function duration($seconds) |
|
316
|
|
|
{ |
|
317
|
|
|
$result = [ |
|
318
|
|
|
'hour' => (int) floor($seconds / 3600), |
|
319
|
|
|
'min' => (int) floor(($seconds / 60) % 60), |
|
320
|
|
|
'sec' => $seconds % 60, |
|
321
|
|
|
'ms' => (int) round(\fmod($seconds, 1) * 1000), |
|
322
|
|
|
]; |
|
323
|
|
|
|
|
324
|
|
|
$duration = ''; |
|
325
|
|
|
foreach ($result as $unit => $value) { |
|
326
|
|
|
if (!empty($value) || $unit === 'ms') { |
|
327
|
|
|
$duration .= $value . "$unit "; |
|
328
|
|
|
} |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
$result['duration'] = trim($duration); |
|
332
|
|
|
|
|
333
|
|
|
return $result; |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
/** |
|
337
|
|
|
* @param NodeInterface $node |
|
338
|
|
|
* |
|
339
|
|
|
* @throws NodalFlowException |
|
340
|
|
|
* |
|
341
|
|
|
* @return $this |
|
342
|
|
|
*/ |
|
343
|
|
|
protected function enforceUniqueness(NodeInterface $node) |
|
344
|
|
|
{ |
|
345
|
|
|
if (isset($this->nodeMap[$node->getNodeHash()])) { |
|
346
|
|
|
throw new NodalFlowException('Cannot reuse Node instances within a Flow', 1, null, [ |
|
347
|
|
|
'duplicate_node' => get_class($node), |
|
348
|
|
|
'hash' => $node->getNodeHash(), |
|
349
|
|
|
]); |
|
350
|
|
|
} |
|
351
|
|
|
|
|
352
|
|
|
return $this; |
|
353
|
|
|
} |
|
354
|
|
|
} |
|
355
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.