1 | <?php |
||||
2 | |||||
3 | namespace TusPhp\Cache; |
||||
4 | |||||
5 | use TusPhp\File; |
||||
6 | use Carbon\Carbon; |
||||
7 | use TusPhp\Config; |
||||
8 | |||||
9 | class FileStore extends AbstractCache |
||||
10 | { |
||||
11 | /** @var string */ |
||||
12 | protected $cacheDir; |
||||
13 | |||||
14 | /** @var string */ |
||||
15 | protected $cacheFile; |
||||
16 | |||||
17 | /** |
||||
18 | * FileStore constructor. |
||||
19 | * |
||||
20 | * @param string|null $cacheDir |
||||
21 | * @param string|null $cacheFile |
||||
22 | */ |
||||
23 | 3 | public function __construct(string $cacheDir = null, string $cacheFile = null) |
|||
24 | { |
||||
25 | 3 | $cacheDir = $cacheDir ?? Config::get('file.dir'); |
|||
26 | 3 | $cacheFile = $cacheFile ?? Config::get('file.name'); |
|||
27 | |||||
28 | 3 | $this->setCacheDir($cacheDir); |
|||
1 ignored issue
–
show
Bug
introduced
by
Loading history...
|
|||||
29 | 3 | $this->setCacheFile($cacheFile); |
|||
1 ignored issue
–
show
It seems like
$cacheFile can also be of type null ; however, parameter $file of TusPhp\Cache\FileStore::setCacheFile() does only seem to accept string , 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
Loading history...
|
|||||
30 | 3 | } |
|||
31 | |||||
32 | /** |
||||
33 | * Set cache dir. |
||||
34 | * |
||||
35 | * @param string $path |
||||
36 | * |
||||
37 | * @return self |
||||
38 | */ |
||||
39 | 2 | public function setCacheDir(string $path) : self |
|||
40 | { |
||||
41 | 2 | $this->cacheDir = $path; |
|||
42 | |||||
43 | 2 | return $this; |
|||
44 | } |
||||
45 | |||||
46 | /** |
||||
47 | * Get cache dir. |
||||
48 | * |
||||
49 | * @return string |
||||
50 | */ |
||||
51 | 1 | public function getCacheDir() : string |
|||
52 | { |
||||
53 | 1 | return $this->cacheDir; |
|||
54 | } |
||||
55 | |||||
56 | /** |
||||
57 | * Set cache file. |
||||
58 | * |
||||
59 | * @param string $file |
||||
60 | * |
||||
61 | * @return self |
||||
62 | */ |
||||
63 | 2 | public function setCacheFile(string $file) : self |
|||
64 | { |
||||
65 | 2 | $this->cacheFile = $file; |
|||
66 | |||||
67 | 2 | return $this; |
|||
68 | } |
||||
69 | |||||
70 | /** |
||||
71 | * Get cache file. |
||||
72 | * |
||||
73 | * @return string |
||||
74 | */ |
||||
75 | 5 | public function getCacheFile() : string |
|||
76 | { |
||||
77 | 5 | return $this->cacheDir . $this->cacheFile; |
|||
78 | } |
||||
79 | |||||
80 | /** |
||||
81 | * Create cache dir if not exists. |
||||
82 | * |
||||
83 | * @return void |
||||
84 | */ |
||||
85 | 2 | protected function createCacheDir() |
|||
86 | { |
||||
87 | 2 | if ( ! file_exists($this->cacheDir)) { |
|||
88 | 2 | mkdir($this->cacheDir); |
|||
89 | } |
||||
90 | 2 | } |
|||
91 | |||||
92 | /** |
||||
93 | * Create a cache file. |
||||
94 | * |
||||
95 | * @return void |
||||
96 | */ |
||||
97 | 2 | protected function createCacheFile() |
|||
98 | { |
||||
99 | 2 | $this->createCacheDir(); |
|||
100 | |||||
101 | 2 | $cacheFilePath = $this->getCacheFile(); |
|||
102 | |||||
103 | 2 | if ( ! file_exists($cacheFilePath)) { |
|||
104 | 2 | touch($cacheFilePath); |
|||
105 | } |
||||
106 | 2 | } |
|||
107 | |||||
108 | /** |
||||
109 | * {@inheritDoc} |
||||
110 | */ |
||||
111 | 8 | public function get(string $key, bool $withExpired = false) |
|||
112 | { |
||||
113 | 8 | $key = $this->getActualCacheKey($key); |
|||
114 | 8 | $contents = $this->getCacheContents(); |
|||
115 | |||||
116 | 8 | if (empty($contents[$key])) { |
|||
117 | 3 | return null; |
|||
118 | } |
||||
119 | |||||
120 | 5 | if ($withExpired) { |
|||
121 | 1 | return $contents[$key]; |
|||
122 | } |
||||
123 | |||||
124 | 5 | return $this->isValid($key) ? $contents[$key] : null; |
|||
125 | } |
||||
126 | |||||
127 | /** |
||||
128 | * Get contents of a file with shared access. |
||||
129 | * |
||||
130 | * @param string $path |
||||
131 | * |
||||
132 | * @return string |
||||
133 | */ |
||||
134 | 2 | public function sharedGet(string $path) : string |
|||
135 | { |
||||
136 | 2 | $contents = ''; |
|||
137 | 2 | $handle = @fopen($path, File::READ_BINARY); |
|||
138 | |||||
139 | 2 | if (false === $handle) { |
|||
140 | 1 | return $contents; |
|||
141 | } |
||||
142 | |||||
143 | try { |
||||
144 | 1 | if (flock($handle, LOCK_SH)) { |
|||
145 | 1 | clearstatcache(true, $path); |
|||
146 | |||||
147 | 1 | $contents = fread($handle, filesize($path) ?: 1); |
|||
148 | |||||
149 | 1 | flock($handle, LOCK_UN); |
|||
150 | } |
||||
151 | 1 | } finally { |
|||
152 | 1 | fclose($handle); |
|||
153 | } |
||||
154 | |||||
155 | 1 | return $contents; |
|||
156 | } |
||||
157 | |||||
158 | /** |
||||
159 | * Write the contents of a file with exclusive lock. |
||||
160 | * |
||||
161 | * @param string $path |
||||
162 | * @param string $contents |
||||
163 | * |
||||
164 | * @return int |
||||
165 | */ |
||||
166 | 3 | public function put(string $path, string $contents) : int |
|||
167 | { |
||||
168 | 3 | return file_put_contents($path, $contents, LOCK_EX); |
|||
169 | } |
||||
170 | |||||
171 | /** |
||||
172 | * {@inheritDoc} |
||||
173 | */ |
||||
174 | 8 | public function set(string $key, $value) |
|||
175 | { |
||||
176 | 8 | $cacheKey = $this->getActualCacheKey($key); |
|||
177 | 8 | $cacheFile = $this->getCacheFile(); |
|||
178 | |||||
179 | 8 | if ( ! file_exists($cacheFile) || ! $this->isValid($cacheKey)) { |
|||
180 | 8 | $this->createCacheFile(); |
|||
181 | } |
||||
182 | |||||
183 | 8 | $contents = json_decode($this->sharedGet($cacheFile), true) ?? []; |
|||
184 | |||||
185 | 8 | if ( ! empty($contents[$cacheKey]) && is_array($value)) { |
|||
186 | 3 | $contents[$cacheKey] = $value + $contents[$cacheKey]; |
|||
187 | } else { |
||||
188 | 8 | $contents[$cacheKey] = $value; |
|||
189 | } |
||||
190 | |||||
191 | 8 | return $this->put($cacheFile, json_encode($contents)); |
|||
192 | } |
||||
193 | |||||
194 | /** |
||||
195 | * {@inheritDoc} |
||||
196 | */ |
||||
197 | 1 | public function delete(string $key) : bool |
|||
198 | { |
||||
199 | 1 | $cacheKey = $this->getActualCacheKey($key); |
|||
200 | 1 | $contents = $this->getCacheContents(); |
|||
201 | |||||
202 | 1 | if (isset($contents[$cacheKey])) { |
|||
203 | 1 | unset($contents[$cacheKey]); |
|||
204 | |||||
205 | 1 | return false !== $this->put($this->getCacheFile(), json_encode($contents)); |
|||
206 | } |
||||
207 | |||||
208 | 1 | return false; |
|||
209 | } |
||||
210 | |||||
211 | /** |
||||
212 | * {@inheritDoc} |
||||
213 | */ |
||||
214 | 2 | public function keys() : array |
|||
215 | { |
||||
216 | 2 | $contents = $this->getCacheContents(); |
|||
217 | |||||
218 | 2 | if (is_array($contents)) { |
|||
219 | 1 | return array_keys($this->getCacheContents()); |
|||
220 | } |
||||
221 | |||||
222 | 1 | return []; |
|||
223 | } |
||||
224 | |||||
225 | /** |
||||
226 | * Check if cache is still valid. |
||||
227 | * |
||||
228 | * @param string $key |
||||
229 | * |
||||
230 | * @return bool |
||||
231 | */ |
||||
232 | 3 | public function isValid(string $key) : bool |
|||
233 | { |
||||
234 | 3 | $key = $this->getActualCacheKey($key); |
|||
235 | 3 | $meta = $this->getCacheContents()[$key] ?? []; |
|||
236 | |||||
237 | 3 | if (empty($meta['expires_at'])) { |
|||
238 | 1 | return false; |
|||
239 | } |
||||
240 | |||||
241 | 2 | return Carbon::now() < Carbon::createFromFormat(self::RFC_7231, $meta['expires_at']); |
|||
242 | } |
||||
243 | |||||
244 | /** |
||||
245 | * Get cache contents. |
||||
246 | * |
||||
247 | * @return array|bool |
||||
248 | */ |
||||
249 | 3 | public function getCacheContents() |
|||
250 | { |
||||
251 | 3 | $cacheFile = $this->getCacheFile(); |
|||
252 | |||||
253 | 3 | if ( ! file_exists($cacheFile)) { |
|||
254 | 1 | return false; |
|||
255 | } |
||||
256 | |||||
257 | 2 | return json_decode($this->sharedGet($cacheFile), true) ?? []; |
|||
258 | } |
||||
259 | |||||
260 | /** |
||||
261 | * Get actual cache key with prefix. |
||||
262 | * |
||||
263 | * @param string $key |
||||
264 | * |
||||
265 | * @return string |
||||
266 | */ |
||||
267 | 1 | public function getActualCacheKey(string $key) : string |
|||
268 | { |
||||
269 | 1 | $prefix = $this->getPrefix(); |
|||
270 | |||||
271 | 1 | if (false === strpos($key, $prefix)) { |
|||
272 | 1 | $key = $prefix . $key; |
|||
273 | } |
||||
274 | |||||
275 | 1 | return $key; |
|||
276 | } |
||||
277 | } |
||||
278 |