Completed
Pull Request — master (#5)
by Fabrice
02:06
created

FlowMap   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 440
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 7
dl 0
loc 440
rs 8.3396
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A __wakeup() 0 5 1
A register() 0 23 2
A getNodeIndex() 0 4 2
A flowStart() 0 6 1
A flowEnd() 0 10 1
A getNodeStat() 0 4 1
B getNodeMap() 0 23 5
C getStats() 0 24 7
A incrementNode() 0 6 1
A incrementFlow() 0 6 1
A resetNodeStats() 0 12 4
A duration() 0 20 4
A setRefs() 0 9 1
A initDefaults() 0 18 2
A resetTotals() 0 8 2
A setFlowIncrement() 0 20 4
B setNodeIncrement() 0 22 4

How to fix   Complexity   

Complex Class

Complex classes like FlowMap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FlowMap, and based on these observations, apply Extract Interface, too.

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