FileCacheDriver::getFilename()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 2
nc 2
nop 2
1
<?php
2
3
/*
4
 * Created by Fernando Robledo <[email protected]>.
5
 */
6
7
namespace Overdesign\PsrCache;
8
9
use Psr\Cache\CacheItemInterface;
10
use Psr\Cache\CacheItemPoolInterface;
11
12
class FileCacheDriver implements CacheItemPoolInterface
13
{
14
    /** @var string */
15
    protected $path;
16
    /** @var CacheItem[] */
17
    protected $deferred = array();
18
19
    /**
20
     * FileCacheDriver constructor.
21
     *
22
     * @param string $path
23
     */
24
    public function __construct($path = __DIR__)
25
    {
26
        $this->path = substr($path, strlen($path) - 1, 1) === DIRECTORY_SEPARATOR ? $path : $path . DIRECTORY_SEPARATOR;
27
    }
28
29
    /**
30
     * Returns current cache folder path
31
     *
32
     * @return string
33
     */
34
    public function getPath()
35
    {
36
        return $this->path;
37
    }
38
39
    /**
40
     * @param string $key
41
     *
42
     * @return string hashed key
43
     * @throws InvalidArgumentException
44
     */
45
    private function checkKey($key)
46
    {
47
        if (!is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
48
            throw new InvalidArgumentException('The given key must be a string.');
49
        } elseif (!preg_match('/^[a-zA-Z\d\.\_]+$/', $key)) {
50
            throw new InvalidArgumentException(sprintf('The given key %s contains invalid characters.', $key));
51
        }
52
53
        return $key;
54
    }
55
56
    /**
57
     * @param string $key
58
     * @param bool $hash
59
     *
60
     * @return string
61
     */
62
    private function getFilename($key, $hash = true)
63
    {
64
        $key = $hash ? sha1($key) : $key;
65
        return $this->path . "cachepool-{$key}.php";
66
    }
67
68
    /**
69
     * Returns a Cache Item representing the specified key.
70
     *
71
     * This method must always return a CacheItemInterface object, even in case of
72
     * a cache miss. It MUST NOT return null.
73
     *
74
     * @param string $key
75
     *   The key for which to return the corresponding Cache Item.
76
     *
77
     * @throws InvalidArgumentException
78
     *   If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
79
     *   MUST be thrown.
80
     *
81
     * @return CacheItemInterface
82
     *   The corresponding Cache Item.
83
     */
84
    public function getItem($key)
85
    {
86
        $key = $this->checkKey($key);
87
88
        if (array_key_exists($key, $this->deferred)) {
89
            return clone $this->deferred[$key];
90
        }
91
92
        $file = $this->getFilename($key);
93
94
        return $this->readCacheFile($file) ?: new CacheItem($key);
95
    }
96
97
    /**
98
     * @param string $file filename
99
     *
100
     * @return false|CacheItem
101
     * @throws InvalidArgumentException
102
     */
103
    private function readCacheFile($file)
104
    {
105
        if (!is_file($file) || !is_readable($file)) {
106
            return false;
107
        }
108
109
        $item = file_get_contents($file);
110
        $item = $item === false ? false : unserialize($item);
111
112
        if ($item === false || !$item instanceof CacheItem) {
113
            return false;
114
        }
115
116
        if ($item->isHit() === false) {
117
            $this->deleteItem($item->getKey()); // clear expired
118
        }
119
120
        return $item;
121
    }
122
123
    /**
124
     * Gets a cache file by key hash
125
     *
126
     * @param string $hash sha1 key hash
127
     *
128
     * @return CacheItem
129
     *
130
     * @throws InvalidArgumentException
131
     */
132
    private function getItemByHash($hash)
133
    {
134
        $file = $this->getFilename($hash, false);
135
136
        return $this->readCacheFile($file) ?: new CacheItem($key);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $key seems to be never defined.
Loading history...
137
    }
138
139
    /**
140
     * Returns a traversable set of cache items.
141
     *
142
     * @param string[] $keys
143
     *   An indexed array of keys of items to retrieve.
144
     *
145
     * @throws InvalidArgumentException
146
     *   If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
147
     *   MUST be thrown.
148
     *
149
     * @return array|\Traversable
150
     *   A traversable collection of Cache Items keyed by the cache keys of
151
     *   each item. A Cache item will be returned for each key, even if that
152
     *   key is not found. However, if no keys are specified then an empty
153
     *   traversable MUST be returned instead.
154
     */
155
    public function getItems(array $keys = array())
156
    {
157
        $collection = array();
158
159
        foreach ($keys as $key) {
160
            $collection[$key] = $this->getItem($key);
161
        }
162
163
        return $collection;
164
    }
165
166
    /**
167
     * Confirms if the cache contains specified cache item.
168
     *
169
     * Note: This method MAY avoid retrieving the cached value for performance reasons.
170
     * This could result in a race condition with CacheItemInterface::get(). To avoid
171
     * such situation use CacheItemInterface::isHit() instead.
172
     *
173
     * @param string $key
174
     *   The key for which to check existence.
175
     *
176
     * @throws InvalidArgumentException
177
     *   If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
178
     *   MUST be thrown.
179
     *
180
     * @return bool
181
     *   True if item exists in the cache, false otherwise.
182
     */
183
    public function hasItem($key)
184
    {
185
        return $this->getItem($key)->isHit();
186
    }
187
188
    /**
189
     * Deletes all items in the pool.
190
     *
191
     * @return bool
192
     *   True if the pool was successfully cleared. False if there was an error.
193
     */
194
    public function clear()
195
    {
196
        $this->deferred = array();
197
198
        $files  = glob($this->path . 'cachepool-*.php', GLOB_NOSORT);
199
        $result = true;
200
201
        foreach ($files as $file) {
202
            if (is_file($file)) {
203
                $result = @unlink($file) && $result;
204
            }
205
        }
206
207
        return $result;
208
    }
209
210
    /**
211
     * Removes the item from the pool.
212
     *
213
     * @param string $key
214
     *   The key to delete.
215
     *
216
     * @throws InvalidArgumentException
217
     *   If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
218
     *   MUST be thrown.
219
     *
220
     * @return bool
221
     *   True if the item was successfully removed. False if there was an error.
222
     */
223
    public function deleteItem($key)
224
    {
225
        $key = $this->checkKey($key);
226
227
        if (array_key_exists($key, $this->deferred)) {
228
            unset($this->deferred[$key]);
229
        }
230
231
        $file = $this->getFilename($key);
232
233
        return file_exists($file) ? @unlink($file) : true;
234
    }
235
236
    /**
237
     * Removes multiple items from the pool.
238
     *
239
     * @param string[] $keys
240
     *   An array of keys that should be removed from the pool.
241
     *
242
     * @throws InvalidArgumentException
243
     *   If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
244
     *   MUST be thrown.
245
     *
246
     * @return bool
247
     *   True if the items were successfully removed. False if there was an error.
248
     */
249
    public function deleteItems(array $keys)
250
    {
251
        $result = true;
252
253
        foreach ($keys as $key) {
254
            $result = $this->deleteItem($key) && $result;
255
        }
256
257
        return $result;
258
    }
259
260
    /**
261
     * Persists a cache item immediately.
262
     *
263
     * @param CacheItemInterface $item
264
     *   The cache item to save.
265
     *
266
     * @return bool True if the item was successfully persisted. False if there was an error.
267
     * True if the item was successfully persisted. False if there was an error.
268
     *
269
     * @throws CacheException
270
     */
271
    public function save(CacheItemInterface $item)
272
    {
273
        $file = $this->getFilename($item->getKey());
274
275
        if (false === file_put_contents($file, serialize($item))) {
276
            throw new CacheException(sprintf('Cant write to cache file %s', $file), CacheException::ERROR_CANT_WRITE);
277
        }
278
279
        return true;
280
    }
281
282
    /**
283
     * Sets a cache item to be persisted later.
284
     *
285
     * @param CacheItemInterface $item
286
     *   The cache item to save.
287
     *
288
     * @return bool
289
     *   False if the item could not be queued or if a commit was attempted and failed. True otherwise.
290
     */
291
    public function saveDeferred(CacheItemInterface $item)
292
    {
293
        $this->deferred[$item->getKey()] = $item;
294
295
        return true;
296
    }
297
298
    /**
299
     * Persists any deferred cache items.
300
     *
301
     * @return bool True if all not-yet-saved items were successfully saved or there were none. False otherwise.
302
     * True if all not-yet-saved items were successfully saved or there were none. False otherwise.
303
     *
304
     * @throws CacheException
305
     */
306
    public function commit()
307
    {
308
        $allSaved = true;
309
310
        foreach ($this->deferred as $item) {
311
            $allSaved = $this->save($item) && $allSaved;
312
            unset($this->deferred[$item->getKey()]);
313
        }
314
315
        return $allSaved;
316
    }
317
318
    /**
319
     * Deletes all the expired items in the pool.
320
     *
321
     * @return bool
322
     *   True if the pool was successfully cleared. False if there was an error.
323
     */
324
    public function clearExpired()
325
    {
326
        $regex  = '/cachepool-(?P<hash>[0-9a-f]+)\.php$/';
327
        $files  = glob($this->path . 'cachepool-*.php', GLOB_NOSORT);
328
        $result = true;
329
330
        foreach ($files as $file) {
331
            if (preg_match($regex, $file, $hash) === false) {
332
                continue;
333
            }
334
335
            try {
336
                $this->getItemByHash($hash['hash']); // Get item auto clears expired items
337
            } catch (InvalidArgumentException $e) {
338
                $result = false;
339
            }
340
        }
341
342
        return $result;
343
    }
344
345
    /**
346
     * Cache pool garbage collector, deletes all cache files and optional empty directories in the current cache path
347
     * It can do a recursive search over the main directory, the maximum deep for the recursive can be specified
348
     *
349
     * @param bool $checkExpired if true only deletes the items that are expired else deletes all cached files
350
     * @param bool $recursive true for doing a recursive gc over the main path
351
     * @param bool $deleteEmpty true for deleting empty directories inside the path
352
     * @param int $depth maximum search depth
353
     *
354
     * @return bool True if all the items where deleted
355
     *
356
     * @throws InvalidArgumentException
357
     */
358
    public function gc($checkExpired = true, $recursive = false, $deleteEmpty = true, $depth = 1)
359
    {
360
        $recursive = $recursive && $depth > 0;
361
        $result    = $checkExpired ? $this->clearExpired() : $this->clear();
362
363
        if (!$recursive) {
364
            return $result;
365
        }
366
367
        $dirs = glob($this->path . '*', GLOB_ONLYDIR | GLOB_NOSORT);
368
369
        foreach ($dirs as $dir) {
370
            $pool = new self($dir);
371
            $result = $result && $pool->gc($recursive, $deleteEmpty, --$depth);
0 ignored issues
show
Bug introduced by
--$depth of type integer is incompatible with the type boolean expected by parameter $deleteEmpty of Overdesign\PsrCache\FileCacheDriver::gc(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
            $result = $result && $pool->gc($recursive, $deleteEmpty, /** @scrutinizer ignore-type */ --$depth);
Loading history...
372
373
            if ($deleteEmpty && count(glob($dir . DIRECTORY_SEPARATOR . '*', GLOB_NOSORT)) === 0) {
0 ignored issues
show
Bug introduced by
It seems like glob($dir . Overdesign\P...n\PsrCache\GLOB_NOSORT) can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

373
            if ($deleteEmpty && count(/** @scrutinizer ignore-type */ glob($dir . DIRECTORY_SEPARATOR . '*', GLOB_NOSORT)) === 0) {
Loading history...
374
                rmdir($dir);
375
            }
376
        }
377
378
        return $result;
379
    }
380
381
    /**
382
     * Save deferred items before destruct
383
     */
384
    public function __destruct()
385
    {
386
        $this->commit();
387
    }
388
}
389