Completed
Push — master ( 2efb17...d4d028 )
by Fabrice
02:43
created

FlowMap::setNodeIncrement()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 1
dl 0
loc 21
rs 9.0534
c 0
b 0
f 0
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
    public function __wakeup()
136
    {
137
        $this->registry->load($this->flow, $this->registryData);
138
        $this->setRefs();
139
    }
140
141
    /**
142
     * @param NodeInterface $node
143
     * @param int           $index
144
     * @param bool          $replace
145
     *
146
     * @throws NodalFlowException
147
     *
148
     * @return $this
149
     */
150
    public function register(NodeInterface $node, $index, $replace = false)
151
    {
152
        if (!$replace) {
153
            $this->registry->registerNode($node);
154
        } else {
155
            $this->registry->removeNode($this->reverseMap[$index]);
156
        }
157
158
        $nodeId                 = $node->getId();
159
        $this->nodeMap[$nodeId] = array_replace($this->nodeMapDefault, [
160
            'class'           => get_class($node),
161
            'flowId'          => $this->flowId,
162
            'hash'            => $nodeId,
163
            'index'           => $index,
164
            'isATraversable'  => $node->isTraversable(),
165
            'isAReturningVal' => $node->isReturningVal(),
166
            'isAFlow'         => $node->isFlow(),
167
        ], $this->nodeIncrements);
168
169
        $this->setNodeIncrement($node);
170
171
        if (isset($this->reverseMap[$index])) {
172
            // replacing a node, maintain nodeMap accordingly
173
            unset($this->nodeMap[$this->reverseMap[$index]->getId()], $this->reverseMap[$index]);
174
        }
175
176
        $this->reverseMap[$index] = $node;
177
178
        return $this;
179
    }
180
181
    /**
182
     * @param string $nodeId
183
     *
184
     * @return int|null
185
     */
186
    public function getNodeIndex($nodeId)
187
    {
188
        return isset($this->nodeMap[$nodeId]) ? $this->nodeMap[$nodeId]['index'] : null;
189
    }
190
191
    /**
192
     * Triggered right before the flow starts
193
     *
194
     * @return $this
195
     */
196
    public function flowStart()
197
    {
198
        $this->flowStats['start'] = microtime(true);
199
200
        return $this;
201
    }
202
203
    /**
204
     * Triggered right after the flow stops
205
     *
206
     * @return $this
207
     */
208
    public function flowEnd()
209
    {
210
        $this->flowStats['end']     = microtime(true);
211
        $this->flowStats['mib']     = memory_get_peak_usage(true) / 1048576;
212
        $this->flowStats['elapsed'] = $this->flowStats['end'] - $this->flowStats['start'];
213
214
        $this->flowStats = array_replace($this->flowStats, $this->duration($this->flowStats['elapsed']));
215
216
        return $this;
217
    }
218
219
    /**
220
     * Let's be fast at incrementing while we are at it
221
     *
222
     * @param string $nodeHash
223
     *
224
     * @return array
225
     */
226
    public function &getNodeStat($nodeHash)
227
    {
228
        return $this->nodeMap[$nodeHash];
229
    }
230
231
    /**
232
     * Get/Generate Node Map
233
     *
234
     * @return array
235
     */
236
    public function getNodeMap()
237
    {
238
        foreach ($this->flow->getNodes() as $node) {
239
            $nodeId = $node->getId();
240
            if ($node instanceof BranchNodeInterface || $node instanceof AggregateNodeInterface) {
241
                $this->nodeMap[$nodeId]['nodes'] = $node->getPayload()->getNodeMap();
0 ignored issues
show
Bug introduced by
The method getPayload() does not exist on fab2s\NodalFlow\Nodes\NodeInterface. It seems like you code against a sub-type of fab2s\NodalFlow\Nodes\NodeInterface such as fab2s\NodalFlow\Nodes\PayloadNodeInterface or fab2s\NodalFlow\Nodes\BranchNodeInterface or fab2s\NodalFlow\Nodes\CallableNode or fab2s\NodalFlow\Nodes\CallableNode or fab2s\NodalFlow\Nodes\AggregateNodeInterface or fab2s\NodalFlow\Nodes\PayloadNodeAbstract. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

241
                $this->nodeMap[$nodeId]['nodes'] = $node->/** @scrutinizer ignore-call */ getPayload()->getNodeMap();
Loading history...
242
                continue;
243
            }
244
        }
245
246
        return $this->nodeMap;
247
    }
248
249
    /**
250
     * Get the latest Node stats
251
     *
252
     * @return array<string,integer|string>
253
     */
254
    public function getStats()
255
    {
256
        $this->resetTotals();
257
        foreach ($this->flow->getNodes() as $node) {
258
            $nodeMap = $this->nodeMap[$node->getId()];
259
            foreach ($this->incrementTotals as $srcKey => $totalKey) {
260
                if (isset($nodeMap[$srcKey])) {
261
                    $this->flowStats[$totalKey] += $nodeMap[$srcKey];
262
                }
263
            }
264
265
            if ($node instanceof BranchNodeInterface) {
266
                $childFlowId                               = $node->getPayload()->getId();
267
                $this->flowStats['branches'][$childFlowId] = $node->getPayload()->getStats();
268
                foreach ($this->incrementTotals as $srcKey => $totalKey) {
269
                    if (isset($this->flowStats['branches'][$childFlowId][$totalKey])) {
270
                        $this->flowStats[$totalKey] += $this->flowStats['branches'][$childFlowId][$totalKey];
271
                    }
272
                }
273
            }
274
        }
275
276
        $flowStatus = $this->flow->getFlowStatus();
277
        if ($flowStatus !== null) {
278
            $this->flowStats['flow_status'] = $flowStatus->getStatus();
279
        }
280
281
        return $this->flowStats;
282
    }
