Completed
Push — master ( d74992...2f807b )
by Kamil
03:48
created

Cache::removeTtl()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 17
cp 0
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 10
nc 4
nop 1
crap 42
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
    public function __construct(LoopInterface $loop, $config = [])
70
    {
71
        $this->loop = $loop;
72
        $this->loopTimer = null;
73
        $this->config = $this->createConfig($config);
74
        $this->open = false;
75
        $this->ending = false;
76
        $this->endingPromises = [];
77
        $this->paused = true;
78
        $this->storage = [];
79
        $this->timers = [];
80
        $this->timersCounter = 0;
81
    }
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 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
        if ($this->open || $this->ending)
108
        {
109
            return Promise::doResolve($this);
110
        }
111
112
        $this->open = true;
113
        $this->ending = false;
114
        $this->handleStart();
115
116
        return Promise::doResolve($this);
117
    }
118
119
    /**
120
     * @override
121
     * @inheritDoc
122
     */
123 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
        if (!$this->open && !$this->ending)
126
        {
127
            return Promise::doResolve($this);
128
        }
129
130
        $this->open = false;
131
        $this->ending = false;
132
        $this->handleStop();
133
134
        return Promise::doResolve($this);
135
    }
136
137
    /**
138
     * @override
139
     * @inheritDoc
140
     */
141
    public function end()
142
    {
143
        if (!$this->open && !$this->ending)
144
        {
145
            return Promise::doResolve($this);
146
        }
147
148
        if ($this->timersCounter === 0)
149
        {
150
            return $this->stop();
151
        }
152
153
        $promise = new Promise();
154
        $this->open = false;
155
        $this->ending = true;
156
        $this->endingPromises[] = $promise;
157
158
        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
    public function pause()
175
    {
176
        if (!$this->paused)
177
        {
178
            $this->paused = true;
179
            $this->loopTimer->cancel();
180
            $this->loopTimer = null;
181
        }
182
    }
183
184
    /**
185
     * @override
186
     * @inheritDoc
187
     */
188
    public function resume()
189
    {
190
        if ($this->paused)
191
        {
192
            $this->paused = false;
193
            $this->loopTimer = $this->getLoop()->addPeriodicTimer($this->config['interval'], [ $this, 'handleTick' ]);
194
        }
195
    }
196
197
    /**
198
     * @override
199
     * @inheritDoc
200
     */
201
    public function set($key, $val, $ttl = 0.0)
202
    {
203
        if (!$this->open)
204
        {
205
            return Promise::doReject(new WriteException('Cache object is not open.'));
206
        }
207
        $this->storage[$key] = $val;
208
209
        if ($ttl > 0)
210
        {
211
            return $this->setTtl($key, $ttl)->then(function() use($val) { return $val; });
212
        }
213
        return Promise::doResolve($val);
214
    }
215
216
    /**
217
     * @override
218
     * @inheritDoc
219
     */
220
    public function get($key)
221
    {
222
        if (!$this->open)
223
        {
224
            return Promise::doReject(new ReadException('Cache object is not open.'));
225
        }
226
        return Promise::doResolve(array_key_exists($key, $this->storage) ? $this->storage[$key] : null);
227
    }
228
229
    /**
230
     * @override
231
     * @inheritDoc
232
     */
233
    public function remove($key)
234
    {
235
        if (!$this->open && !$this->ending)
236
        {
237
            return Promise::doReject(new WriteException('Cache object is not open.'));
238
        }
239
        if (!array_key_exists($key, $this->storage))
240
        {
241
            return Promise::doResolve(false);
242
        }
243
        unset($this->storage[$key]);
244
245
        if (isset($this->timers[$key]))
246
        {
247
            return $this->removeTtl($key)->then(function() { return true; });
248
        }
249
        return Promise::doResolve(true);
250
    }
251
252
    /**
253
     * @override
254
     * @inheritDoc
255
     */
256 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
        if (!$this->open)
259
        {
260
            return Promise::doReject(new ReadException('Cache object is not open.'));
261
        }
262
        return Promise::doResolve(array_key_exists($key, $this->storage));
263
    }
