Completed
Push — master ( c81f64...abdda3 )
by Kamil
02:43
created

Cache::start()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.125

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 6
cts 12
cp 0.5
rs 9.2
cc 3
eloc 12
nc 3
nop 0
crap 4.125
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 16
        if ($this->loop->isRunning() === false)
106
        {
107
            $promise = new Promise();
108
            $this->loop->onStart(function() use($promise) {
109
                return $this->start()->then(function() use($promise) {
110
                    return $promise->resolve($this);
111
                });
112
            });
113
            return $promise;
114
        }
115
116 16
        $this->open = true;
117 16
        $this->handleStart();
118
119 16
        return Promise::doResolve($this);
120
    }
121
122
    /**
123
     * @override
124
     * @inheritDoc
125
     */
126 29
    public function stop()
127
    {
128 29
        if (!$this->open)
129
        {
130 13
            return Promise::doResolve($this);
131
        }
132
133 16
        $this->open = false;
134 16
        $this->handleStop();
135
136 16
        return Promise::doResolve($this);
137
    }
138
139
    /**
140
     * @override
141
     * @inheritDoc
142
     */
143 2
    public function end()
144
    {
145 2
        return $this->stop();
146
    }
147
148
    /**
149
     * @override
150
     * @inheritDoc
151
     */
152
    public function isPaused()
153
    {
154
        return $this->paused;
155
    }
156
157
    /**
158
     * @override
159
     * @inheritDoc
160
     */
161 16
    public function pause()
162
    {
163 16
        if (!$this->paused)
164
        {
165 16
            $this->paused = true;
166 16
            $this->loopTimer->cancel();
167 16
            $this->loopTimer = null;
168
        }
169 16
    }
170
171
    /**
172
     * @override
173
     * @inheritDoc
174
     */
175 16
    public function resume()
176
    {
177 16
        if ($this->paused)
178
        {
179 16
            $this->paused = false;
180 16
            $this->loopTimer = $this->getLoop()->addPeriodicTimer($this->config['interval'], [ $this, 'handleTick' ]);
181
        }
182 16
    }
183
184
    /**
185
     * @override
186
     * @inheritDoc
187
     */
188 15
    public function set($key, $val, $ttl = 0.0)
189
    {
190 15
        if (!$this->open)
191
        {
192 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
193
        }
194 14
        $this->storage[$key] = $val;
195
196 14
        if (is_object($val))
197
        {
198 1
            return Promise::doReject(new WriteException('Objects are not supported.'));
199
        }
200
201 13
        if ($ttl > 0)
202
        {
203
            return $this->setTtl($key, $ttl)->then(function() use($val) { return $val; });
204
        }
205 11
        return Promise::doResolve($val);
206
    }
207
208
    /**
209
     * @override
210
     * @inheritDoc
211
     */
212 6
    public function get($key)
213
    {
214 6
        if (!$this->open)
215
        {
216 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
217
        }
218 5
        return Promise::doResolve(array_key_exists($key, $this->storage) ? $this->storage[$key] : null);
219
    }
220
221
    /**
222
     * @override
223
     * @inheritDoc
224
     */
225 5
    public function remove($key)
226
    {
227 5
        if (!$this->open)
228
        {
229 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
230
        }
231 4
        if (!array_key_exists($key, $this->storage))
232
        {
233
            return Promise::doResolve(false);
234
        }
235 4
        unset($this->storage[$key]);
236
237 4
        if (isset($this->timers[$key]))
238
        {
239
            return $this->removeTtl($key)->then(function() { return true; });
240
        }
241 1
        return Promise::doResolve(true);
242
    }
243
244
    /**
245
     * @override
246
     * @inheritDoc
247
     */
248 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...
249
    {
250 5
        if (!$this->open)
251
        {
252 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
253
        }
254 4
        return Promise::doResolve(array_key_exists($key, $this->storage));
255
    }
256
257
    /**
258
     * @override
259
     * @inheritDoc
260
     */
261 6
    public function setTtl($key, $ttl)
