Completed
Push — master ( a65a6b...acbbb0 )
by Kamil
05:11 queued 01:54
created

Cache::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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