Completed
Push — master ( 3e948f...6ec1ff )
by Alessandro
01:59
created

JsonCache   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 290
rs 8.8
wmc 36

13 Methods

Rating   Name   Duplication   Size   Complexity  
A deleteStorage() 0 6 2
A setMultiple() 0 12 4
A __construct() 0 14 1
A set() 0 14 4
A delete() 0 10 3
A saveDataToStorage() 0 3 1
A getDataFromStorage() 0 7 3
B get() 0 13 5
A has() 0 11 3
A getMultiple() 0 10 3
A clear() 0 3 1
A filterValidateKey() 0 6 2
A deleteMultiple() 0 12 4
1
<?php
2
/**
3
 * @package JsonCache
4
 * @version v0.1.0-alpha
5
 * @author Alessandro Raffa, Infobiotech S.r.l. <[email protected]>
6
 * @copyright (c) 2014-2017, Infobiotech S.r.l.
7
 * @license http://mit-license.org/
8
 * @uses league/flysystem
9
 * @uses psr/simple-cache
10
 */
11
12
namespace Infobiotech;
13
14
use Psr\SimpleCache\CacheInterface;
15
use League\Flysystem\Filesystem;
16
use League\Flysystem\AdapterInterface as FlysystemAdapter;
17
18
/**
19
 * JSON-based PSR-16 cache implementation.
20
 *
21
 * @author Alessandro Raffa, Infobiotech S.r.l. <[email protected]>
22
 * @todo Add a local buffer https://github.com/matthiasmullie/scrapbook#local-buffer
23
 * @todo Add transactions https://github.com/matthiasmullie/scrapbook#transactions
24
 * @todo Add stampede protection https://github.com/matthiasmullie/scrapbook#stampede-protection
25
 * @todo Add more interfaces https://github.com/matthiasmullie/scrapbook#interfaces
26
 *
27
 */
