Passed
Push — master ( 6dd623...d568d9 )
by Fabrice
01:31
created

FlowMap::increment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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