262
    {
263 6
        if (!$this->open)
264
        {
265 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
266
        }
267 5
        if ($ttl <= 0) {
268
            return Promise::doReject(new WriteException('TTL needs to be higher than 0.'));
269
        }
270 5
        if (!array_key_exists($key, $this->storage))
271
        {
272
            return Promise::doReject(new WriteException('Timeout cannot be set on undefined key.'));
273
        }
274
275 5
        $timer = round($ttl / $this->config['interval']);
276 5
        $this->timers[$key] = [ 'timeout' => $timer, 'ttl' => $ttl ];
277 5
        $this->timersCounter++;
278 5
        return Promise::doResolve($ttl);
279
    }
280
281
    /**
282
     * @override
283
     * @inheritDoc
284
     */
285 2
    public function getTtl($key)
286
    {
287 2
        if (!$this->open)
288
        {
289 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
290
        }
291 1
        if (!isset($this->timers[$key]))
292
        {
293 1
            return Promise::doResolve(0);
294
        }
295 1
        return Promise::doResolve($this->timers[$key]['ttl']);
296
    }
297
298
    /**
299
     * @override
300
     * @inheritDoc
301
     */
302 5
    public function removeTtl($key)
303
    {
304 5
        if (!$this->open)
305
        {
306 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
307
        }
308 4
        if (!isset($this->timers[$key]))
309
        {
310
            return Promise::doResolve(false);
311
        }
312
313 4
        unset($this->timers[$key]);
314 4
        $this->timersCounter--;
315
316 4
        return Promise::doResolve(true);
317
    }
318
319
    /**
320
     * @override
321
     * @inheritDoc
322
     */
323 2
    public function existsTtl($key)
324
    {
325 2
        if (!$this->open)
326
        {
327 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
328
        }
329 1
        return Promise::doResolve(isset($this->timers[$key]));
330
    }
331
332
    /**
333
     * @override
334
     * @inheritDoc
335
     */
336 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...
337
    {
338 2
        if (!$this->open)
339
        {
340 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
341
        }
342 1
        return Promise::doResolve(array_keys($this->storage))->then(function($result) {
343 1
            sort($result);
344 1
            return $result;
345 1
        });
346
    }
347
348
    /**
349
     * @override
350
     * @inheritDoc
351
     */
352 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...
353
    {
354 2
        if (!$this->open)
355
        {
356 1
            return Promise::doReject(new ReadException('Cache object is not open.'));
357
        }
358 1
        return Promise::doResolve([
359 1
            'keys'   => count($this->storage),
360
        ]);
361
    }
362
363
    /**
364
     * @override
365
     * @inheritDoc
366
     */
367 15
    public function flush()
368
    {
369 15
        if (!$this->open)
370
        {
371 1
            return Promise::doReject(new WriteException('Cache object is not open.'));
372
        }
373
374 14
        $this->storage = [];
375 14
        $this->timers = [];
376 14
        $this->timersCounter = 0;
377
378 14
        return Promise::doResolve();
379
    }
380
381
    /**
382
     * Create configuration.
383
     *
384
     * @return mixed[]
385
     */
386 29
    protected function createConfig($config = [])
387
    {
388 29
        return array_merge([ 'interval' => 1e-1 ], $config);
389
    }
390
391
    /**
392
     * Create stats.
393
     *
394
     * @return mixed[]
395
     */
396 29
    protected function createStats()
397
    {
398 29
        return [];
399
    }
400
401
    /**
402
     * Handle start.
403
     */
404 16
    protected function handleStart()
405
    {
406 16
        $this->resume();
407 16
        $this->emit('start', [ $this ]);
408 16
    }
409
410
    /**
411
     * Handle stop.
412
     */
413 16
    protected function handleStop()
414
    {
415 16
        $this->storage = [];
416 16
        $this->timers = [];
417 16
        $this->timersCounter = 0;
418
419 16
        $this->pause();
420 16
        $this->emit('stop', [ $this ]);
421 16
    }
422
423
    /**
424
     * Handle loop tick.
425
     *
426
     * @internal
427
     */
428 3
    public function handleTick()
429
    {
430 3
        $timers = [];
431
432 3
        foreach ($this->timers as $key=>$timer)
433
        {
434 3
            if ($this->timers[$key]['timeout'] <= 1)
435
            {
436 3
                $timers[] = $key;
437
            }
438
            else
439
            {
440 3
                $this->timers[$key]['timeout']--;
441
            }
442
        }
443
444 3
        foreach ($timers as $key)
445
        {
446 3
            $this->remove($key);
447
        }
448 3
    }
449
}
450