|
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; |
|
11
|
|
|
|
|
12
|
|
|
use fab2s\NodalFlow\Callbacks\CallbackInterface; |
|
13
|
|
|
use fab2s\NodalFlow\Flows\FlowInterface; |
|
14
|
|
|
use fab2s\NodalFlow\Flows\FlowStatus; |
|
15
|
|
|
use fab2s\NodalFlow\Flows\FlowStatusInterface; |
|
16
|
|
|
use fab2s\NodalFlow\Nodes\AggregateNodeInterface; |
|
17
|
|
|
use fab2s\NodalFlow\Nodes\BranchNode; |
|
18
|
|
|
use fab2s\NodalFlow\Nodes\BranchNodeInterface; |
|
19
|
|
|
use fab2s\NodalFlow\Nodes\NodeInterface; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* Class NodalFlow |
|
23
|
|
|
*/ |
|
24
|
|
|
class NodalFlow implements FlowInterface |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* Flow steps triggering callbacks |
|
28
|
|
|
*/ |
|
29
|
|
|
const FLOW_START = 'start'; |
|
30
|
|
|
const FLOW_PROGRESS = 'progress'; |
|
31
|
|
|
const FLOW_SUCCESS = 'success'; |
|
32
|
|
|
const FLOW_FAIL = 'fail'; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* The parent Flow, only set when branched |
|
36
|
|
|
* |
|
37
|
|
|
* @var FlowInterface |
|
38
|
|
|
*/ |
|
39
|
|
|
public $parent; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* This Flow id |
|
43
|
|
|
* |
|
44
|
|
|
* @var string |
|
45
|
|
|
*/ |
|
46
|
|
|
protected $id; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* The underlying node structure |
|
50
|
|
|
* |
|
51
|
|
|
* @var NodeInterface[] |
|
52
|
|
|
*/ |
|
53
|
|
|
protected $nodes = []; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* The current Node index |
|
57
|
|
|
* |
|
58
|
|
|
* @var int |
|
59
|
|
|
*/ |
|
60
|
|
|
protected $nodeIdx = 0; |
|
61
|
|
|
|
|
62
|
|
|
/** |
|
63
|
|
|
* The last index value |
|
64
|
|
|
* |
|
65
|
|
|
* @var int |
|
66
|
|
|
*/ |
|
67
|
|
|
protected $lastIdx = 0; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* The number of Node in this Flow |
|
71
|
|
|
* |
|
72
|
|
|
* @var int |
|
73
|
|
|
*/ |
|
74
|
|
|
protected $nodeCount = 0; |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* The number of iteration within this Flow |
|
78
|
|
|
* |
|
79
|
|
|
* @var int |
|
80
|
|
|
*/ |
|
81
|
|
|
protected $numIterate = 0; |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* The number of break within this Flow |
|
85
|
|
|
* |
|
86
|
|
|
* @var int |
|
87
|
|
|
*/ |
|
88
|
|
|
protected $numBreak = 0; |
|
89
|
|
|
|
|
90
|
|
|
/** |
|
91
|
|
|
* The number of continue within this Flow |
|
92
|
|
|
* |
|
93
|
|
|
* @var int |
|
94
|
|
|
*/ |
|
95
|
|
|
protected $numContinue = 0; |
|
96
|
|
|
|
|
97
|
|
|
/** |
|
98
|
|
|
* The current registered Callback class if any |
|
99
|
|
|
* |
|
100
|
|
|
* @var CallbackInterface|null |
|
101
|
|
|
*/ |
|
102
|
|
|
protected $callBack; |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* Progress modulo to apply |
|
106
|
|
|
* Set to x if you want to trigger |
|
107
|
|
|
* progress every x iterations in flow |
|
108
|
|
|
* |
|
109
|
|
|
* @var int |
|
110
|
|
|
*/ |
|
111
|
|
|
protected $progressMod = 1024; |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* The default Node Map values |
|
115
|
|
|
* |
|
116
|
|
|
* @var array |
|
117
|
|
|
*/ |
|
118
|
|
|
protected $nodeMapDefault = [ |
|
119
|
|
|
'class' => null, |
|
120
|
|
|
'branchId' => null, |
|
121
|
|
|
'hash' => null, |
|
122
|
|
|
'index' => 0, |
|
123
|
|
|
'num_exec' => 0, |
|
124
|
|
|
'num_iterate' => 0, |
|
125
|
|
|
'num_break' => 0, |
|
126
|
|
|
'num_continue' => 0, |
|
127
|
|
|
]; |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* The default Node stats values |
|
131
|
|
|
* |
|
132
|
|
|
* @var array |
|
133
|
|
|
*/ |
|
134
|
|
|
protected $nodeStatsDefault = [ |
|
135
|
|
|
'num_exec' => 0, |
|
136
|
|
|
'num_iterate' => 0, |
|
137
|
|
|
'num_break' => 0, |
|
138
|
|
|
'num_continue' => 0, |
|
139
|
|
|
]; |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* Node stats values |
|
143
|
|
|
* |
|
144
|
|
|
* @var array |
|
145
|
|
|
*/ |
|
146
|
|
|
protected $nodeStats = []; |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* The object map, used to enforce object unicity within the Flow |
|
150
|
|
|
* |
|
151
|
|
|
* @var array |
|
152
|
|
|
*/ |
|
153
|
|
|
protected $objectMap = []; |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* The Node Map |
|
157
|
|
|
* |
|
158
|
|
|
* @var array |
|
159
|
|
|
*/ |
|
160
|
|
|
protected $nodeMap = []; |
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* The Flow stats default values |
|
164
|
|
|
* |
|
165
|
|
|
* @var array |
|
166
|
|
|
*/ |
|
167
|
|
|
protected $statsDefault = [ |
|
168
|
|
|
'start' => null, |
|
169
|
|
|
'end' => null, |
|
170
|
|
|
'duration' => null, |
|
171
|
|
|
'mib' => null, |
|
172
|
|
|
]; |
|
173
|
|
|
|
|
174
|
|
|
/** |
|
175
|
|
|
* The Flow Stats |
|
176
|
|
|
* |
|
177
|
|
|
* @var array |
|
178
|
|
|
*/ |
|
179
|
|
|
protected $stats = [ |
|
180
|
|
|
'invocations' => [], |
|
181
|
|
|
]; |
|
182
|
|
|
|
|
183
|
|
|
/** |
|
184
|
|
|
* Number of exec calls in this Flow |
|
185
|
|
|
* |
|
186
|
|
|
* @var int |
|
187
|
|
|
*/ |
|
188
|
|
|
protected $numExec = 0; |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* Continue flag |
|
192
|
|
|
* |
|
193
|
|
|
* @var bool |
|
194
|
|
|
*/ |
|
195
|
|
|
protected $continue = false; |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* Break Flag |
|
199
|
|
|
* |
|
200
|
|
|
* @var bool |
|
201
|
|
|
*/ |
|
202
|
|
|
protected $break = false; |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* Current Flow Status |
|
206
|
|
|
* |
|
207
|
|
|
* @var FlowStatusInterface |
|
208
|
|
|
*/ |
|
209
|
|
|
protected $flowStatus; |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* @var string |
|
213
|
|
|
*/ |
|
214
|
|
|
protected $interruptNodeId; |
|
215
|
|
|
|
|
216
|
|
|
/** |
|
217
|
|
|
* Current nonce |
|
218
|
|
|
* |
|
219
|
|
|
* @var int |
|
220
|
|
|
*/ |
|
221
|
|
|
private static $nonce = 0; |
|
222
|
|
|
|
|
223
|
|
|
/** |
|
224
|
|
|
* Instantiate a Flow |
|
225
|
|
|
*/ |
|
226
|
|
|
public function __construct() |
|
227
|
|
|
{ |
|
228
|
|
|
$this->id = $this->uniqId(); |
|
229
|
|
|
$this->stats += $this->statsDefault; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* Adds a Node to the flow |
|
234
|
|
|
* |
|
235
|
|
|
* @param NodeInterface $node |
|
236
|
|
|
* |
|
237
|
|
|
* @throws NodalFlowException |
|
238
|
|
|
* |
|
239
|
|
|
* @return $this |
|
240
|
|
|
*/ |
|
241
|
|
|
public function add(NodeInterface $node) |
|
242
|
|
|
{ |
|
243
|
|
|
$nodeHash = $node->getNodeHash(); |
|
244
|
|
|
|
|
245
|
|
|
if (isset($this->nodeMap[$nodeHash])) { |
|
246
|
|
|
throw new NodalFlowException('Cannot reuse Node instances within a Flow', 1, null, [ |
|
247
|
|
|
'duplicate_node' => get_class($node), |
|
248
|
|
|
'hash' => $nodeHash, |
|
249
|
|
|
]); |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
if ($node instanceof BranchNodeInterface) { |
|
253
|
|
|
// this node is a branch, set it's parent |
|
254
|
|
|
$node->getPayload()->setParent($this); |
|
255
|
|
|
} |
|
256
|
|
|
|
|
257
|
|
|
$node->setCarrier($this); |
|
258
|
|
|
|
|
259
|
|
|
$this->nodes[$this->nodeIdx] = $node; |
|
260
|
|
|
$this->nodeMap[$nodeHash] = \array_replace($this->nodeMapDefault, [ |
|
261
|
|
|
'class' => \get_class($node), |
|
262
|
|
|
'branchId' => $this->id, |
|
263
|
|
|
'hash' => $nodeHash, |
|
264
|
|
|
'index' => $this->nodeIdx, |
|
265
|
|
|
'isATraversable' => $node->isTraversable(), |
|
266
|
|
|
'isAReturningVal' => $node->isReturningVal(), |
|
267
|
|
|
'isAFlow' => $node->isFlow(), |
|
268
|
|
|
]); |
|
269
|
|
|
|
|
270
|
|
|
// register references to nodeStats to increment faster |
|
271
|
|
|
// nodeStats can also be used as reverse lookup table |
|
272
|
|
|
$this->nodeStats[$this->nodeIdx] = &$this->nodeMap[$nodeHash]; |
|
273
|
|
|
|
|
274
|
|
|
++$this->nodeIdx; |
|
275
|
|
|
|
|
276
|
|
|
return $this; |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
/** |
|
280
|
|
|
* Adds a Payload Node to the Flow |
|
281
|
|
|
* |
|
282
|
|
|
* @param callable $payload |
|
283
|
|
|
* @param mixed $isAReturningVal |
|
284
|
|
|
* @param mixed $isATraversable |
|
285
|
|
|
* |
|
286
|
|
|
* @return $this |
|
287
|
|
|
*/ |
|
288
|
|
|
public function addPayload(callable $payload, $isAReturningVal, $isATraversable = false) |
|
289
|
|
|
{ |
|
290
|
|
|
$node = PayloadNodeFactory::create($payload, $isAReturningVal, $isATraversable); |
|
291
|
|
|
|
|
292
|
|
|
$this->add($node); |
|
293
|
|
|
|
|
294
|
|
|
return $this; |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
/** |
|
298
|
|
|
* Register callback class |
|
299
|
|
|
* |
|
300
|
|
|
* @param CallbackInterface $callBack |
|
301
|
|
|
* |
|
302
|
|
|
* @return $this |
|
303
|
|
|
*/ |
|
304
|
|
|
public function setCallBack(CallbackInterface $callBack) |
|
305
|
|
|
{ |
|
306
|
|
|
$this->callBack = $callBack; |
|
307
|
|
|
|
|
308
|
|
|
return $this; |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
/** |
|
312
|
|
|
* Used to set the eventual Node Target of an Interrupt signal |
|
313
|
|
|
* set to : |
|
314
|
|
|
* - A node hash to target |
|
315
|
|
|
* - true to interrupt every upstream nodes |
|
316
|
|
|
* in this Flow |
|
317
|
|
|
* - false to only interrupt up to the first |
|
318
|
|
|
* upstream Traversable in this Flow |
|
319
|
|
|
* |
|
320
|
|
|
* @param string|bool $interruptNodeId |
|
321
|
|
|
* |
|
322
|
|
|
* @return $this |
|
323
|
|
|
*/ |
|
324
|
|
|
public function setInterruptNodeId($interruptNodeId) |
|
325
|
|
|
{ |
|
326
|
|
|
$this->interruptNodeId = $interruptNodeId; |
|
|
|
|
|
|
327
|
|
|
|
|
328
|
|
|
return $this; |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
/** |
|
332
|
|
|
* Set parent Flow, happens only when branched |
|
333
|
|
|
* |
|
334
|
|
|
* @param FlowInterface $flow |
|
335
|
|
|
* |
|
336
|
|
|
* @return $this |
|
337
|
|
|
*/ |
|
338
|
|
|
public function setParent(FlowInterface $flow) |
|
339
|
|
|
{ |
|
340
|
|
|
$this->parent = $flow; |
|
341
|
|
|
|
|
342
|
|
|
return $this; |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
/** |
|
346
|
|
|
* Get eventual parent Flow |
|
347
|
|
|
* |
|
348
|
|
|
* @return FlowInterface |
|
349
|
|
|
*/ |
|
350
|
|
|
public function getParent() |
|
351
|
|
|
{ |
|
352
|
|
|
return $this->parent; |
|
353
|
|
|
} |
|
354
|
|
|
|
|
355
|
|
|
/** |
|
356
|
|
|
* Tells if this flow has a parent |
|
357
|
|
|
* |
|
358
|
|
|
* @return bool |
|
359
|
|
|
*/ |
|
360
|
|
|
public function hasParent() |
|
361
|
|
|
{ |
|
362
|
|
|
return !empty($this->parent); |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
/** |
|
366
|
|
|
* Generates a truly unique id for the Flow context |
|
367
|
|
|
* |
|
368
|
|
|
* @return string |
|
369
|
|
|
*/ |
|
370
|
|
|
public function uniqId() |
|
371
|
|
|
{ |
|
372
|
|
|
// while we're at it, drop any doubt about |
|
373
|
|
|
// colliding from here |
|
374
|
|
|
return \sha1(uniqid() . $this->getNonce()); |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* Execute the flow |
|
379
|
|
|
* |
|
380
|
|
|
* @param null|mixed $param The eventual init argument to the first node |
|
381
|
|
|
* or, in case of a branch, the last relevant |
|
382
|
|
|
* argument from upstream Flow |
|
383
|
|
|
* |
|
384
|
|
|
* @throws NodalFlowException |
|
385
|
|
|
* |
|
386
|
|
|
* @return mixed the last result of the |
|
387
|
|
|
* last returning value node |
|
388
|
|
|
*/ |
|
389
|
|
|
public function exec($param = null) |
|
390
|
|
|
{ |
|
391
|
|
|
try { |
|
392
|
|
|
$result = $this->rewind() |
|
393
|
|
|
->flowStart() |
|
394
|
|
|
->recurse($param); |
|
395
|
|
|
// set flowStatus to make sure that we have the proper |
|
396
|
|
|
// value in flowEnd even when overridden without (or when |
|
397
|
|
|
// improperly) calling parent |
|
398
|
|
|
if ($this->flowStatus->isRunning()) { |
|
399
|
|
|
$this->flowStatus = new FlowStatus(FlowStatus::FLOW_CLEAN); |
|
400
|
|
|
} |
|
401
|
|
|
|
|
402
|
|
|
$this->flowEnd(); |
|
403
|
|
|
|
|
404
|
|
|
return $result; |
|
405
|
|
|
} catch (\Exception $e) { |
|
406
|
|
|
$this->flowStatus = new FlowStatus(FlowStatus::FLOW_EXCEPTION); |
|
407
|
|
|
$this->flowEnd(); |
|
408
|
|
|
if ($e instanceof NodalFlowException) { |
|
409
|
|
|
throw $e; |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
throw new NodalFlowException('Flow execution failed', 0, $e, [ |
|
413
|
|
|
'nodeMap' => $this->getNodeMap(), |
|
414
|
|
|
]); |
|
415
|
|
|
} |
|
416
|
|
|
} |
|
417
|
|
|
|
|
418
|
|
|
/** |
|
419
|
|
|
* Computes a human readable duration string from floating seconds |
|
420
|
|
|
* |
|
421
|
|
|
* @param float $seconds |
|
422
|
|
|
* |
|
423
|
|
|
* @return array |
|
|
|
|
|
|
424
|
|
|
*/ |
|
425
|
|
|
public function duration($seconds) |
|
426
|
|
|
{ |
|
427
|
|
|
$result = [ |
|
428
|
|
|
'hour' => (int) \floor($seconds / 3600), |
|
429
|
|
|
'min' => (int) \floor(($seconds / 60) % 60), |
|
430
|
|
|
'sec' => $seconds % 60, |
|
431
|
|
|
'ms' => (int) \round(\fmod($seconds, 1) * 1000), |
|
432
|
|
|
]; |
|
433
|
|
|
|
|
434
|
|
|
$durationStr = ''; |
|
435
|
|
|
foreach ($result as $unit => $value) { |
|
436
|
|
|
if (!empty($value)) { |
|
437
|
|
|
$durationStr .= $value . "$unit "; |
|
438
|
|
|
} |
|
439
|
|
|
} |
|
440
|
|
|
|
|
441
|
|
|
$result['durationStr'] = \trim($durationStr); |
|
442
|
|
|
|
|
443
|
|
|
return $result; |
|
444
|
|
|
} |
|
445
|
|
|
|
|
446
|
|
|
/** |
|
447
|
|
|
* Resets Nodes stats, can be used prior to Flow's re-exec |
|
448
|
|
|
* |
|
449
|
|
|
* @return $this |
|
450
|
|
|
*/ |
|
451
|
|
|
public function resetNodeStats() |
|
452
|
|
|
{ |
|
453
|
|
|
foreach ($this->nodeStats as &$nodeStat) { |
|
454
|
|
|
$nodeStat = \array_replace($nodeStat, $this->nodeStatsDefault); |
|
455
|
|
|
} |
|
456
|
|
|
|
|
457
|
|
|
return $this; |
|
458
|
|
|
} |
|
459
|
|
|
|
|
460
|
|
|
/** |
|
461
|
|
|
* Get the stats array with latest Node stats |
|
462
|
|
|
* |
|
463
|
|
|
* @return array |
|
464
|
|
|
*/ |
|
465
|
|
|
public function getStats() |
|
466
|
|
|
{ |
|
467
|
|
|
foreach ($this->nodes as $node) { |
|
468
|
|
|
if (\is_a($node, BranchNode::class)) { |
|
469
|
|
|
$this->stats['branches'][$node->getPayload()->getFlowId()] = $node->getPayload()->getStats(); |
|
|
|
|
|
|
470
|
|
|
} |
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
return $this->stats; |
|
474
|
|
|
} |
|
475
|
|
|
|
|
476
|
|
|
/** |
|
477
|
|
|
* Return the Flow id as set during instantiation |
|
478
|
|
|
* |
|
479
|
|
|
* @return string |
|
480
|
|
|
*/ |
|
481
|
|
|
public function getId() |
|
482
|
|
|
{ |
|
483
|
|
|
return $this->id; |
|
484
|
|
|
} |
|
485
|
|
|
|
|
486
|
|
|
/** |
|
487
|
|
|
* getId() alias for backward compatibility |
|
488
|
|
|
* |
|
489
|
|
|
* @deprecated |
|
490
|
|
|
* |
|
491
|
|
|
* @return string |
|
492
|
|
|
*/ |
|
493
|
|
|
public function getFlowId() |
|
494
|
|
|
{ |
|
495
|
|
|
return $this->getId(); |
|
496
|
|
|
} |
|
497
|
|
|
|
|
498
|
|
|
/** |
|
499
|
|
|
* Get the Node array |
|
500
|
|
|
* |
|
501
|
|
|
* @return NodeInterface[] |
|
502
|
|
|
*/ |
|
503
|
|
|
public function getNodes() |
|
504
|
|
|
{ |
|
505
|
|
|
return $this->nodes; |
|
506
|
|
|
} |
|
507
|
|
|
|
|
508
|
|
|
/** |
|
509
|
|
|
* Generate Node Map |
|
510
|
|
|
* |
|
511
|
|
|
* @return array |
|
512
|
|
|
*/ |
|
513
|
|
|
public function getNodeMap() |
|
514
|
|
|
{ |
|
515
|
|
|
foreach ($this->nodes as $node) { |
|
516
|
|
|
if (\is_a($node, BranchNode::class)) { |
|
517
|
|
|
$this->nodeMap[$node->getNodeHash()]['nodes'] = $node->getPayload()->getNodeMap(); |
|
|
|
|
|
|
518
|
|
|
continue; |
|
519
|
|
|
} |
|
520
|
|
|
|
|
521
|
|
|
if ($node instanceof AggregateNodeInterface) { |
|
522
|
|
|
foreach ($node->getNodeCollection() as $aggregatedNode) { |
|
523
|
|
|
$this->nodeMap[$node->getNodeHash()]['nodes'][$aggregatedNode->getNodeHash()] = [ |
|
524
|
|
|
'class' => \get_class($aggregatedNode), |
|
525
|
|
|
'hash' => $aggregatedNode->getNodeHash(), |
|
526
|
|
|
]; |
|
527
|
|
|
} |
|
528
|
|
|
continue; |
|
529
|
|
|
} |
|
530
|
|
|
} |
|
531
|
|
|
|
|
532
|
|
|
return $this->nodeMap; |
|
533
|
|
|
} |
|
534
|
|
|
|
|
535
|
|
|
/** |
|
536
|
|
|
* Get the Node stats |
|
537
|
|
|
* |
|
538
|
|
|
* @return array |
|
539
|
|
|
*/ |
|
540
|
|
|
public function getNodeStats() |
|
541
|
|
|
{ |
|
542
|
|
|
foreach ($this->nodes as $nodeIdx => $node) { |
|
543
|
|
|
if ($node instanceof BranchNodeInterface) { |
|
544
|
|
|
$payload = $node->getPayload(); |
|
545
|
|
|
// The plan would be to extract the stat logic from here rather |
|
546
|
|
|
// than adding method to the interface |
|
547
|
|
|
$this->nodeStats[$nodeIdx]['nodes'] = is_callable([$payload, 'getNodeStats']) ? $payload->getNodeStats() : 'N/A'; |
|
|
|
|
|
|
548
|
|
|
} |
|
549
|
|
|
} |
|
550
|
|
|
|
|
551
|
|
|
return $this->nodeStats; |
|
552
|
|
|
} |
|
553
|
|
|
|
|
554
|
|
|
/** |
|
555
|
|
|
* Rewinds the Flow |
|
556
|
|
|
* |
|
557
|
|
|
* @return $this |
|
558
|
|
|
*/ |
|
559
|
|
|
public function rewind() |
|
560
|
|
|
{ |
|
561
|
|
|
$this->nodeCount = count($this->nodes); |
|
562
|
|
|
$this->lastIdx = $this->nodeCount - 1; |
|
563
|
|
|
$this->nodeIdx = 0; |
|
564
|
|
|
$this->break = false; |
|
565
|
|
|
$this->continue = false; |
|
566
|
|
|
$this->interruptNodeId = null; |
|
567
|
|
|
|
|
568
|
|
|
return $this; |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
/** |
|
572
|
|
|
* Define the progress modulo, Progress Callback will be |
|
573
|
|
|
* triggered upon each iteration in the flow modulo $progressMod |
|
574
|
|
|
* |
|
575
|
|
|
* @param int $progressMod |
|
576
|
|
|
* |
|
577
|
|
|
* @return $this |
|
578
|
|
|
*/ |
|
579
|
|
|
public function setProgressMod($progressMod) |
|
580
|
|
|
{ |
|
581
|
|
|
$this->progressMod = max(1, (int) $progressMod); |
|
582
|
|
|
|
|
583
|
|
|
return $this; |
|
584
|
|
|
} |
|
585
|
|
|
|
|
586
|
|
|
/** |
|
587
|
|
|
* Get current $progressMod |
|
588
|
|
|
* |
|
589
|
|
|
* @return int |
|
590
|
|
|
*/ |
|
591
|
|
|
public function getProgressMod() |
|
592
|
|
|
{ |
|
593
|
|
|
return $this->progressMod; |
|
594
|
|
|
} |
|
595
|
|
|
|
|
596
|
|
|
/** |
|
597
|
|
|
* The Flow status can either indicate be: |
|
598
|
|
|
* - clean (isClean()): everything went well |
|
599
|
|
|
* - dirty (isDirty()): one Node broke the flow |
|
600
|
|
|
* - exception (isException()): an exception was raised during the flow |
|
601
|
|
|
* |
|
602
|
|
|
* @return FlowStatusInterface |
|
603
|
|
|
*/ |
|
604
|
|
|
public function getFlowStatus() |
|
605
|
|
|
{ |
|
606
|
|
|
return $this->flowStatus; |
|
607
|
|
|
} |
|
608
|
|
|
|
|
609
|
|
|
/** |
|
610
|
|
|
* Break the flow's execution, conceptually similar to breaking |
|
611
|
|
|
* a regular loop |
|
612
|
|
|
* |
|
613
|
|
|
* @param InterrupterInterface|null $flowInterrupt |
|
614
|
|
|
* |
|
615
|
|
|
* @return $this |
|
616
|
|
|
*/ |
|
617
|
|
|
public function breakFlow(InterrupterInterface $flowInterrupt = null) |
|
618
|
|
|
{ |
|
619
|
|
|
return $this->interruptFlow(InterrupterInterface::TYPE_BREAK, $flowInterrupt); |
|
620
|
|
|
} |
|
621
|
|
|
|
|
622
|
|
|
/** |
|
623
|
|
|
* Continue the flow's execution, conceptually similar to continuing |
|
624
|
|
|
* a regular loop |
|
625
|
|
|
* |
|
626
|
|
|
* @param InterrupterInterface|null $flowInterrupt |
|
627
|
|
|
* |
|
628
|
|
|
* @return $this |
|
629
|
|
|
*/ |
|
630
|
|
|
public function continueFlow(InterrupterInterface $flowInterrupt = null) |
|
631
|
|
|
{ |
|
632
|
|
|
return $this->interruptFlow(InterrupterInterface::TYPE_CONTINUE, $flowInterrupt); |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
/** |
|
636
|
|
|
* @param string $interruptType |
|
637
|
|
|
* @param InterrupterInterface|null $flowInterrupt |
|
638
|
|
|
* |
|
639
|
|
|
* @throws NodalFlowException |
|
640
|
|
|
* |
|
641
|
|
|
* @return $this |
|
642
|
|
|
*/ |
|
643
|
|
|
public function interruptFlow($interruptType, InterrupterInterface $flowInterrupt = null) |
|
644
|
|
|
{ |
|
645
|
|
|
switch ($interruptType) { |
|
646
|
|
|
case InterrupterInterface::TYPE_CONTINUE: |
|
647
|
|
|
$this->continue = true; |
|
648
|
|
|
++$this->numContinue; |
|
649
|
|
|
break; |
|
650
|
|
|
case InterrupterInterface::TYPE_BREAK: |
|
651
|
|
|
$this->flowStatus = new FlowStatus(FlowStatus::FLOW_DIRTY); |
|
652
|
|
|
$this->break = true; |
|
653
|
|
|
++$this->numBreak; |
|
654
|
|
|
break; |
|
655
|
|
|
default: |
|
656
|
|
|
throw new NodalFlowException('FlowInterrupt Type missing'); |
|
657
|
|
|
} |
|
658
|
|
|
|
|
659
|
|
|
if ($flowInterrupt) { |
|
660
|
|
|
$flowInterrupt->setType($interruptType)->propagate($this); |
|
661
|
|
|
} |
|
662
|
|
|
|
|
663
|
|
|
return $this; |
|
664
|
|
|
} |
|
665
|
|
|
|
|
666
|
|
|
/** |
|
667
|
|
|
* @param NodeInterface|null $node |
|
668
|
|
|
* |
|
669
|
|
|
* @return bool |
|
670
|
|
|
*/ |
|
671
|
|
|
protected function interruptNode(NodeInterface $node = null) |
|
672
|
|
|
{ |
|
673
|
|
|
// if we have an interruptNodeId, bubble up until we match a node |
|
674
|
|
|
// else stop propagation |
|
675
|
|
|
return $this->interruptNodeId ? $this->interruptNodeId !== $node->getNodeHash() : false; |
|
|
|
|
|
|
676
|
|
|
} |
|
677
|
|
|
|
|
678
|
|
|
/** |
|
679
|
|
|
* Triggered just before the flow starts |
|
680
|
|
|
* |
|
681
|
|
|
* @return $this |
|
682
|
|
|
*/ |
|
683
|
|
|
protected function flowStart() |
|
684
|
|
|
{ |
|
685
|
|
|
++$this->numExec; |
|
686
|
|
|
$this->stats['start'] = \microtime(true); |
|
687
|
|
|
$this->triggerCallback(static::FLOW_START); |
|
688
|
|
|
|
|
689
|
|
|
if (!$this->hasParent()) { |
|
690
|
|
|
$this->stats['invocations'][$this->numExec]['start'] = $this->stats['start']; |
|
691
|
|
|
} |
|
692
|
|
|
|
|
693
|
|
|
// flow is started |
|
694
|
|
|
$this->flowStatus = new FlowStatus(FlowStatus::FLOW_RUNNING); |
|
695
|
|
|
|
|
696
|
|
|
return $this; |
|
697
|
|
|
} |
|
698
|
|
|
|
|
699
|
|
|
/** |
|
700
|
|
|
* Triggered right after the flow stops |
|
701
|
|
|
* |
|
702
|
|
|
* @return $this |
|
703
|
|
|
*/ |
|
704
|
|
|
protected function flowEnd() |
|
705
|
|
|
{ |
|
706
|
|
|
$this->stats['end'] = \microtime(true); |
|
707
|
|
|
$this->stats['mib'] = \memory_get_peak_usage(true) / 1048576; |
|
708
|
|
|
$this->stats['duration'] = $this->stats['end'] - $this->stats['start']; |
|
709
|
|
|
$this->stats['num_break'] = $this->numBreak; |
|
710
|
|
|
$this->stats['num_continue'] = $this->numContinue; |
|
711
|
|
|
|
|
712
|
|
|
if (!$this->hasParent()) { |
|
713
|
|
|
$this->stats['invocations'][$this->numExec]['end'] = $this->stats['end']; |
|
714
|
|
|
$this->stats['invocations'][$this->numExec]['duration'] = $this->stats['duration']; |
|
715
|
|
|
$this->stats['invocations'][$this->numExec]['mib'] = $this->stats['mib']; |
|
716
|
|
|
} |
|
717
|
|
|
|
|
718
|
|
|
$this->triggerCallback($this->flowStatus->isException() ? static::FLOW_FAIL : static::FLOW_SUCCESS); |
|
719
|
|
|
|
|
720
|
|
|
return $this; |
|
721
|
|
|
} |
|
722
|
|
|
|
|
723
|
|
|
/** |
|
724
|
|
|
* Return a simple nonce, fully valid within any flow |
|
725
|
|
|
* |
|
726
|
|
|
* @return int |
|
727
|
|
|
*/ |
|
728
|
|
|
protected function getNonce() |
|
729
|
|
|
{ |
|
730
|
|
|
return self::$nonce++; |
|
731
|
|
|
} |
|
732
|
|
|
|
|
733
|
|
|
/** |
|
734
|
|
|
* Recurse over nodes which may as well be Flows and |
|
735
|
|
|
* Traversable ... |
|
736
|
|
|
* Welcome to the abysses of recursion or iter-recursion ^^ |
|
737
|
|
|
* |
|
738
|
|
|
* `recurse` perform kind of an hybrid recursion as the |
|
739
|
|
|
* Flow is effectively iterating and recurring over its |
|
740
|
|
|
* Nodes, which may as well be seen as over itself |
|
741
|
|
|
* |
|
742
|
|
|
* Iterating tends to limit the amount of recursion levels: |
|
743
|
|
|
* recursion is only triggered when executing a Traversable |
|
744
|
|
|
* Node's downstream Nodes while every consecutive exec |
|
745
|
|
|
* Nodes are executed within a while loop. |
|
746
|
|
|
* And recursion keeps the size of the recursion context |
|
747
|
|
|
* to a minimum as pretty much everything is done by the |
|
748
|
|
|
* iterating instance |
|
749
|
|
|
* |
|
750
|
|
|
* @param mixed $param |
|
751
|
|
|
* @param int $nodeIdx |
|
752
|
|
|
* |
|
753
|
|
|
* @return mixed the last value returned by the last |
|
754
|
|
|
* returning value Node in the flow |
|
755
|
|
|
*/ |
|
756
|
|
|
protected function recurse($param = null, $nodeIdx = 0) |
|
757
|
|
|
{ |
|
758
|
|
|
while ($nodeIdx <= $this->lastIdx) { |
|
759
|
|
|
$node = $this->nodes[$nodeIdx]; |
|
760
|
|
|
$nodeStat = &$this->nodeStats[$nodeIdx]; |
|
761
|
|
|
$returnVal = $node->isReturningVal(); |
|
762
|
|
|
|
|
763
|
|
|
if ($node->isTraversable()) { |
|
764
|
|
|
foreach ($node->getTraversable($param) as $value) { |
|
|
|
|
|
|
765
|
|
|
if ($returnVal) { |
|
766
|
|
|
// pass current $value as next param |
|
767
|
|
|
$param = $value; |
|
768
|
|
|
} |
|
769
|
|
|
|
|
770
|
|
|
++$nodeStat['num_iterate']; |
|
771
|
|
|
++$this->numIterate; |
|
772
|
|
|
if (!($this->numIterate % $this->progressMod)) { |
|
773
|
|
|
$this->triggerCallback(static::FLOW_PROGRESS, $node); |
|
774
|
|
|
} |
|
775
|
|
|
|
|
776
|
|
|
$param = $this->recurse($param, $nodeIdx + 1); |
|
777
|
|
|
if ($this->continue) { |
|
778
|
|
|
if ($this->continue = $this->interruptNode($node)) { |
|
779
|
|
|
// since we want to bubble the continue upstream |
|
780
|
|
|
// we break here waiting for next $param if any |
|
781
|
|
|
++$nodeStat['num_break']; |
|
782
|
|
|
break; |
|
783
|
|
|
} |
|
784
|
|
|
|
|
785
|
|
|
// we drop one iteration |
|
786
|
|
|
++$nodeStat['num_continue']; |
|
787
|
|
|
continue; |
|
788
|
|
|
} |
|
789
|
|
|
|
|
790
|
|
|
if ($this->break) { |
|
791
|
|
|
// we drop all subsequent iterations |
|
792
|
|
|
++$nodeStat['num_break']; |
|
793
|
|
|
$this->break = $this->interruptNode($node); |
|
794
|
|
|
break; |
|
795
|
|
|
} |
|
796
|
|
|
} |
|
797
|
|
|
|
|
798
|
|
|
// we reached the end of this Traversable and executed all its downstream Nodes |
|
799
|
|
|
++$nodeStat['num_exec']; |
|
800
|
|
|
|
|
801
|
|
|
return $param; |
|
802
|
|
|
} |
|
803
|
|
|
|
|
804
|
|
|
$value = $node->exec($param); |
|
|
|
|
|
|
805
|
|
|
++$nodeStat['num_exec']; |
|
806
|
|
|
|
|
807
|
|
|
if ($this->continue) { |
|
808
|
|
|
++$nodeStat['num_continue']; |
|
809
|
|
|
// a continue does not need to bubble up unless |
|
810
|
|
|
// it specifically targets a node in this flow |
|
811
|
|
|
// or targets an upstream flow |
|
812
|
|
|
$this->continue = $this->interruptNode($node); |
|
813
|
|
|
|
|
814
|
|
|
return $param; |
|
815
|
|
|
} |
|
816
|
|
|
|
|
817
|
|
|
if ($this->break) { |
|
818
|
|
|
++$nodeStat['num_break']; |
|
819
|
|
|
// a break always need to bubble up to the first upstream Traversable if any |
|
820
|
|
|
return $param; |
|
821
|
|
|
} |
|
822
|
|
|
|
|
823
|
|
|
if ($returnVal) { |
|
824
|
|
|
// pass current $value as next param |
|
825
|
|
|
$param = $value; |
|
826
|
|
|
} |
|
827
|
|
|
|
|
828
|
|
|
++$nodeIdx; |
|
829
|
|
|
} |
|
830
|
|
|
|
|
831
|
|
|
// we reached the end of this recursion |
|
832
|
|
|
return $param; |
|
833
|
|
|
} |
|
834
|
|
|
|
|
835
|
|
|
/** |
|
836
|
|
|
* KISS helper to trigger Callback slots |
|
837
|
|
|
* |
|
838
|
|
|
* @param string $which |
|
839
|
|
|
* @param null|NodeInterface $node |
|
840
|
|
|
* |
|
841
|
|
|
* @return $this |
|
842
|
|
|
*/ |
|
843
|
|
|
protected function triggerCallback($which, NodeInterface $node = null) |
|
844
|
|
|
{ |
|
845
|
|
|
if (null !== $this->callBack) { |
|
846
|
|
|
$this->callBack->$which($this, $node); |
|
847
|
|
|
} |
|
848
|
|
|
|
|
849
|
|
|
return $this; |
|
850
|
|
|
} |
|
851
|
|
|
} |
|
852
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountIdthat can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theidproperty of an instance of theAccountclass. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.