SelectLoop   C
last analyzed

Complexity

Total Complexity 58

Size/Duplication

Total Lines 500
Duplicated Lines 13.2 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 82.94%

Importance

Changes 0
Metric Value
wmc 58
lcom 1
cbo 6
dl 66
loc 500
ccs 141
cts 170
cp 0.8294
rs 6.3005
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A __destruct() 0 13 1
A isRunning() 0 4 2
A addReadStream() 10 10 2
A addWriteStream() 10 10 2
A removeReadStream() 0 9 1
A removeWriteStream() 0 9 1
A removeStream() 0 5 1
A addTimer() 0 8 1
A addPeriodicTimer() 0 8 1
A cancelTimer() 0 7 2
A isTimerActive() 0 4 1
A onStart() 0 4 1
A onStop() 0 4 1
A onBeforeTick() 0 4 1
A onAfterTick() 0 4 1
A tick() 0 11 1
C start() 0 48 10
A stop() 0 10 2
A setFlowController() 0 4 1
A getFlowController() 0 4 1
A erase() 0 15 3
A export() 13 13 3
A import() 13 13 3
A swap() 0 15 3
B waitForStreamActivity() 20 32 6
A streamSelect() 0 13 4
A getTransferableProperties() 0 8 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SelectLoop 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 SelectLoop, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Dazzle\Loop\Model;
4
5
use Dazzle\Loop\Flow\FlowController;
6
use Dazzle\Loop\Tick\TickContinousQueue;
7
use Dazzle\Loop\Tick\TickFiniteQueue;
8
use Dazzle\Loop\Timer\Timer;
9
use Dazzle\Loop\Timer\TimerBox;
10
use Dazzle\Loop\Timer\TimerInterface;
11
use Dazzle\Loop\LoopModelInterface;
12
13
class SelectLoop implements LoopModelInterface
14
{
15
    /**
16
     * @var int
17
     */
18
    const MICROSECONDS_PER_SECOND = 1e6;
19
20
    /**
21
     * @var TickContinousQueue
22
     */
23
    protected $startTickQueue;
24
25
    /**
26
     * @var TickContinousQueue
27
     */
28
    protected $stopTickQueue;
29
30
    /**
31
     * @var TickContinousQueue
32
     */
33
    protected $nextTickQueue;
34
35
    /**
36
     * @var TickFiniteQueue
37
     */
38
    protected $futureTickQueue;
39
40
    /**
41
     * @var FlowController
42
     */
43
    protected $flowController;
44
45
    /**
46
     * @var TimerBox
47
     */
48
    protected $timers;
49
50
    /**
51
     * @var resource[]
52
     */
53
    protected $readStreams = [];
54
55
    /**
56
     * @var callable[]
57
     */
58
    protected $readListeners = [];
59
60
    /**
61
     * @var resource[]
62
     */
63
    protected $writeStreams = [];
64
65
    /**
66
     * @var callable[]
67
     */
68
    protected $writeListeners = [];
69
70
    /**
71
     *
72
     */
73 55
    public function __construct()
74
    {
75 55
        $this->startTickQueue = new TickContinousQueue($this);
76 55
        $this->stopTickQueue = new TickContinousQueue($this);
77 55
        $this->nextTickQueue = new TickContinousQueue($this);
78 55
        $this->futureTickQueue = new TickFiniteQueue($this);
79 55
        $this->flowController = new FlowController();
80 55
        $this->timers = new TimerBox();
81 55
    }
82
83
    /**
84
     *
85
     */
86 3
    public function __destruct()
87
    {
88 3
        unset($this->startTickQueue);
89 3
        unset($this->stopTickQueue);
90 3
        unset($this->nextTickQueue);
91 3
        unset($this->futureTickQueue);
92 3
        unset($this->flowController);
93 3
        unset($this->timers);
94 3
        unset($this->readStreams);
95 3
        unset($this->readListeners);
96 3
        unset($this->writeStreams);
97 3
        unset($this->writeListeners);
98 3
    }
99
100
    /**
101
     * @override
102
     * @inheritDoc
103
     */
104 12
    public function isRunning()
105
    {
106 12
        return isset($this->flowController->isRunning) ? $this->flowController->isRunning : false;
107
    }
108
109
    /**
110
     * @override
111
     * @inheritDoc
112
     */
113 14 View Code Duplication
    public function addReadStream($stream, callable $listener)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
    {
115 14
        $key = (int) $stream;
116
117 14
        if (!isset($this->readStreams[$key]))
118
        {
119 14
            $this->readStreams[$key] = $stream;
120 14
            $this->readListeners[$key] = $listener;
121
        }
122 14
    }
123
124
    /**
125
     * @override
126
     * @inheritDoc
127
     */
128 14 View Code Duplication
    public function addWriteStream($stream, callable $listener)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
129
    {
130 14
        $key = (int) $stream;
131
132 14
        if (!isset($this->writeStreams[$key]))
133
        {
134 14
            $this->writeStreams[$key] = $stream;
135 14
            $this->writeListeners[$key] = $listener;
136
        }
137 14
    }
138
139
    /**
140
     * @override
141
     * @inheritDoc
142
     */
143 10
    public function removeReadStream($stream)
144
    {
145 10
        $key = (int) $stream;
146
147
        unset(
148 10
            $this->readStreams[$key],
149 10
            $this->readListeners[$key]
150
        );
151 10
    }
152
153
    /**
154
     * @override
155
     * @inheritDoc
156
     */
157 14
    public function removeWriteStream($stream)
158
    {
159 14
        $key = (int) $stream;
160
161
        unset(
162 14
            $this->writeStreams[$key],
163 14
            $this->writeListeners[$key]
164
        );
165 14
    }
166
167
    /**
168
     * @override
169
     * @inheritDoc
170
     */
171 6
    public function removeStream($stream)
172
    {
173 6
        $this->removeReadStream($stream);
174 6
        $this->removeWriteStream($stream);
175 6
    }
176
177
    /**
178
     * @override
179
     * @inheritDoc
180
     */
181 8
    public function addTimer($interval, callable $callback)
182
    {
183 8
        $timer = new Timer($this, $interval, $callback, false);
184
185 8
        $this->timers->add($timer);
186
187 8
        return $timer;
188
    }
189
190
    /**
191
     * @override
192
     * @inheritDoc
193
     */
194 10
    public function addPeriodicTimer($interval, callable $callback)
195
    {
196 10
        $timer = new Timer($this, $interval, $callback, true);
197
198 10
        $this->timers->add($timer);
199
200 10
        return $timer;
201
    }
202
203
    /**
204
     * @override
205
     * @inheritDoc
206
     */
207 4
    public function cancelTimer(TimerInterface $timer)
208
    {
209 4
        if (isset($this->timers))
210
        {
211 4
            $this->timers->remove($timer);
212
        }
213 4
    }
214
215
    /**
216
     * @override
217
     * @inheritDoc
218
     */
219 4
    public function isTimerActive(TimerInterface $timer)
220
    {
221 4
        return $this->timers->contains($timer);
222
    }
223
224
    /**
225
     * @override
226
     * @inheritDoc
227
     */
228 2
    public function onStart(callable $listener)
229
    {
230 2
        $this->startTickQueue->add($listener);
231 2
    }
232
233
    /**
234
     * @override
235
     * @inheritDoc
236
     */
237 2
    public function onStop(callable $listener)
238
    {
239 2
        $this->stopTickQueue->add($listener);
240 2
    }
241
242
    /**
243
     * @override
244
     * @inheritDoc
245
     */
246 8
    public function onBeforeTick(callable $listener)
247
    {
248 8
        $this->nextTickQueue->add($listener);
249 8
    }
250
251
    /**
252
     * @override
253
     * @inheritDoc
254
     */
255 14
    public function onAfterTick(callable $listener)
256
    {
257 14
        $this->futureTickQueue->add($listener);
258 14
    }
259
260
    /**
261
     * @override
262
     * @inheritDoc
263
     */
264 28
    public function tick()
265
    {
266 28
        $this->flowController->isRunning = true;
267
268 28
        $this->nextTickQueue->tick();
269 28
        $this->futureTickQueue->tick();
270 28
        $this->timers->tick();
271 28
        $this->waitForStreamActivity(0);
272
273 28
        $this->flowController->isRunning = false;
274 28
    }
275
276
    /**
277
     * @override
278
     * @inheritDoc
279
     */
280 10
    public function start()
281
    {
282 10
        if ($this->flowController->isRunning)
283
        {
284
            return;
285
        }
286
287
        // TODO KRF-107
288 10
        $this->addPeriodicTimer(1, function() {
289
            usleep(1);
290 10
        });
291
292 10
        $this->flowController->isRunning = true;
293 10
        $this->startTickQueue->tick();
294
295 10
        while ($this->flowController->isRunning)
296
        {
297 10
            $this->nextTickQueue->tick();
298
299 10
            $this->futureTickQueue->tick();
300
301 10
            $this->timers->tick();
302
303
            // Next-tick or future-tick queues have pending callbacks ...
304 10
            if (!$this->flowController->isRunning || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty())
305
            {
306 10
                $timeout = 0;
307
            }
308
            // There is a pending timer, only block until it is due ...
309 4
            else if ($scheduledAt = $this->timers->getFirst())
310
            {
311 4
                $timeout = $scheduledAt - $this->timers->getTime();
312 4
                $timeout = ($timeout < 0) ? 0 : $timeout * self::MICROSECONDS_PER_SECOND;
313
            }
314
            // The only possible event is stream activity, so wait forever ...
315
            else if ($this->readStreams || $this->writeStreams)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->readStreams of type resource[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->writeStreams of type resource[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
316
            {
317
                $timeout = null;
318
            }
319
            // There's nothing left to do ...
320
            else
321
            {
322
                break;
323
            }
324
325 10
            $this->waitForStreamActivity($timeout);
326
        }
327 10
    }
328
329
    /**
330
     * @override
331
     * @inheritDoc
332
     */
333 14
    public function stop()
334
    {
335 14
        if (!$this->flowController->isRunning)
336
        {
337 4
            return;
338
        }
339
340 10
        $this->stopTickQueue->tick();
341 10
        $this->flowController->isRunning = false;
342 10
    }
343
344
    /**
345
     * @override
346
     * @inheritDoc
347
     */
348 2
    public function setFlowController($flowController)
349
    {
350 2
        $this->flowController = $flowController;
351 2
    }
352
353
    /**
354
     * @override
355
     * @inheritDoc
356
     */
357 4
    public function getFlowController()
358
    {
359 4
        return $this->flowController;
360
    }
361
362
    /**
363
     * @override
364
     * @inheritDoc
365
     */
366 4
    public function erase($all = false)
367
    {
368 4
        $this->stop();
369 4
        $loop = new static();
370
371 4
        $list = $all === true ? $this : $this->getTransferableProperties();
372 4
        foreach ($list as $key=>$val)
0 ignored issues
show
Bug introduced by
The expression $list of type this<Dazzle\Loop\Model\S...lowController":"null"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
373
        {
374 4
            $this->$key = $loop->$key;
375
        }
376
377 4
        $this->flowController->isRunning = false;
378
379 4
        return $this;
380
    }
381
382
    /**
383
     * @override
384
     * @inheritDoc
385
     */
386 View Code Duplication
    public function export(LoopModelInterface $loop, $all = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
387
    {
388
        $this->stop();
389
        $loop->stop();
390
391
        $list = $all === true ? $this : $this->getTransferableProperties();
392
        foreach ($list as $key=>$val)
0 ignored issues
show
Bug introduced by
The expression $list of type this<Dazzle\Loop\Model\S...lowController":"null"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
393
        {
394
            $loop->$key = $this->$key;
395
        }
396
397
        return $this;
398
    }
399
400
    /**
401
     * @override
402
     * @inheritDoc
403
     */
404 View Code Duplication
    public function import(LoopModelInterface $loop, $all = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
405
    {
406
        $this->stop();
407
        $loop->stop();
408
409
        $list = $all === true ? $this : $this->getTransferableProperties();
410
        foreach ($list as $key=>$val)
0 ignored issues
show
Bug introduced by
The expression $list of type this<Dazzle\Loop\Model\S...lowController":"null"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
411
        {
412
            $this->$key = $loop->$key;
413
        }
414
415
        return $this;
416
    }
417
418
    /**
419
     * @override
420
     * @inheritDoc
421
     */
422
    public function swap(LoopModelInterface $loop, $all = false)
423
    {
424
        $this->stop();
425
        $loop->stop();
426
427
        $list = $all === true ? $this : $this->getTransferableProperties();
428
        foreach ($list as $key=>$val)
0 ignored issues
show
Bug introduced by
The expression $list of type this<Dazzle\Loop\Model\S...lowController":"null"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
429
        {
430
            $tmp = $loop->$key;
431
            $loop->$key = $this->$key;
432
            $this->$key = $tmp;
433
        }
434
435
        return $this;
436
    }
437
438
    /**
439
     * Wait/check for stream activity, or until the next timer is due.
440
     *
441
     * @param float $timeout
442
     */
443 38
    private function waitForStreamActivity($timeout)
444
    {
445 38
        $read  = $this->readStreams;
446 38
        $write = $this->writeStreams;
447
448 38
        if ($this->streamSelect($read, $write, $timeout) === false)
449
        {
450
            return;
451
        }
452
453 38 View Code Duplication
        foreach ($read as $stream)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
454
        {
455 8
            $key = (int) $stream;
456
457 8
            if (isset($this->readListeners[$key]))
458
            {
459 8
                $callable = $this->readListeners[$key];
460 8
                $callable($stream, $this);
461
            }
462
        }
463
464 38 View Code Duplication
        foreach ($write as $stream)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
465
        {
466 8
            $key = (int) $stream;
467
468 8
            if (isset($this->writeListeners[$key]))
469
            {
470 8
                $callable = $this->writeListeners[$key];
471 8
                $callable($stream, $this);
472
            }
473
        }
474 38
    }
475
476
    /**
477
     * Emulate a stream_select() implementation that does not break when passed empty stream arrays.
478
     *
479
     * @param array &$read
480
     * @param array &$write
481
     * @param integer|null $timeout
482
     *
483
     * @return integer The total number of streams that are ready for read/write.
484
     */
485 38
    private function streamSelect(array &$read, array &$write, $timeout)
486
    {
487 38
        if ($read || $write)
0 ignored issues
show
Bug Best Practice introduced by
The expression $read of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $write of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
488
        {
489 12
            $except = null;
490
491 12
            return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
492
        }
493
494 32
        usleep($timeout);
495
496 32
        return 0;
497
    }
498
499
    /**
500
     * Get list of properties that can be exported/imported safely.
501
     *
502
     * @return array
503
     */
504 2
    private function getTransferableProperties()
505
    {
506
        return [
507 2
            'nextTickQueue'     => null,
508
            'futureTickQueue'   => null,
509
            'flowController'    => null
510
        ];
511
    }
512
}
513