Completed
Push — master ( f1aa56...cf1aee )
by Nielsen
02:03
created

FileCache::set()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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