264
265
    /**
266
     * @override
267
     * @inheritDoc
268
     */
269
    public function setTtl($key, $ttl)
270
    {
271
        if (!$this->open)
272
        {
273
            return Promise::doReject(new WriteException('Cache object is not open.'));
274
        }
275
        if ($ttl <= 0) {
276
            return Promise::doReject(new WriteException('TTL needs to be higher than 0.'));
277
        }
278
        if (!array_key_exists($key, $this->storage))
279
        {
280
            return Promise::doReject(new WriteException('Timeout cannot be set on undefined key.'));
281
        }
282
283
        $timer = round($ttl / $this->config['interval']);
284
        $this->timers[$key] = [ 'timeout' => $timer, 'ttl' => $ttl ];
285
        $this->timersCounter++;
286
        return Promise::doResolve($ttl);
287
    }
288
289
    /**
290
     * @override
291
     * @inheritDoc
292
     */
293
    public function getTtl($key)
294
    {
295
        if (!$this->open)
296
        {
297
            return Promise::doReject(new ReadException('Cache object is not open.'));
298
        }
299
        if (!isset($this->timers[$key]))
300
        {
301
            return Promise::doResolve(0);
302
        }
303
        return Promise::doResolve($this->timers[$key]['ttl']);
304
    }
305
306
    /**
307
     * @override
308
     * @inheritDoc
309
     */
310
    public function removeTtl($key)
311
    {
312
        if (!$this->open && !$this->ending)
313
        {
314
            return Promise::doReject(new WriteException('Cache object is not open.'));
315
        }
316
        if (!isset($this->timers[$key]))
317
        {
318
            return Promise::doResolve(false);
319
        }
320
321
        unset($this->timers[$key]);
322
        $this->timersCounter--;
323
324
        if ($this->ending && $this->timersCounter === 0)
325
        {
326
            return $this->stop()->then(function() { return true; });
327
        }
328
        return Promise::doResolve(true);
329
    }
330
331
    /**
332
     * @override
333
     * @inheritDoc
334
     */
335
    public function existsTtl($key)
336
    {
337
        if (!$this->open)
338
        {
339
            return Promise::doReject(new ReadException('Cache object is not open.'));
340
        }
341
        return Promise::doResolve(isset($this->timers[$key]));
342
    }
343
344
    /**
345
     * @override
346
     * @inheritDoc
347
     */
348 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
        if (!$this->open)
351
        {
352
            return Promise::doReject(new ReadException('Cache object is not open.'));
353
        }
354
        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
    protected function createConfig($config = [])
402
    {
403
        return array_merge([ 'interval' => 1e-1 ], $config);
404
    }
405
406
    /**
407
     * Handle start.
408
     */
409
    protected function handleStart()
410
    {
411
        $this->resume();
412
        $this->emit('start', [ $this ]);
413
    }
414
415
    /**
416
     * Handle stop.
417
     */
418
    protected function handleStop()
419
    {
420
        $this->storage = [];
421
        $this->timers = [];
422
        $this->timersCounter = 0;
423
424
        $this->pause();
425
426
        foreach ($this->endingPromises as $endingPromise)
427
        {
428
            $endingPromise->resolve($this);
429
        }
430
        $this->endingPromises = [];
431
432
        $this->emit('stop', [ $this ]);
433
    }
434
435
    /**
436
     * Handle loop tick.
437
     *
438
     * @internal
439
     */
440
    public function handleTick()
441
    {
442
        $timers = [];
443
444
        foreach ($this->timers as $key=>$timer)
445
        {
446
            if ($this->timers[$key]['timeout'] <= 1)
447
            {
448
                $timers[] = $key;
449
            }
450
            else
451
            {
452
                $this->timers[$key]['timeout']--;
453
            }
454
        }
455
456
        foreach ($timers as $key)
457
        {
458
            $this->remove($key);
459
        }
460
    }
461
}
462