Completed
Push — master ( abdda3...f6743e )
by Kamil
02:29
created

MemoryCache::createConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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