Completed
Push — master ( ed65a2...293665 )
by Fabrice
01:50
created

FlowMap::__wakeup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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