Passed
Push — master ( 82eb11...0228a6 )
by Fabrice
02:32
created

FlowMap::getNodeMap()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 3
nop 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
     *
145
     * @throws NodalFlowException
146
     *
147
     * @return $this
148
     */
149
    public function register(NodeInterface $node, $index)
150
    {
151
        $this->registry->registerNode($node);
152
        $nodeId                 = $node->getId();
153
        $this->nodeMap[$nodeId] = array_replace($this->nodeMapDefault, [
154
            'class'           => get_class($node),
155
            'flowId'          => $this->flowId,
156
            'hash'            => $nodeId,
157
            'index'           => $index,
158
            'isATraversable'  => $node->isTraversable(),
159
            'isAReturningVal' => $node->isReturningVal(),
160
            'isAFlow'         => $node->isFlow(),
161
        ], $this->nodeIncrements);
162
163
        $this->setNodeIncrement($node);
164
165
        if (isset($this->reverseMap[$index])) {
166
            // replacing a node, maintain nodeMap accordingly
167
            unset($this->nodeMap[$this->reverseMap[$index]->getId()]);
168
        }
169
170
        $this->reverseMap[$index] = $node;
171
172
        return $this;
173
    }
174
175
    /**
176
     * @param string $nodeId
177
     *
178
     * @return int|null
179
     */
180
    public function getNodeIndex($nodeId)
181
    {
182
        return isset($this->nodeMap[$nodeId]) ? $this->nodeMap[$nodeId]['index'] : null;
183
    }
184
185
    /**
186
     * Triggered right before the flow starts
187
     *
188
     * @return $this
189
     */
190
    public function flowStart()
191
    {
192
        $this->flowStats['start'] = microtime(true);
193
194
        return $this;
195
    }
196
197
    /**
198
     * Triggered right after the flow stops
199
     *
200
     * @return $this
201
     */
202
    public function flowEnd()
203
    {
204
        $this->flowStats['end']     = microtime(true);
205
        $this->flowStats['mib']     = memory_get_peak_usage(true) / 1048576;
206
        $this->flowStats['elapsed'] = $this->flowStats['end'] - $this->flowStats['start'];
207
208
        $this->flowStats = array_replace($this->flowStats, $this->duration($this->flowStats['elapsed']));
209
210
        return $this;
211
    }
212
213
    /**
214
     * Let's be fast at incrementing while we are at it
215
     *
216
     * @param string $nodeHash
217
     *
218
     * @return array
219
     */
220
    public function &getNodeStat($nodeHash)
221
    {
222
        return $this->nodeMap[$nodeHash];
223
    }
224
225
    /**
226
     * Get/Generate Node Map
227
     *
228
     * @return array
229
     */
230
    public function getNodeMap()
231
    {
232
        foreach ($this->flow->getNodes() as $node) {
233
            $nodeId = $node->getId();
234
            if ($node instanceof BranchNodeInterface || $node instanceof AggregateNodeInterface) {
235
                $this->nodeMap[$nodeId]['nodes'] = $node->getPayload()->getNodeMap();
236
                continue;
237
            }
238
        }
239
240
        return $this->nodeMap;
241
    }
242
243
    /**
244
     * Get the latest Node stats
245
     *
246
     * @return array<string,integer|string>
247
     */
248
    public function getStats()
249
    {
250
        $this->resetTotals();
251
        foreach ($this->flow->getNodes() as $node) {
252
            $nodeMap = $this->nodeMap[$node->getId()];
253
            foreach ($this->incrementTotals as $srcKey => $totalKey) {
254
                if (isset($nodeMap[$srcKey])) {
255
                    $this->flowStats[$totalKey] += $nodeMap[$srcKey];
256
                }
257
            }
258
259
            if ($node instanceof BranchNodeInterface) {
260
                $childFlowId                               = $node->getPayload()->getId();
261
                $this->flowStats['branches'][$childFlowId] = $node->getPayload()->getStats();
262
                foreach ($this->incrementTotals as $srcKey => $totalKey) {
263
                    if (isset($this->flowStats['branches'][$childFlowId][$totalKey])) {
264
                        $this->flowStats[$totalKey] += $this->flowStats['branches'][$childFlowId][$totalKey];
265
                    }
266
                }
267
            }
268
        }
269
270
        $flowStatus = $this->flow->getFlowStatus();
271
        if ($flowStatus !== null) {
272
            $this->flowStats['flow_status'] = $flowStatus->getStatus();
273
        }
274
275
        return $this->flowStats;
276
    }
277
278
    /**
279
     * @param string $nodeHash
280
     * @param string $key
281
     *
282
     * @return $this
283
     */
284
    public function incrementNode($nodeHash, $key)
285
    {
286
        ++$this->nodeMap[$nodeHash][$key];
287
288
        return $this;
289
    }
290
291
    /**
292
     * @param string $key
293
     *
294
     * @return $this
295
     */
296
    public function incrementFlow($key)
297
    {
298
        ++$this->flowStats[$key];
299
300
        return $this;
301
    }