28
class JsonCache implements CacheInterface
29
{
30
    /*
31
     *
32
     */
33
    const FILED_VALUE      = 'value';
34
    const FILED_EXPIRATION = 'expiration';
35
    /*
36
     *
37
     */
38
    const DEFAULT_TTL      = 1200;
39
40
    /**
41
     *
42
     * @var \League\Flysystem\Filesystem
43
     */
44
    protected $filesystem;
45
46
    /**
47
     *
48
     * @var string
49
     */
50
    protected $namespace;
51
52
    /**
53
     *
54
     * @param FlysystemAdapter $filesystemAdapter
55
     * @param string $namespace
56
     * @return JsonCache
57
     */
58
    public function __construct(FlysystemAdapter $filesystemAdapter, $namespace)
59
    {
60
        /*
61
         *
62
         */
63
        $this->filesystem = new Filesystem($filesystemAdapter);
64
        /*
65
         *
66
         */
67
        $this->namespace  = $namespace.'.json';
68
        /*
69
         *
70
         */
71
        return $this;
72
    }
73
74
    /**
75
     * Fetches a value from the cache.
76
     *
77
     * @param string $key     The unique key of this item in the cache.
78
     * @param mixed  $default Default value to return if the key does not exist.
79
     *
80
     * @return mixed The value of the item from the cache, or $default in case of cache miss.
81
     *
82
     * @throws \Psr\SimpleCache\InvalidArgumentException
83
     *   MUST be thrown if the $key string is not a legal value.
84
     *
85
     * @todo Implement better $key validation and filtering
86
     */
87
    public function get($key, $default = null)
88
    {
89
        $value = $default;
90
        if (!is_string($this->filterValidateKey($key))) {
91
            throw new \Psr\SimpleCache\InvalidArgumentException();
92
        }
93
        $data = $this->getDataFromStorage();
94
        if (isset($data[$key]) && isset($data[$key][self::FILED_EXPIRATION])) {
95
            if ($data[$key][self::FILED_EXPIRATION] > microtime(true)) {
96
                $value = $data[$key][self::FILED_VALUE];
97
            }
98
        }
99
        return $value;
100
    }
101
102
    /**
103
     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
104
     *
105
     * @param string                $key   The key of the item to store.
106
     * @param mixed                 $value The value of the item to store, must be serializable.
107
     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
108
     *                                     the driver supports TTL then the library may set a default value
109
     *                                     for it or let the driver take care of that.
110
     *
111
     * @return bool True on success and false on failure.
112
     *
113
     * @throws \Psr\SimpleCache\InvalidArgumentException
114
     *   MUST be thrown if the $key string is not a legal value.
115
     *
116
     * @todo Implement better $key validation and filtering
117
     */
118
    public function set($key, $value, $ttl = null)
119
    {
120
        if (!is_string($this->filterValidateKey($key))) {
121
            throw new \Psr\SimpleCache\InvalidArgumentException();
122
        }
123
        if (empty($ttl) || !is_numeric($ttl)) {
124
            $ttl = self::DEFAULT_TTL;
125
        }
126
        $data       = $this->getDataFromStorage();
127
        $data[$key] = [
128
            self::FILED_VALUE      => $value,
129
            self::FILED_EXPIRATION => microtime(true) + (float) $ttl,
130
        ];
131
        return $this->saveDataToStorage($data);
132
    }
133
134
    /**
135
     * Delete an item from the cache by its unique key.
136
     *
137
     * @param string $key The unique cache key of the item to delete.
138
     *
139
     * @return bool True if the item was successfully removed. False if there was an error.
140
     *
141
     * @throws \Psr\SimpleCache\InvalidArgumentException
142
     *   MUST be thrown if the $key string is not a legal value.
143
     */
144
    public function delete($key)
145
    {
146
        if (!is_string($this->filterValidateKey($key))) {
147
            throw new \Psr\SimpleCache\InvalidArgumentException();
148
        }
149
        $data = $this->getDataFromStorage();
150
        if (isset($data[$key])) {
151
            unset($data[$key]);
152
        }
153
        return $this->saveDataToStorage($data);
154
    }
155
156
    /**
157
     * Wipes clean the entire cache's keys.
158
     *
159
     * @return bool True on success and false on failure.
160
     */
161
    public function clear()
162
    {
163
        return $this->deleteStorage();
164
    }
165
166
    /**
167
     * Obtains multiple cache items by their unique keys.
168
     *
169
     * @param iterable $keys    A list of keys that can obtained in a single operation.
170
     * @param mixed    $default Default value to return for keys that do not exist.
171
     *
172
     * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
173
     *
174
     * @throws \Psr\SimpleCache\InvalidArgumentException
175
     *   MUST be thrown if $keys is neither an array nor a Traversable,
176
     *   or if any of the $keys are not a legal value.
177
     */
178
    public function getMultiple($keys, $default = null)
179
    {
180
        if (!is_array($keys)) {
181
            throw new \Psr\SimpleCache\InvalidArgumentException();
182
        }
183
        $values = [];
184
        foreach ($keys as $key) {
185
            $values[$key] = $this->get($key, $default);
186
        }
187
        return $values;
188
    }
189
190
    /**
191
     * Persists a set of key => value pairs in the cache, with an optional TTL.
192
     *
193
     * @param iterable              $values A list of key => value pairs for a multiple-set operation.
194
     * @param null|int|\DateInterval $ttl    Optional. The TTL value of this item. If no value is sent and
195
     *                                      the driver supports TTL then the library may set a default value
196
     *                                      for it or let the driver take care of that.
197
     *
198
     * @return bool True on success and false on failure.
199
     *
200
     * @throws \Psr\SimpleCache\InvalidArgumentException
201
     *   MUST be thrown if $values is neither an array nor a Traversable,
202
     *   or if any of the $values are not a legal value.
203
     */
204
    public function setMultiple($values, $ttl = null)
205
    {
206
        $failure = false;
207
        if (!is_array($values)) {
208
            throw new \Psr\SimpleCache\InvalidArgumentException();
209
        }
210
        foreach ($values as $key => $value) {
211
            if (!$this->set($key, $value, $ttl)) {
212
                $failure = true;
213
            }
214
        }
215
        return (bool) !$failure;
216
    }
217
218
    /**
219
     * Deletes multiple cache items in a single operation.
220
     *
221
     * @param iterable $keys A list of string-based keys to be deleted.
222
     *
223
     * @return bool True if the items were successfully removed. False if there was an error.
224
     *
225
     * @throws \Psr\SimpleCache\InvalidArgumentException
226
     *   MUST be thrown if $keys is neither an array nor a Traversable,
227
     *   or if any of the $keys are not a legal value.
228
     */
229
    public function deleteMultiple($keys)
230
    {
231
        $failure = false;
232
        if (!is_array($keys)) {
233
            throw new \Psr\SimpleCache\InvalidArgumentException();
234
        }
235
        foreach ($keys as $key) {
236
            if (!$this->delete($key)) {
237
                $failure = true;
238
            }
239
        }
240
        return (bool) !$failure;
241
    }
242
243
    /**
244
     * Determines whether an item is present in the cache.
245
     *
246
     * NOTE: It is recommended that has() is only to be used for cache warming type purposes
247
     * and not to be used within your live applications operations for get/set, as this method
248
     * is subject to a race condition where your has() will return true and immediately after,
249
     * another script can remove it making the state of your app out of date.
250
     *
251
     * @param string $key The cache item key.
252
     *
253
     * @return bool
254
     *
255
     * @throws \Psr\SimpleCache\InvalidArgumentException
256
     *   MUST be thrown if the $key string is not a legal value.
257
     */
258
    public function has($key)
259
    {
260
        $has = false;
261
        if (!is_string($this->filterValidateKey($key))) {
262
            throw new \Psr\SimpleCache\InvalidArgumentException();
263
        }
264
        $data = $this->getDataFromStorage();
265
        if (isset($data[$key])) {
266
            $has = true;
267
        }
268
        return $has;
269
    }
270
271
    /**
272
     *
273
     * @return array
274
     */
275
    protected function getDataFromStorage()
276
    {
277
        if (!$this->filesystem->has($this->namespace)) {
278
            $this->filesystem->write($this->namespace, json_encode([]));
279
        }
280
        $rawDataFromStorage = $this->filesystem->read($this->namespace);
281
        return $rawDataFromStorage !== false ? json_decode($rawDataFromStorage, true) : [];
282
    }
283
284
    /**
285
     *
286
     * @param array $data
287
     * @return boolean
288
     */
289
    protected function saveDataToStorage($data)
290
    {
291
        return (bool) $this->filesystem->put($this->namespace, json_encode($data));
292
    }
293
294
    /**
295
     *
296
     * @param array $data
297
     * @return boolean
298
     */
299
    protected function deleteStorage()
300
    {
301
        if ($this->filesystem->has($this->namespace)) {
302
            return (bool) $this->filesystem->delete($this->namespace);
303
        }
304
        return true;
305
    }
306
307
    /**
308
     *
309
     * @param string $key
310
     * @return string|boolean
311
     */
312
    protected function filterValidateKey($key)
313
    {
314
        if (!is_string($key)) {
315
            return false;
316
        }
317
        return $key;
318
    }
319
}
320