Passed
Push — master ( 973769...9ec99d )
by Fabrice
01:43
created

FlowMap::duration()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 12
nc 3
nop 1
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
 */
20
class FlowMap implements FlowMapInterface
21
{
22
    /**
23
     * Flow map
24
     *
25
     * @var array
26
     */
27
    protected $nodeMap = [];
28
29
    /**
30
     * @var array
31
     */
32
    protected $reverseMap = [];
33
34
    /**
35
     * The default Node Map values
36
     *
37
     * @var array
38
     */
39
    protected $nodeMapDefault = [
40
        'class'           => null,
41
        'flowId'          => null,
42
        'hash'            => null,
43
        'index'           => null,
44
        'isATraversable'  => null,
45
        'isAReturningVal' => null,
46
        'isAFlow'         => null,
47
        'num_exec'        => 0,
48
        'num_iterate'     => 0,
49
        'num_break'       => 0,
50
        'num_continue'    => 0,
51
    ];
52
53
    /**
54
     * The default Node stats values
55
     *
56
     * @var array
57
     */
58
    protected $incrementStatsDefault = [
59
        'num_exec'     => 0,
60
        'num_iterate'  => 0,
61
        'num_break'    => 0,
62
        'num_continue' => 0,
63
    ];
64
65
    /**
66
     * The Flow stats default values
67
     *
68
     * @var array
69
     */
70
    protected $flowStatsDefault = [
71
        'class'    => null,
72
        'id'       => null,
73
        'start'    => null,
74
        'end'      => null,
75
        'elapsed'  => null,
76
        'duration' => null,
77
        'mib'      => null,
78
    ];
79
80
    /**
81
     * @var array
82
     */
83
    protected $defaultFlowStats;
84
85
    /**
86
     * @var array
87
     */
88
    protected $flowStats;
89
90
    /**
91
     * @var bool
92
     */
93
    protected $resetOnRestart = false;
94
95
    /**
96
     * @var FlowInterface
97
     */
98
    protected $flow;
99
100
    /**
101
     * @var bool
102
     */
103
    protected $hasRun = false;
104
105
    /**
106
     * Instantiate a Flow Status
107
     *
108
     * @param FlowInterface $flow
109
     *
110
     * @internal param string $status The flow status
111
     */
112
    public function __construct(FlowInterface $flow)
113
    {
114
        $this->flow             = $flow;
115
        $this->defaultFlowStats = array_replace($this->flowStatsDefault, $this->incrementStatsDefault, [
116
            'class' => get_class($this->flow),
117
            'id'    => $this->flow->getId(),
118
        ]);
119
        $this->flowStats        = $this->defaultFlowStats;
120
    }
121
122
    /**
123
     * Set additional increment keys, use :
124
     *      'keyName' => int
125
     * to add keyName as increment, starting at int
126
     * or :
127
     *      'keyName' => 'existingIncrement'
128
     * to assign keyName as a reference to existingIncrement
129
     *
130
     * @param array $flowIncrements
131
     *
132
     * @throws NodalFlowException
133
     *
134
     * @return $this
135
     */
136
    public function setFlowIncrement(array $flowIncrements)
137
    {
138
        if ($this->hasRun) {
139
            throw new NodalFlowException('Cannot set custom increment after Flow started');
140
        }
141
142
        foreach ($flowIncrements as $incrementKey => $target) {
143
            if (is_string($target)) {
144
                if (!isset($this->flowStats[$target])) {
145
                    throw new NodalFlowException('Cannot set reference on unset target');
146
                }
147
148
                $this->flowStats[$incrementKey] = &$this->flowStats[$target];
149
                continue;
150
            }
151
152
            $this->defaultFlowStats[$incrementKey] = $target;
153
            $this->flowStats[$incrementKey]        = $target;
154
        }
155
156
        return $this;
157
    }
158
159
    /**
160
     * @param NodeInterface $node
161
     * @param int           $index
162
     *
163
     * @throws NodalFlowException
164
     */
165
    public function register(NodeInterface $node, $index)
166
    {
167
        $this->enforceUniqueness($node);
168
        $nodeHash                      = $node->getNodeHash();
169
        $this->nodeMap[$nodeHash]      = array_replace($this->nodeMapDefault, [
170
            'class'           => get_class($node),
171
            'flowId'          => $node->getCarrier()->getId(),
172
            'hash'            => $nodeHash,
173
            'index'           => $index,
174
            'isATraversable'  => $node->isTraversable(),
175
            'isAReturningVal' => $node->isReturningVal(),
176
            'isAFlow'         => $node->isFlow(),
177
        ]);
178
179
        if (isset($this->reverseMap[$index])) {
180
            // replacing a note, maintain nodeMap accordingly
181
            unset($this->nodeMap[$this->reverseMap[$index]]);
182
        }
183
184
        $this->reverseMap[$index] = $nodeHash;
185
    }
186
187
    /**
188
     * Triggered right before the flow starts
189
     *
190
     * @return $this
191
     */
192
    public function flowStart()
193
    {
194
        $this->flowStats['start'] = microtime(true);
195
        $this->hasRun             = true;
196
197
        return $this;
198
    }
199
200
    /**
201
     * Triggered right after the flow stops
202
     *
203
     * @return $this
204
     */
205
    public function flowEnd()
206
    {
207
        $this->flowStats['end']     = microtime(true);
208
        $this->flowStats['mib']     = memory_get_peak_usage(true) / 1048576;
209
        $this->flowStats['elapsed'] = $this->flowStats['end'] - $this->flowStats['start'];
210
211
        $this->flowStats = array_replace($this->flowStats, $this->duration($this->flowStats['elapsed']));
212
213
        return $this;
214
    }
215
216
    /**
217
     * Get/Generate Node Map
218
     *
219
     * @return array
220
     */
221
    public function getNodeMap()
222
    {
223
        foreach ($this->flow->getNodes() as $node) {
224
            if ($node instanceof BranchNodeInterface) {
225
                $this->nodeMap[$node->getNodeHash()]['nodes'] = $node->getPayload()->getNodeMap();
226
                continue;
227
            }
228
229
            if ($node instanceof AggregateNodeInterface) {
230
                $flowId = $node->getCarrier()->getId();
231
                foreach ($node->getNodeCollection() as $aggregatedNode) {
232
                    $this->nodeMap[$node->getNodeHash()]['nodes'][$aggregatedNode->getNodeHash()] = array_replace($this->nodeMapDefault, [
233
                        'class'  => get_class($aggregatedNode),
234
                        'flowId' => $flowId,
235
                        'hash'   => $aggregatedNode->getNodeHash(),
236
                    ]);
237
                }
238
                continue;
239
            }
240
        }
241
242
        return $this->nodeMap;
243
    }
244
245
    /**
246
     * Get the latest Node stats
247
     *
248
     * @return array
249
     */
250
    public function getStats()
251
    {
252
        $result = array_intersect_key($this->flowStats, $this->defaultFlowStats);
253
        foreach ($this->flow->getNodes() as $node) {
254
            if ($node instanceof BranchNodeInterface) {
255
                $result['branches'][$node->getPayload()->getId()] = $node->getPayload()->getStats();
256
            }
257
        }
258
259
        return $result;
260
    }
261
262
    /**
263
     * @param string $nodeHash
264
     * @param array  $incrementAliases
265
     *
266
     * @throws NodalFlowException
267
     *
268
     * @return $this
269
     */
270
    public function setIncrementAlias($nodeHash, array $incrementAliases = [])
271
    {
272
        foreach ($incrementAliases as $aliasKey => $incKey) {
273
            if (!isset($this->incrementStatsDefault[$incKey])) {
274
                throw new NodalFlowException('Tried to set an increment alias to an un-registered increment', 1, null, [
275
                    'aliasKey'     => $aliasKey,
276
                    'incrementKey' => $incKey,
277
                ]);
278
            }
279
280
            $this->nodeMap[$nodeHash][$aliasKey] = &$this->nodeMap[$nodeHash][$incKey];
281
        }
282
283
        return $this;
284
    }
285
286
    /**
287
     * @param string $nodeHash
288
     * @param string $key
289
     *
290
     * @return $this
291
     */
292
    public function increment($nodeHash, $key)
293
    {
294
        ++$this->nodeMap[$nodeHash][$key];
295
296
        return $this;
297
    }
298
299
    /**
300
     * @param string $key
301
     *
302
     * @return $this
303
     */
304
    public function incrementFlow($key)
305
    {
306
        ++$this->flowStats[$key];
307
308
        return $this;
309
    }
310
311
    /**
312
     * Resets Nodes stats, can be used prior to Flow's re-exec
313
     *
314
     * @return $this
315
     */
316
    public function resetNodeStats()
317
    {
318
        foreach ($this->nodeMap as &$nodeStat) {
319
            $nodeStat = array_replace($nodeStat, $this->incrementStatsDefault);
320
        }
321
322
        return $this;
323
    }
324
325
    /**
326
     * Computes a human readable duration string from floating seconds
327
     *
328
     * @param float $seconds
329
     *
330
     * @return array<string,integer|string>
331
     */
332
    public function duration($seconds)
333
    {
334
        $result = [
335
            'hour'     => (int) floor($seconds / 3600),
336
            'min'      => (int) floor(($seconds / 60) % 60),
337
            'sec'      => $seconds % 60,
338
            'ms'       => (int) round(\fmod($seconds, 1) * 1000),
339
        ];
340
341
        $duration = '';
342
        foreach ($result as $unit => $value) {
343
            if (!empty($value) || $unit === 'ms') {
344
                $duration .= $value . "$unit ";
345
            }
346
        }
347
348
        $result['duration'] = trim($duration);
349
350
        return $result;
351
    }
352
353
    /**
354
     * @param NodeInterface $node
355
     *
356
     * @throws NodalFlowException
357
     *
358
     * @return $this
359
     */
360
    protected function enforceUniqueness(NodeInterface $node)
361
    {
362
        if (isset($this->nodeMap[$node->getNodeHash()])) {
363
            throw new NodalFlowException('Cannot reuse Node instances within a Flow', 1, null, [
364
                'duplicate_node' => get_class($node),
365
                'hash'           => $node->getNodeHash(),
366
            ]);
367
        }
368
369
        return $this;
370
    }
371
}
372