Completed
Push — master ( f083aa...b6d504 )
by Nielsen
01:53
created

FileCache::clear()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 1
crap 4
1
<?php
2
3
namespace Ezcache\Cache;
4
5
use DateInterval;
6
use DateTime;
7
use Exception;
8
9
/**
10
 * Class FileCache
11
 *
12
 * @package Ezcache\Cache
13
 * @author nielsengoncalves
14
 */
15
class FileCache implements CacheInterface {
16
17
    use CacheUtilsTrait;
18
19
    const JSON_FORMAT = '.json';
20
21
    private $cacheDirectory;
22
    private $ttl;
23
    private $lastError = [];
24
    private $namespace;
25
26
    /**
27
     * FileCache constructor.
28
     *
29
     * @param string $directory the directory where cache operations will happen.
30
     * @param int $ttl the cache life time in seconds (0 = Forever).
31
     * @param string $namespace the cache namespace.
32
     */
33 6
    public function __construct(string $directory, int $ttl = 0, string $namespace = null) {
34 6
        $this->setCacheDirectory($directory);
35 6
        $this->ttl = $ttl;
36 6
        if ($namespace !== null) {
37 1
            $this->setNamespace($namespace);
38
        }
39 6
    }
40
41
    /**
42
     * Set the cache namespace.
43
     *
44
     * @param string $namespace the cache namespace.
45
     */
46 1
    public function setNamespace(string $namespace) : bool {
47 1
        $namespace = trim($namespace, '//, ');
48 1
        $dir = $this->getBasePath($namespace);
49
50 1
        if ((is_dir($dir) && is_writable($dir)) || mkdir($dir, 0755)) {
51 1
            $this->namespace = $namespace;
52 1
            return true;
53
        }
54
55
        return false;
56
    }
57
58
    /**
59
     * Set a value to a key on cache.
60
     *
61
     * @param string $key the key to be setted.
62
     * @param mixed $value the correspondent value of that cache key.
63
     * @param int|null $ttl the cache life time in seconds (If no value passed will use the default value).
64
     *
65
     * @return bool true on success or false on failure.
66
     */
67 5
    public function set(string $key, $value, int $ttl = null) : bool {
68
69 5
        $filePath = $this->getFilePath($key);
70
71
        // Uses the ttl passed in the function call or uses the default value
72 5
        $ttl      = $ttl ?? $this->ttl;
73 5
        $interval = new DateInterval(empty($ttl) ? 'P100Y' : "PT{$ttl}S");
74 5
        $date     = new DateTime();
75
76 5
        $fileData = json_encode([
77 5
            'value'      => serialize($value),
78 5
            'created_at' => $date->format('Y-m-d H:i:s'),
79 5
            'expires_at' => $date->add($interval)->format('Y-m-d H:i:s')
80
        ]);
81
82 5
        return file_put_contents($filePath, $fileData);
83
    }
84
85
    /**
86
     * Return the valid cache value stored with the given key.
87
     *
88
     * @param string $key the cache key to be found.
89
     *
90
     * @return mixed the data found.
91
     */
92 4
    public function get(string $key) {
93
94 4
        $fileData = $this->getFileData($key);
95
96 4
        if (empty($fileData) || (date('Y-m-d H:i:s') > $fileData['expires_at'])) {
97 3
            return null;
98
        }
99
100 4
        return unserialize($fileData['value']);
101
    }
102
103
    /**
104
     * Delete cache especified by key.
105
     *
106
     * @param string $key the cache key to be deleted.
107
     *
108
     * @return bool true on success or false on failure.
109
     */
110 1
    public function delete(string $key) : bool {
111 1
        return unlink($this->getFilePath($key));
112
    }
113
114
    /**
115
     * Check if given key exists and is valid on cache.
116
     *
117
     * @param string $key the cache key to be verified.
118
     * @param bool $isValid if set to true the function will verify if it is valid (not expired).
119
     *
120
     * @return bool true if exists false otherwise.
121
     */
122 1
    public function exists(string $key, bool $isValid = false) : bool {
123 1
        $fileData = $this->getFileData($key);
124 1
        return !(empty($fileData) || ($isValid && date('Y-m-d H:i:s') > $fileData['expires_at']));
125
    }
126
127
    /**
128
     * Renew the cache expiration time.
129
     *
130
     * @param string $key the cache key to be renewed.
131
     * @param int|null $ttl extra time to live in seconds.
132
     *
133
     * @return bool true on success or false on failure.
134
     */
135 1
    public function renew(string $key, int $ttl = null) : bool {
136
137 1
        $filePath = $this->getFilePath($key);
138 1
        $fileData = $this->getFileData($key);
139
140 1
        if (empty($fileData)) {
141
            return false;
142
        }
143
144 1
        $ttl = $ttl ?? $this->ttl;
145 1
        $interval = new DateInterval(empty($ttl) ? 'P100Y' : "PT{$ttl}S");
146 1
        $fileData['expires_at'] = (new DateTime())->add($interval)->format('Y-m-d H:i:s');
147 1
        file_put_contents($filePath, json_encode($fileData));
148 1
        return true;
149
    }
150
151
    /**
152
     * Clear all cache files at directory.
153
     *
154
     * @param string|null $namespace the cache namespace.
155
     *
156
     * @return bool true on success or false on failure.
157
     */
158 1
    public function clear(string $namespace = null) : bool {
159
160 1
        $dir = $this->getBasePath($namespace);
161 1
        $files = $this->streamSafeGlob($dir, '*.cache' . self::JSON_FORMAT);
162 1
        foreach ($files as $file) {
163 1
            if (is_file($file)) {
164 1
                if (!unlink($file)) {
165 1
                    return false;
166
                }
167
            }
168
        }
169 1
        return true;
170
    }
171
172
    /**
173
     * Set the directory where cache operations will happen.
174
     *
175
     * @param string $cacheDirectory the directory where cache operations will happen.
176
     *
177
     * @return bool true on success or false on failure.
178
     */
179 6
    public function setCacheDirectory(string $cacheDirectory) : bool {
180 6
        $this->cacheDirectory = null;
181 6
        $cacheDirectory = rtrim($cacheDirectory, '//, ') . '/';
182 6
        if (!((file_exists($cacheDirectory) && is_writable($cacheDirectory)) || mkdir($cacheDirectory, 0755, true))) {
183
            $this->setLastError(new Exception("Failed to use $cacheDirectory as cache directory."));
184
            return false;
185
        }
186 6
        $this->cacheDirectory = $cacheDirectory;
187 6
        return true;
188
    }
189
190
    /**
191
     * Return the last error occurred.
192
     *
193
     * @return array the array with the last error data.
194
     */
195
    public function getLastError() : array {
196
        return $this->lastError;
197
    }
198
199
    /**
200
     * Return the path where the cache file should be located.
201
     *
202
     * @param string $key the cache key
203
     *
204
     * @return string the file path
205
     */
206 5
    private function getFilePath(string $key) : string {
207 5
        return $this->getBasePath($this->namespace) . $key . ".cache" . self::JSON_FORMAT;
208
    }
209
210
    /**
211
     * Return the base path where the cache files should be located.
212
     *
213
     * @return string the file path
214
     */
215 6
    private function getBasePath(string $namespace = null) : string {
216 6
        return $this->cacheDirectory . (!empty($namespace) ? $namespace . DIRECTORY_SEPARATOR : '');
217
    }
218
219
    /**
220
     * Set the last error that ocurred using the lib
221
     *
222
     * @param Exception $ex the exception
223
     */
224
    private function setLastError(Exception $ex) {
225
        $this->lastError['code']    = $ex->getCode();
226
        $this->lastError['message'] = $ex->getMessage();
227
        $this->lastError['trace']   = $ex->getTraceAsString();
228
    }
229
230
    /**
231
     * Get the file data
232
     *
233
     * @param string $key the cache key
234
     *
235
     * @return array with the file data or empty array when no data found
236
     */
237 5
    private function getFileData(string $key) : array {
238 5
        $filePath = $this->getFilePath($key);
239 5
        $contents = @file_get_contents($filePath);
240
241 5
        if (!$contents) {
242 2
            return [];
243 5
        } else if (($data = json_decode($contents, true)) === null && json_last_error() != JSON_ERROR_NONE) {
244
            $this->setLastError(new CacheException(sprintf("Failed to decode the %s data.", $key)));
245
            return [];
246
        }
247
248 5
        return $data;
249
    }
250
}