Passed
Push — master ( 8666b2...9e6899 )
by Hong
04:52 queued 02:01
created

Cache::setMultiple()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 7
rs 10
1
<?php
2
3
/**
4
 * Phoole (PHP7.2+)
5
 *
6
 * @category  Library
7
 * @package   Phoole\Cache
8
 * @copyright Copyright (c) 2019 Hong Zhang
9
 */
10
declare(strict_types=1);
11
12
namespace Phoole\Cache;
13
14
use Psr\SimpleCache\CacheInterface;
15
use Phoole\Cache\Adaptor\AdaptorInterface;
16
use Psr\SimpleCache\InvalidArgumentException;
17
18
/**
19
 * Cache
20
 *
21
 * @package Phoole\Cache
22
 */
23
class Cache implements CacheInterface
24
{
25
    /**
26
     * @var AdaptorInterface
27
     */
28
    protected $adaptor;
29
30
    /**
31
     * @var bool
32
     */
33
    protected $byPass = false;
34
35
    /**
36
     * default TTL
37
     *
38
     * @var int
39
     */
40
    protected $defaultTTL;
41
42
    /**
43
     * distributed expiration time for DIFFERENT files
44
     *
45
     * 0 - 5 (%)
46
     *
47
     * @var int
48
     */
49
    protected $distributedExpiration;
50
51
    /**
52
     * Avoid stampede for one file by alter expire for each get
53
     *
54
     * 0 - 60 second
55
     *
56
     * @var int
57
     */
58
    protected $stampedeGap;
59
60
    /**
61
     * @param AdaptorInterface $adaptor
62
     * @param int $defaultTTL              86400 sec (one day)
63
     * @param int $distributedExpiration   0 - 5(%)
64
     * @param int $stampedeGap             0 - 60 sec
65
     */
66
    public function __construct(
67
        AdaptorInterface $adaptor,
68
        int $defaultTTL = 86400,
69
        int $distributedExpiration = 5,
70
        int $stampedeGap = 60
71
    ) {
72
        $this->adaptor = $adaptor;
73
        $this->defaultTTL = $defaultTTL;
74
        $this->distributedExpiration = $distributedExpiration;
75
        $this->stampedeGap = $stampedeGap;
76
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81
    public function get($key, $default = null)
82
    {
83
        // explicitly bypass the cache
84
        if ($this->byPass) {
85
            return $default;
86
        }
87
88
        $key = $this->checkKey($key);
89
        list($res, $time) = $this->adaptor->get($key);
90
91
        if ($this->checkTime($time)) {
92
            return $this->unSerialize($res);
93
        }
94
        return $default;
95
    }
96
97
    /**
98
     * {@inheritDoc}
99
     */
100
    public function set($key, $value, $ttl = null)
101
    {
102
        $ttl = $this->getTTL($ttl);
103
        $key = $this->checkKey($key);
104
        $val = $this->serialize($value);
105
        return $value ? $this->adaptor->set($key, $val, $ttl) : false;
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     */
111
    public function delete($key)
112
    {
113
        $key = $this->checkKey($key);
114
        return $this->adaptor->delete($key);
115
    }
116
117
    /**
118
     * {@inheritDoc}
119
     */
120
    public function clear()
121
    {
122
        return $this->adaptor->clear();
123
    }
124
125
    /**
126
     * {@inheritDoc}
127
     */
128
    public function getMultiple($keys, $default = null)
129
    {
130
        $result = [];
131
        foreach ($keys as $key) {
132
            $result[$key] = $this->get($key, $default);
133
        }
134
        return $result;
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140
    public function setMultiple($values, $ttl = null)
141
    {
142
        $res = true;
143
        foreach ($values as $key => $value) {
144
            $res &= $this->set($key, $value, $ttl);
145
        }
146
        return $res;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res also could return the type integer which is incompatible with the return type mandated by Psr\SimpleCache\CacheInterface::setMultiple() of boolean.
Loading history...
147
    }
148
149
    /**
150
     * {@inheritDoc}
151
     */
152
    public function deleteMultiple($keys)
153
    {
154
        $res = true;
155
        foreach ($keys as $key) {
156
            $res &= $this->delete($key);
157
        }
158
        return $res;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res also could return the type integer which is incompatible with the return type mandated by Psr\SimpleCache\CacheInterface::deleteMultiple() of boolean.
Loading history...
159
    }
160
161
    /**
162
     * {@inheritDoc}
163
     */
164
    public function has($key)
165
    {
166
        return null !== $this->get($key);
167
    }
168
169
    /**
170
     * @param  bool $bypass   explicitly bypass the cache
171
     * @return void
172
     */
173
    public function setByPass(bool $bypass = true)
174
    {
175
        $this->byPass = $bypass;
176
    }
177
178
    /**
179
     * Check key is valid or not
180
     *
181
     * @param  string $key
182
     * @return string
183
     * @throws InvalidArgumentException
184
     */
185
    protected function checkKey($key): string
186
    {
187
        try {
188
            return (string) $key;
189
        } catch (\Throwable $e) {
1 ignored issue
show
Unused Code introduced by
catch (\Throwable $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
190
            throw new class ($e->getMessage()) extends \InvalidArgumentException implements InvalidArgumentException {
191
            };
192
        }
193
    }
194
195
    /**
196
     * check expiration time
197
     *
198
     * @param  int $time
199
     * @return bool
200
     */
201
    protected function checkTime(int $time): bool
202
    {
203
        $now = time();
204
205
        // not expired
206
        if ($time > $now) {
207
            return true;
208
        }
209
        
210
        // just expired
211
        if ($time > $now - $this->stampedeGap) {
212
            // 5% chance expired (need rebuild cache)
213
            return rand(0, 100) > 5;
214
        }
215
216
        // expired
217
        return false;
218
    }
219
220
    /**
221
     * TTL +- 5% fluctuation
222
     *
223
     * @param  null|int|\DateInterval $ttl
224
     * @return int
225
     */
226
    protected function getTTL($ttl): int
227
    {
228
        if ($ttl instanceof \DateInterval) {
229
            $ttl = (int) $ttl->format('%s');
230
        }
231
232
        if (is_null($ttl)) {
233
            $ttl = $this->defaultTTL;
234
        }
235
236
        $rand = rand(-$this->distributedExpiration, $this->distributedExpiration);
237
        return (int) round($ttl * (100 + $rand) / 100);
238
    }
239
240
    /**
241
     * Serialize the value
242
     *
243
     * @param  mixed $value
244
     * @return string
245
     */
246
    protected function serialize($value): string
247
    {
248
        return \serialize($value);
249
    }
250
251
    /**
252
     * unserialize the value
253
     *
254
     * @param  string $value
255
     * @return mixed
256
     */
257
    protected function unSerialize(string $value)
258
    {
259
        return \unserialize($value);
260
    }
261
}
262