Completed
Push — master ( 9d3ee0...c81f64 )
by Kamil
02:36
created

Cache   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 424
Duplicated Lines 6.84 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 93.57%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 7
dl 29
loc 424
ccs 131
cts 140
cp 0.9357
rs 7.9487
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A __destruct() 0 5 1
A isStarted() 0 4 1
A start() 0 12 2
A stop() 0 12 2
A end() 0 4 1
A isPaused() 0 4 1
A pause() 0 9 2
A resume() 0 8 2
A set() 0 19 4
A get() 0 8 3
A remove() 0 18 4
A exists() 8 8 2
A setTtl() 0 19 4
A getTtl() 0 12 3
A removeTtl() 0 16 3
A existsTtl() 0 8 2
A getKeys() 11 11 2
A getStats() 10 10 2
A flush() 0 13 2
A createConfig() 0 4 1
A createStats() 0 4 1
A handleStart() 0 5 1
A handleStop() 0 9 1
A handleTick() 0 21 4

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 Cache 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 Cache, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Dazzle\Cache;
4
5
use Dazzle\Event\BaseEventEmitter;
6
use Dazzle\Loop\LoopAwareTrait;
7
use Dazzle\Loop\LoopInterface;
8
use Dazzle\Loop\Timer\TimerInterface;
9
use Dazzle\Promise\Promise;
10
use Dazzle\Promise\PromiseInterface;
11
use Dazzle\Throwable\Exception\Runtime\ReadException;
12
use Dazzle\Throwable\Exception\Runtime\WriteException;
13
use Error;
14
use Exception;
15
16
class Cache extends BaseEventEmitter implements CacheInterface
17
{
18
    use LoopAwareTrait;
19
20
    /**
21
     * @var TimerInterface
22
     */
23
    protected $loopTimer;
24
25
    /**
26
     * @var mixed[]
27
     */
28
    protected $config;
29
30
    /**
31
     * @var bool
32
     */
33
    protected $open;
34
35
    /**
36
     * @var bool
37
     */
38
    protected $paused;
39
40
    /**
41
     * @var mixed[]
42
     */
43
    protected $storage;
44
45
    /**
46
     * @var TimerInterface[]
47
     */
48
    protected $timers;
49
50
    /**
51
     * @var int
52
     */
53
    protected $timersCounter;
54
55
    /**
56
     * @var mixed[]
57
     */
58
    protected $stats;
59
60
    /**
61
     * @param LoopInterface $loop
62
     * @param mixed[] $config
63
     */
64 29
    public function __construct(LoopInterface $loop, $config = [])
65
    {
66 29
        $this->loop = $loop;
67 29
        $this->loopTimer = null;
68 29
        $this->config = $this->createConfig($config);
69 29
        $this->open = false;
70 29
        $this->paused = true;
71 29
        $this->storage = [];
72 29
        $this->timers = [];
73 29
        $this->timersCounter = 0;
74 29
        $this->stats = $this->createStats();
75 29
    }
76
77
    /**
78
     *
79
     */
80 13
    public function __destruct()
81
    {
82 13
        $this->stop();
83 13
        parent::__destruct();
84 13
    }
85
86
    /**
87
     * @override
88
     * @inheritDoc
89
     */
90
    public function isStarted()
91
    {
92
        return $this->open;
93
    }
94
95
    /**
96
     * @override
97
     * @inheritDoc
98
     */
99 16
    public function start()
100
    {
101 16
        if ($this->open)
102
        {
103
            return Promise::doResolve($this);
104
        }
105
106 16
        $this->open = true;
107 16
        $this->handleStart();
108
109 16
        return Promise::doResolve($this);
110
    }
111
112
    /**
113
     * @override
114
     * @inheritDoc
115
     */
116 29
    public function stop()
117
    {
118 29
        if (!$this->open)
119
        {
120 13
            return Promise::doResolve($this);
121
        }
122
123 16
        $this->open = false;
124 16
        $this->handleStop();
125
126 16
        return Promise::doResolve($this);
127
    }
128
129
    /**
130
     * @override
131
     * @inheritDoc
132
     */
133 2
    public function end()
134
    {
135 2
        return $this->stop();
136
    }
137
138
    /**
139
     * @override
140
     * @inheritDoc
141
     */
142
    public function isPaused()
143
    {
144
        return $this->paused;
145
    }
146
147
    /**
148
     * @override
149
     * @inheritDoc
150
     */
151 16
    public function pause()
152
    {
153 16
        if (!$this->paused)
154
        {
155 16
            $this->paused = true;
156 16
            $this->loopTimer->cancel();
157 16
            $this->loopTimer = null;
158
        }
159 16
    }
160
161
    /**
162
     * @override
163
     * @inheritDoc
164
     */
165 16
    public function resume()
166
    {
167 16
        if ($this->paused)
168
        {
169 16
            $this->paused = false;
170 16
            $this->loopTimer = $this->getLoop()->addPeriodicTimer($this->config['interval'], [ $this, 'handleTick' ]);
171
        }
172 16
    }
173
174
    /**
175
     * @override
176
     * @inheritDoc
177
     */
178 15
    public function set($key, $val, $ttl = 0.0)
179
    {
180 15
        if (!$this->open)
181
        {
182 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
183
        }
184 14
        $this->storage[$key] = $val;
185
186 14
        if (is_object($val))
187
        {
188 1
            return Promise::doReject(new WriteException('Objects are not supported.'));
189
        }
190
191 13
        if ($ttl > 0)
192
        {
193
            return $this->setTtl($key, $ttl)->then(function() use($val) { return $val; });
194
        }
195 11
        return Promise::doResolve($val);
196
    }
197
198
    /**
199
     * @override
200
     * @inheritDoc
201
     */
202 6
    public function get($key)
203
    {
204 6
        if (!$this->open)
205
        {
206 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
207
        }
208 5
        return Promise::doResolve(array_key_exists($key, $this->storage) ? $this->storage[$key] : null);
209
    }
210
211
    /**
212
     * @override
213
     * @inheritDoc
214
     */
215 5
    public function remove($key)
216
    {
217 5
        if (!$this->open)
218
        {
219 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
220
        }
221 4
        if (!array_key_exists($key, $this->storage))
222
        {
223
            return Promise::doResolve(false);
224
        }
225 4
        unset($this->storage[$key]);
226
227 4
        if (isset($this->timers[$key]))
228
        {
229
            return $this->removeTtl($key)->then(function() { return true; });
230
        }
231 1
        return Promise::doResolve(true);
232
    }
233
234
    /**
235
     * @override
236
     * @inheritDoc
237
     */
238 5 View Code Duplication
    public function exists($key)
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...
239
    {
240 5
        if (!$this->open)
241
        {
242 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
243
        }
244 4
        return Promise::doResolve(array_key_exists($key, $this->storage));
245
    }
246
247
    /**
248
     * @override
249
     * @inheritDoc
250
     */
251 6
    public function setTtl($key, $ttl)
252
    {
253 6
        if (!$this->open)
254
        {
255 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
256
        }
257 5
        if ($ttl <= 0) {
258
            return Promise::doReject(new WriteException('TTL needs to be higher than 0.'));
259
        }
260 5
        if (!array_key_exists($key, $this->storage))
261
        {
262
            return Promise::doReject(new WriteException('Timeout cannot be set on undefined key.'));
263
        }
264
265 5
        $timer = round($ttl / $this->config['interval']);
266 5
        $this->timers[$key] = [ 'timeout' => $timer, 'ttl' => $ttl ];
267 5
        $this->timersCounter++;
268 5
        return Promise::doResolve($ttl);
269
    }
270
271
    /**
272
     * @override
273
     * @inheritDoc
274
     */
275 2
    public function getTtl($key)
276
    {
277 2
        if (!$this->open)
278
        {
279 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
280
        }
281 1
        if (!isset($this->timers[$key]))
282
        {
283 1
            return Promise::doResolve(0);
284
        }
285 1
        return Promise::doResolve($this->timers[$key]['ttl']);
286
    }
287
288
    /**
289
     * @override
290
     * @inheritDoc
291
     */
292 5
    public function removeTtl($key)
293
    {
294 5
        if (!$this->open)
295
        {
296 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
297
        }
298 4
        if (!isset($this->timers[$key]))
299
        {
300
            return Promise::doResolve(false);
301
        }
302
303 4
        unset($this->timers[$key]);
304 4
        $this->timersCounter--;
305
306 4
        return Promise::doResolve(true);
307
    }
308
309
    /**
310
     * @override
311
     * @inheritDoc
312
     */
313 2
    public function existsTtl($key)
314
    {
315 2
        if (!$this->open)
316
        {
317 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
318
        }
319 1
        return Promise::doResolve(isset($this->timers[$key]));
320
    }
321
322
    /**
323
     * @override
324
     * @inheritDoc
325
     */
326 2 View Code Duplication
    public function getKeys()
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...
327
    {
328 2
        if (!$this->open)
329
        {
330 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
331
        }
332 1
        return Promise::doResolve(array_keys($this->storage))->then(function($result) {
333 1
            sort($result);
334 1
            return $result;
335 1
        });
336
    }
337
338
    /**
339
     * @override
340
     * @inheritDoc
341
     */
342 2 View Code Duplication
    public function getStats()
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...
343
    {
344 2
        if (!$this->open)
345
        {
346 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
347
        }
348 1
        return Promise::doResolve([
349 1
            'keys'   => count($this->storage),
350
        ]);
351
    }
352
353
    /**
354
     * @override
355
     * @inheritDoc
356
     */
357 15
    public function flush()
358
    {
359 15
        if (!$this->open)
360
        {
361 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
362
        }
363
364 14
        $this->storage = [];
365 14
        $this->timers = [];
366 14
        $this->timersCounter = 0;
367
368 14
        return Promise::doResolve();
369
    }
370
371
    /**
372
     * Create configuration.
373
     *
374
     * @return mixed[]
375
     */
376 29
    protected function createConfig($config = [])
377
    {
378 29
        return array_merge([ 'interval' => 1e-1 ], $config);
379
    }
380
381
    /**
382
     * Create stats.
383
     *
384
     * @return mixed[]
385
     */
386 29
    protected function createStats()
387
    {
388 29
        return [];
389
    }
390
391
    /**
392
     * Handle start.
393
     */
394 16
    protected function handleStart()
395
    {
396 16
        $this->resume();
397 16
        $this->emit('start', [ $this ]);
398 16
    }
399
400
    /**
401
     * Handle stop.
402
     */
403 16
    protected function handleStop()
404
    {
405 16
        $this->storage = [];
406 16
        $this->timers = [];
407 16
        $this->timersCounter = 0;
408
409 16
        $this->pause();
410 16
        $this->emit('stop', [ $this ]);
411 16
    }
412
413
    /**
414
     * Handle loop tick.
415
     *
416
     * @internal
417
     */
418 3
    public function handleTick()
419
    {
420 3
        $timers = [];
421
422 3
        foreach ($this->timers as $key=>$timer)
423
        {
424 3
            if ($this->timers[$key]['timeout'] <= 1)
425
            {
426 3
                $timers[] = $key;
427
            }
428
            else
429
            {
430 3
                $this->timers[$key]['timeout']--;
431
            }
432
        }
433
434 3
        foreach ($timers as $key)
435
        {
436 3
            $this->remove($key);
437
        }
438 3
    }
439
}
440