Passed
Push — master ( 4bba79...d3b029 )
by Terry
17:12
created

CacheProvider::gc()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 6
nop 2
dl 0
loc 28
ccs 16
cts 16
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the Shieldon Simple Cache package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\SimpleCache;
14
15
use Psr\SimpleCache\CacheInterface;
16
use Shieldon\SimpleCache\AssertTrait;
17
use DateInterval;
18
use Datetime;
19
use function is_null;
20
use function time;
21
22
/**
23
 * The abstract class for cache service providers.
24
 */
25
abstract class CacheProvider implements CacheInterface
26
{
27
    use AssertTrait;
28
29
    /**
30
     * @inheritDoc
31
     */
32 88
    public function get($key, $default = null)
33
    {
34 88
        $this->assertArgumentString($key);
35
36 86
        $data = $this->doGet($key);
37
38 86
        if (!empty($data)) {
39 82
            if ($this->isExpired($data['ttl'], $data['timestamp'])) {
40 12
                $this->delete($key);
41 12
                $data['value'] = $default;
42
            }
43
44 82
            $default = $data['value'];
45
        }
46
47 86
        return $default;
48
    }
49
50
    /**
51
     * @inheritDoc
52
     */
53 104
    public function set($key, $value, $ttl = null)
54
    {
55 104
        $this->assertArgumentString($key);
56 100
        $this->assertValidTypeOfTtl($ttl);
57
58 96
        $timestamp = time();
59
60 96
        if (is_null($ttl)) {
61 20
            $ttl = 0;
62
63 96
        } elseif ($ttl instanceof DateInterval) {
64 20
            $datetimeObj = new DateTime();
65 20
            $datetimeObj->add($ttl);
66
67 20
            $ttl = $datetimeObj->getTimestamp() - $timestamp;
68
        }
69
70 96
        return $this->doSet($key, $value, $ttl, $timestamp);
71
    }
72
73
    /**
74
     * @inheritDoc
75
     */
76 42
    public function delete($key)
77
    {
78 42
        $this->assertArgumentString($key);
79
80 42
        return $this->doDelete($key);
81
    }
82
83
    /**
84
     * @inheritDoc
85
     */
86 54
    public function clear()
87
    {
88 54
        return $this->doClear();
89
    }
90
91
    /**
92
     * @inheritDoc
93
     */
94 44
    public function has($key)
95
    {
96 44
        $this->assertArgumentString($key);
97
98 42
        if ($this->doHas($key)) {
99 42
            return true;
100
        }
101
102 42
        return false;
103
    }
104
105
    /**
106
     * @inheritDoc
107
     */
108 22
    public function getMultiple($keys, $default = null)
109
    {
110 22
        $this->assertArgumentIterable($keys);
111
112 22
        $data = [];
113
114 22
        foreach ($keys as $key) {
115 22
            $data[$key] = $this->get($key, $default);
116
        }
117
118 22
        return $data;
119
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124 38
    public function setMultiple($values, $ttl = null)
125
    {
126 38
        $this->assertArgumentIterable($values);
127
128 38
        foreach ($values as $key => $value) {
129 38
            if (!$this->set($key, $value, $ttl)) {
130
                // @codeCoverageIgnoreStart
131
                return false;
132
                // @codeCoverageIgnoreEnd
133
            }
134
        }
135
136 34
        return true;
137
    }
138
139
    /**
140
     * @inheritDoc
141
     */
142 24
    public function deleteMultiple($keys)
143
    {
144 24
        $this->assertArgumentIterable($keys);
145
146 22
        foreach ($keys as $key) {
147 22
            if (!$this->doDelete($key)) {
148
                // @codeCoverageIgnoreStart
149
                return false;
150
                // @codeCoverageIgnoreEnd
151
            }
152
        }
153
154 22
        return true;
155
    }
156
157
    /**
158
     * Performing cache data garbage collection for drivers that don't have 
159
     * ability to remove expired items automatically.
160
     * This method is not needed for Redis and Memcached driver.
161
     *
162
     * @param int $probability Numerator.
163
     * @param int $divisor     Denominator.
164
     *
165
     * @return array
166
     */
167 20
    public function gc(int $probability, int $divisor): array
168
    {
169 20
        if ($probability > $divisor) {
170 18
            $probability = $divisor;
171
        }
172 20
        $chance = intval($divisor / $probability);
173 20
        $hit    = rand(1, $chance);
174 20
        $list   = [];
175
176 20
        if ($hit === 1) {
177
178
            // Always return [] from Redis and Memcached driver.
179 20
            $data = $this->getAll();
180
181 20
            if (!empty($data)) {
182 10
                foreach ($data as $key => $value) {
183 10
                    $ttl      = (int) $value['ttl'];
184 10
                    $lasttime = (int) $value['timestamp'];
185
186 10
                    if ($this->isExpired($ttl, $lasttime)) {
187 10
                        $this->delete($key);
188
189 10
                        $list[] = $key;
190
                    }
191
                }
192
            }
193
        }
194 20
        return $list;
195
    }
196
197
    /**
198
     * Check if the TTL is expired or not.
199
     *
200
     * @param int $ttl       The time to live of a cached data.
201
     * @param int $timestamp The unix timesamp that want to check.
202
     * 
203
     * @return bool
204
     */
205 84
    protected function isExpired(int $ttl, int $timestamp): bool
206
    {
207 84
        $now = time();
208
209
        // If $ttl equal to 0 means that never expires.
210 84
        if (empty($ttl)) {
211 22
            return false;
212
213 82
        } elseif ($now - $timestamp < $ttl) {
214 82
            return false;
215
        }
216
217 20
        return true;
218
    }
219
220
    /**
221
     * Fetch all cache items to prepare removing expired items.
222
     * This method is not needed for Redis and Memcached driver because that
223
     * it is used only in `gc()`.
224
     *
225
     * @return array
226
     */
227 6
    protected function getAll(): array
228
    {
229 6
        return [];
230
    }
231
232
    /**
233
     * Fetch a cache by an extended Cache Driver.
234
     *
235
     * @param string $key     The key of a cache.
236
     * @param mixed  $default Default value to return if the key does not exist.
237
     *
238
     * @return array The data structure looks like:
239
     *
240
     * [
241
     *   [
242
     *     'value'     => (mixed) $value
243
     *     'ttl'       => (int)   $ttl,
244
     *     'timestamp' => (int)   $timestamp,
245
     *   ],
246
     *   ...
247
     * ]
248
     *
249
     */
250
    abstract protected function doGet(string $key): array;
251
252
    /**
253
     * Set a cache by an extended Cache Driver.
254
     *
255
     * @param string $key       The key of a cache.
256
     * @param mixed  $value     The value of a cache. (serialized)
257
     * @param int    $ttl       The time to live for a cache.
258
     * @param int    $timestamp The time to store a cache.
259
     *
260
     * @return bool
261
     */
262
    abstract protected function doSet(string $key, $value, int $ttl, int $timestamp): bool;
263
264
    /**
265
     * Delete a cache by an extended Cache Driver.
266
     *
267
     * @param string $key The key of a cache.
268
     * 
269
     * @return bool
270
     */
271
    abstract protected function doDelete(string $key): bool;
272
273
    /**
274
     * Delete all caches by an extended Cache Driver.
275
     * 
276
     * @return bool
277
     */
278
    abstract protected function doClear(): bool;
279
280
    /**
281
     * Check if a cahce exists or not.
282
     * 
283
     * @param string $key The key of a cache.
284
     * 
285
     * @return bool
286
     */
287
    abstract protected function doHas(string $key): bool;
288
}
289