FlowMap::incrementNode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 5
rs 10
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
 * Do not implement Serializable interface on purpose
20
 *
21
 * @SEE https://externals.io/message/98834#98834
22
 */
23
class FlowMap implements FlowMapInterface
24
{
25
    /**
26
     * Flow map
27
     *
28
     * @var array
29
     */
30
    protected $nodeMap = [];
31
32
    /**
33
     * @var NodeInterface[]
34
     */
35
    protected $reverseMap = [];
36
37
    /**
38
     * The default Node Map values
39
     *
40
     * @var array
41
     */
42
    protected $nodeMapDefault = [
43
        'class'           => null,
44
        'flowId'          => null,
45
        'hash'            => null,
46
        'index'           => null,
47
        'isATraversable'  => null,
48
        'isAReturningVal' => null,
49
        'isAFlow'         => null,
50
    ];
51
52
    /**
53
     * The default Node stats values
54
     *
55
     * @var array
56
     */
57
    protected $nodeIncrements = [
58
        'num_exec'     => 0,
59
        'num_iterate'  => 0,
60
        'num_break'    => 0,
61
        'num_continue' => 0,
62
    ];
63
64
    /**
65
     * The Flow map default values
66
     *
67
     * @var array
68
     */
69
    protected $flowMapDefault = [
70
        'class'    => null,
71
        'id'       => null,
72
        'start'    => null,
73
        'end'      => null,
74
        'elapsed'  => null,
75
        'duration' => null,
76
        'mib'      => null,
77
    ];
78
79
    /**
80
     * @var array
81
     */
82
    protected $incrementTotals = [];
83
84
    /**
85
     * @var array
86
     */
87
    protected $flowIncrements;
88
89
    /**
90
     * @var array
91
     */
92
    protected $flowStats;
93
94
    /**
95
     * @var FlowRegistryInterface
96
     */
97
    protected $registry;
98
99
    /**
100
     * @var array
101
     */
102
    protected $registryData = [];
103
104
    /**
105
     * @var FlowInterface
106
     */
107
    protected $flow;
108
109
    /**
110
     * @var string
111
     */
112
    protected $flowId;
113
114
    /**
115
     * Instantiate a Flow Status
116
     *
117
     * @param FlowInterface $flow
118
     * @param array         $flowIncrements
119
     *
120
     * @throws NodalFlowException
121
     */
122
    public function __construct(FlowInterface $flow, array $flowIncrements = [])
123
    {
124
        $this->flow     = $flow;
125
        $this->flowId   = $this->flow->getId();
126
        $this->registry = (new FlowRegistry)->registerFlow($flow);
127
        $this->initDefaults()->setRefs()->setFlowIncrement($flowIncrements);
128
    }
129
130
    /**
131
     * If you don't feel like doing this at home, I completely
132
     * understand, I'd be very happy to hear about a better and
133
     * more efficient way
134
     *
135
     * @throws NodalFlowException
136
     */
137
    public function __wakeup()
138
    {
139
        $this->registry->load($this->flow, $this->registryData);
140
        $this->setRefs();
141
    }
142
143
    /**
144
     * @param NodeInterface $node
145
     * @param int           $index
146
     * @param bool          $replace
147
     *
148
     * @throws NodalFlowException
149
     *
150
     * @return $this
151
     */
152
    public function register(NodeInterface $node, int $index, bool $replace = false): FlowMapInterface
153
    {
154
        if (!$replace) {
155
            $this->registry->registerNode($node);
156
        } else {
157
            $this->registry->removeNode($this->reverseMap[$index]);
158
        }
159
160
        $nodeId                 = $node->getId();
161
        $this->nodeMap[$nodeId] = array_replace($this->nodeMapDefault, [
162
            'class'           => get_class($node),
163
            'flowId'          => $this->flowId,
164
            'hash'            => $nodeId,
165
            'index'           => $index,
166
            'isATraversable'  => $node->isTraversable(),
167
            'isAReturningVal' => $node->isReturningVal(),
168
            'isAFlow'         => $node->isFlow(),
169
        ], $this->nodeIncrements);
170
171
        $this->setNodeIncrement($node);
172
173
        if (isset($this->reverseMap[$index])) {
174
            // replacing a node, maintain nodeMap accordingly
175
            unset($this->nodeMap[$this->reverseMap[$index]->getId()], $this->reverseMap[$index]);
176
        }
177
178
        $this->reverseMap[$index] = $node;
179
180
        return $this;
181
    }
182
183
    /**
184
     * @param string $nodeId
185
     *
186
     * @return int|null
187
     */
188
    public function getNodeIndex(string $nodeId): ? int
189
    {
190
        return isset($this->nodeMap[$nodeId]) ? $this->nodeMap[$nodeId]['index'] : null;
191
    }
192
193
    /**
194
     * Triggered right before the flow starts
195
     *
196
     * @return $this
197
     */
198
    public function flowStart(): FlowMapInterface
199
    {
200
        $this->flowStats['start'] = microtime(true);
201
202
        return $this;
203
    }
204
205
    /**
206
     * Triggered right after the flow stops
207
     *
208
     * @return $this
209
     */
210
    public function flowEnd(): FlowMapInterface
211
    {
212
        $this->flowStats['end']     = microtime(true);
213
        $this->flowStats['mib']     = memory_get_peak_usage(true) / 1048576;
214
        $this->flowStats['elapsed'] = $this->flowStats['end'] - $this->flowStats['start'];
215
216
        $this->flowStats = array_replace($this->flowStats, $this->duration($this->flowStats['elapsed']));
217
218
        return $this;
219
    }
220
221
    /**
222
     * Let's be fast at incrementing while we are at it
223
     *
224
     * @param string $nodeId
225
     *
226
     * @return array
227
     */
228
    public function &getNodeStat(string $nodeId): array
229
    {
230
        return $this->nodeMap[$nodeId];
231
    }
232
233
    /**
234
     * Get/Generate Node Map
235
     *
236
     * @return array
237
     */
238
    public function getNodeMap(): array
239
    {
240
        foreach ($this->flow->getNodes() as $node) {
241
            $nodeId = $node->getId();
242
            if ($node instanceof BranchNodeInterface || $node instanceof AggregateNodeInterface) {
243
                $this->nodeMap[$nodeId]['nodes'] = $node->getPayload()->getNodeMap();
244
            }
245
        }
246
247
        return $this->nodeMap;
248
    }
249
250
    /**
251
     * Get the latest Node stats
252
     *
253
     * @return array<string,integer|string>
254
     */
255
    public function getStats(): array
256
    {
257
        $this->resetTotals();
258
        foreach ($this->flow->getNodes() as $node) {
259
            $nodeMap = $this->nodeMap[$node->getId()];
260
            foreach ($this->incrementTotals as $srcKey => $totalKey) {
261
                if (isset($nodeMap[$srcKey])) {
262
                    $this->flowStats[$totalKey] += $nodeMap[$srcKey];
263
                }
264
            }
265
266
            if ($node instanceof BranchNodeInterface) {
267
                $childFlowId                               = $node->getPayload()->getId();
268
                $this->flowStats['branches'][$childFlowId] = $node->getPayload()->getStats();
269
                foreach ($this->incrementTotals as $srcKey => $totalKey) {
270
                    if (isset($this->flowStats['branches'][$childFlowId][$totalKey])) {
271
                        $this->flowStats[$totalKey] += $this->flowStats['branches'][$childFlowId][$totalKey];
272
                    }
273
                }
274
            }
275
        }
276
277
        $flowStatus = $this->flow->getFlowStatus();
278
        if ($flowStatus !== null) {
279
            $this->flowStats['flow_status'] = $flowStatus->getStatus();
280
        }
281
282
        return $this->flowStats;
283
    }
284
285
    /**
286
     * @param string $nodeId
287
     * @param string $key
288
     *
289
     * @return $this
290
     */
291
    public function incrementNode(string $nodeId, string $key): FlowMapInterface
292
    {
293
        ++$this->nodeMap[$nodeId][$key];
294
295
        return $this;
296
    }
297
298
    /**
299
     * @param string $key
300
     *
301
     * @return $this
302
     */
303
    public function incrementFlow(string $key): FlowMapInterface
304
    {
305
        ++$this->flowStats[$key];
306
307
        return $this;
308
    }
309
310
    /**
311
     * Resets Nodes stats, can be used prior to Flow's re-exec
312
     *
313
     * @return $this
314
     */
315
    public function resetNodeStats(): FlowMapInterface
316
    {
317
        foreach ($this->nodeMap as &$nodeStat) {
318
            foreach ($this->nodeIncrements as $key => $value) {
319
                if (isset($nodeStat[$key])) {
320
                    $nodeStat[$key] = $value;
321
                }
322
            }
323
        }
324
325
        return $this;
326
    }
327
328
    /**
329
     * Computes a human readable duration string from floating seconds
330
     *
331
     * @param float $seconds
332
     *
333
     * @return array<string,integer|string>
334
     */
335
    public function duration(float $seconds): array
336
    {
337
        $result = [
338
            'hour'     => (int) floor($seconds / 3600),
339
            'min'      => (int) floor(\fmod($seconds / 60, 60)),
340
            'sec'      => (int) \fmod($seconds, 60),
341
            'ms'       => (int) round(\fmod($seconds, 1) * 1000),
342
        ];
343
344
        $duration = '';
345
        foreach ($result as $unit => $value) {
346
            if (!empty($value) || $unit === 'ms') {
347
                $duration .= $value . "$unit ";
348
            }
349
        }
350
351
        $result['duration'] = trim($duration);
352
353
        return $result;
354
    }
355
356
    /**
357
     * @return $this
358
     */
359
    protected function setRefs(): self
360
    {
361
        $this->registryData              = &$this->registry->get($this->flowId);
362
        $this->registryData['flowStats'] = &$this->flowStats;
363
        $this->registryData['nodeStats'] = &$this->nodeMap;
364
        $this->registryData['nodes']     = &$this->reverseMap;
365
366
        return $this;
367
    }
368
369
    /**
370
     * @return $this
371
     */
372
    protected function initDefaults(): self
373
    {
374
        $this->flowIncrements = $this->nodeIncrements;
375
        foreach (array_keys($this->flowIncrements) as $key) {
376
            $totalKey                        = $key . '_total';
377
            $this->incrementTotals[$key]     = $totalKey;
378
            $this->flowIncrements[$totalKey] = 0;
379
        }
380
381
        $this->flowMapDefault = array_replace($this->flowMapDefault, $this->flowIncrements, [
382
            'class' => get_class($this->flow),
383
            'id'    => $this->flowId,
384
        ]);
385
386
        $this->flowStats = $this->flowMapDefault;
387
388
        return $this;
389
    }
390
391
    /**
392
     * @return $this
393
     */
394
    protected function resetTotals(): self
395
    {
396
        foreach ($this->incrementTotals as $totalKey) {
397
            $this->flowStats[$totalKey] = 0;
398
        }
399
400
        return $this;
401
    }
402
403
    /**
404
     * Set additional increment keys, use :
405
     *      'keyName' => int
406
     * to add keyName as increment, starting at int
407
     * or :
408
     *      'keyName' => 'existingIncrement'
409
     * to assign keyName as a reference to existingIncrement
410
     *
411
     * @param array $flowIncrements
412
     *
413
     * @throws NodalFlowException
414
     *
415
     * @return $this
416
     */
417
    protected function setFlowIncrement(array $flowIncrements): self
418
    {
419
        foreach ($flowIncrements as $incrementKey => $target) {
420
            if (is_string($target)) {
421
                if (!isset($this->flowStats[$target])) {
422
                    throw new NodalFlowException('Cannot set reference on unset target');
423
                }
424
425
                $this->flowStats[$incrementKey]            = &$this->flowStats[$target];
426
                $this->flowStats[$incrementKey . '_total'] = &$this->flowStats[$target . '_total'];
427
                continue;
428
            }
429
430
            $this->flowIncrements[$incrementKey]  = $target;
431
            $this->incrementTotals[$incrementKey] = $incrementKey . '_total';
432
            $this->flowStats[$incrementKey]       = $target;
433
        }
434
435
        return $this;
436
    }
437
438
    /**
439
     * @param NodeInterface $node
440
     *
441
     * @throws NodalFlowException
442
     *
443
     * @return $this
444
     */
445
    protected function setNodeIncrement(NodeInterface $node): self
446
    {
447
        $nodeId = $node->getId();
448
        foreach ($node->getNodeIncrements() as $incrementKey => $target) {
449
            if (is_string($target)) {
450
                if (!isset($this->nodeIncrements[$target])) {
451
                    throw new NodalFlowException('Tried to set an increment alias to an un-registered increment', 1, null, [
452
                        'aliasKey'  => $incrementKey,
453
                        'targetKey' => $target,
454
                    ]);
455
                }
456
457
                $this->nodeMap[$nodeId][$incrementKey] = &$this->nodeMap[$nodeId][$target];
458
                continue;
459
            }
460
461
            $this->nodeIncrements[$incrementKey]   = $target;
462
            $this->nodeMap[$nodeId][$incrementKey] = $target;
463
        }
464
465
        return $this;
466
    }
467
}
468