283
284
    /**
285
     * @param string $nodeHash
286
     * @param string $key
287
     *
288
     * @return $this
289
     */
290
    public function incrementNode($nodeHash, $key)
291
    {
292
        ++$this->nodeMap[$nodeHash][$key];
293
294
        return $this;
295
    }
296
297
    /**
298
     * @param string $key
299
     *
300
     * @return $this
301
     */
302
    public function incrementFlow($key)
303
    {
304
        ++$this->flowStats[$key];
305
306
        return $this;
307
    }
308
309
    /**
310
     * Resets Nodes stats, can be used prior to Flow's re-exec
311
     *
312
     * @return $this
313
     */
314
    public function resetNodeStats()
315
    {
316
        foreach ($this->nodeMap as &$nodeStat) {
317
            foreach ($this->nodeIncrements as $key => $value) {
318
                if (isset($nodeStat[$key])) {
319
                    $nodeStat[$key] = $value;
320
                }
321
            }
322
        }
323
324
        return $this;
325
    }
326
327
    /**
328
     * Computes a human readable duration string from floating seconds
329
     *
330
     * @param float $seconds
331
     *
332
     * @return array<string,integer|string>
333
     */
334
    public function duration($seconds)
335
    {
336
        $result = [
337
            'hour'     => (int) floor($seconds / 3600),
338
            'min'      => (int) floor(($seconds / 60) % 60),
339
            'sec'      => $seconds % 60,
340
            'ms'       => (int) round(\fmod($seconds, 1) * 1000),
341
        ];
342
343
        $duration = '';
344
        foreach ($result as $unit => $value) {
345
            if (!empty($value) || $unit === 'ms') {
346
                $duration .= $value . "$unit ";
347
            }
348
        }
349
350
        $result['duration'] = trim($duration);
351
352
        return $result;
353
    }
354
355
    /**
356
     * @return $this
357
     */
358
    protected function setRefs()
359
    {
360
        $this->registryData              = &$this->registry->get($this->flowId);
361
        $this->registryData['flowStats'] = &$this->flowStats;
362
        $this->registryData['nodeStats'] = &$this->nodeMap;
363
        $this->registryData['nodes']     = &$this->reverseMap;
364
365
        return $this;
366
    }
367
368
    /**
369
     * @return $this
370
     */
371
    protected function initDefaults()
372
    {
373
        $this->flowIncrements = $this->nodeIncrements;
374
        foreach (array_keys($this->flowIncrements) as $key) {
375
            $totalKey                        = $key . '_total';
376
            $this->incrementTotals[$key]     = $totalKey;
377
            $this->flowIncrements[$totalKey] = 0;
378
        }
379
380
        $this->flowMapDefault = array_replace($this->flowMapDefault, $this->flowIncrements, [
381
            'class' => get_class($this->flow),
382
            'id'    => $this->flowId,
383
        ]);
384
385
        $this->flowStats = $this->flowMapDefault;
386
387
        return $this;
388
    }
389
390
    /**
391
     * @return $this
392
     */
393
    protected function resetTotals()
394
    {
395
        foreach ($this->incrementTotals as $totalKey) {
396
            $this->flowStats[$totalKey] = 0;
397
        }
398
399
        return $this;
400
    }
401
402
    /**
403
     * Set additional increment keys, use :
404
     *      'keyName' => int
405
     * to add keyName as increment, starting at int
406
     * or :
407
     *      'keyName' => 'existingIncrement'
408
     * to assign keyName as a reference to existingIncrement
409
     *
410
     * @param array $flowIncrements
411
     *
412
     * @throws NodalFlowException
413
     *
414
     * @return $this
415
     */
416
    protected function setFlowIncrement(array $flowIncrements)
417
    {
418
        foreach ($flowIncrements as $incrementKey => $target) {
419
            if (is_string($target)) {
420
                if (!isset($this->flowStats[$target])) {
421
                    throw new NodalFlowException('Cannot set reference on unset target');
422
                }
423
424
                $this->flowStats[$incrementKey]            = &$this->flowStats[$target];
425
                $this->flowStats[$incrementKey . '_total'] = &$this->flowStats[$target . '_total'];
426
                continue;
427
            }
428
429
            $this->flowIncrements[$incrementKey]  = $target;
430
            $this->incrementTotals[$incrementKey] = $incrementKey . '_total';
431
            $this->flowStats[$incrementKey]       = $target;
432
        }
433
434
        return $this;
435
    }
436
437
    /**
438
     * @param NodeInterface $node
439
     *
440
     * @throws NodalFlowException
441
     *
442
     * @return $this
443
     */
444
    protected function setNodeIncrement(NodeInterface $node)
445
    {
446
        $nodeId = $node->getId();
447
        foreach ($node->getNodeIncrements() as $incrementKey => $target) {
448
            if (is_string($target)) {
449
                if (!isset($this->nodeIncrements[$target])) {
450
                    throw new NodalFlowException('Tried to set an increment alias to an un-registered increment', 1, null, [
451
                        'aliasKey'  => $incrementKey,
452
                        'targetKey' => $target,
453
                    ]);
454
                }
455
456
                $this->nodeMap[$nodeId][$incrementKey] = &$this->nodeMap[$nodeId][$target];
457
                continue;
458
            }
459
460
            $this->nodeIncrements[$incrementKey]   = $target;
461
            $this->nodeMap[$nodeId][$incrementKey] = $target;
462
        }
463
464
        return $this;
465
    }
466
}
467