302
303
    /**
304
     * Resets Nodes stats, can be used prior to Flow's re-exec
305
     *
306
     * @return $this
307
     */
308
    public function resetNodeStats()
309
    {
310
        foreach ($this->nodeMap as &$nodeStat) {
311
            foreach ($this->nodeIncrements as $key => $value) {
312
                if (isset($nodeStat[$key])) {
313
                    $nodeStat[$key] = $value;
314
                }
315
            }
316
        }
317
318
        return $this;
319
    }
320
321
    /**
322
     * Computes a human readable duration string from floating seconds
323
     *
324
     * @param float $seconds
325
     *
326
     * @return array<string,integer|string>
327
     */
328
    public function duration($seconds)
329
    {
330
        $result = [
331
            'hour'     => (int) floor($seconds / 3600),
332
            'min'      => (int) floor(($seconds / 60) % 60),
333
            'sec'      => $seconds % 60,
334
            'ms'       => (int) round(\fmod($seconds, 1) * 1000),
335
        ];
336
337
        $duration = '';
338
        foreach ($result as $unit => $value) {
339
            if (!empty($value) || $unit === 'ms') {
340
                $duration .= $value . "$unit ";
341
            }
342
        }
343
344
        $result['duration'] = trim($duration);
345
346
        return $result;
347
    }
348
349
    /**
350
     * @return $this
351
     */
352
    protected function setRefs()
353
    {
354
        $this->registryData              = &$this->registry->get($this->flowId);
355
        $this->registryData['flowStats'] = &$this->flowStats;
356
        $this->registryData['nodeStats'] = &$this->nodeMap;
357
        $this->registryData['nodes']     = &$this->reverseMap;
358
359
        return $this;
360
    }
361
362
    /**
363
     * @return $this
364
     */
365
    protected function initDefaults()
366
    {
367
        $this->flowIncrements = $this->nodeIncrements;
368
        foreach (array_keys($this->flowIncrements) as $key) {
369
            $totalKey                        = $key . '_total';
370
            $this->incrementTotals[$key]     = $totalKey;
371
            $this->flowIncrements[$totalKey] = 0;
372
        }
373
374
        $this->flowMapDefault = array_replace($this->flowMapDefault, $this->flowIncrements, [
375
            'class' => get_class($this->flow),
376
            'id'    => $this->flowId,
377
        ]);
378
379
        $this->flowStats = $this->flowMapDefault;
380
381
        return $this;
382
    }
383
384
    /**
385
     * @return $this
386
     */
387
    protected function resetTotals()
388
    {
389
        foreach ($this->incrementTotals as $totalKey) {
390
            $this->flowStats[$totalKey] = 0;
391
        }
392
393
        return $this;
394
    }
395
396
    /**
397
     * Set additional increment keys, use :
398
     *      'keyName' => int
399
     * to add keyName as increment, starting at int
400
     * or :
401
     *      'keyName' => 'existingIncrement'
402
     * to assign keyName as a reference to existingIncrement
403
     *
404
     * @param array $flowIncrements
405
     *
406
     * @throws NodalFlowException
407
     *
408
     * @return $this
409
     */
410
    protected function setFlowIncrement(array $flowIncrements)
411
    {
412
        foreach ($flowIncrements as $incrementKey => $target) {
413
            if (is_string($target)) {
414
                if (!isset($this->flowStats[$target])) {
415
                    throw new NodalFlowException('Cannot set reference on unset target');
416
                }
417
418
                $this->flowStats[$incrementKey]            = &$this->flowStats[$target];
419
                $this->flowStats[$incrementKey . '_total'] = &$this->flowStats[$target . '_total'];
420
                continue;
421
            }
422
423
            $this->flowIncrements[$incrementKey]  = $target;
424
            $this->incrementTotals[$incrementKey] = $incrementKey . '_total';
425
            $this->flowStats[$incrementKey]       = $target;
426
        }
427
428
        return $this;
429
    }
430
431
    /**
432
     * @param NodeInterface $node
433
     *
434
     * @throws NodalFlowException
435
     *
436
     * @return $this
437
     */
438
    protected function setNodeIncrement(NodeInterface $node)
439
    {
440
        $nodeId = $node->getId();
441
        foreach ($node->getNodeIncrements() as $incrementKey => $target) {
442
            if (is_string($target)) {
443
                if (!isset($this->nodeIncrements[$target])) {
444
                    throw new NodalFlowException('Tried to set an increment alias to an un-registered increment', 1, null, [
445
                        'aliasKey'  => $incrementKey,
446
                        'targetKey' => $target,
447
                    ]);
448
                }
449
450
                $this->nodeMap[$nodeId][$incrementKey] = &$this->nodeMap[$nodeId][$target];
451
                continue;
452
            }
453
454
            $this->nodeIncrements[$incrementKey]   = $target;
455
            $this->nodeMap[$nodeId][$incrementKey] = $target;
456
        }
457
458
        return $this;
459
    }
460
}
461