FileCache::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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