|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* This file is part of the Phalcon Framework. |
|
5
|
|
|
* |
|
6
|
|
|
* (c) Phalcon Team <[email protected]> |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE.txt |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
declare(strict_types=1); |
|
13
|
|
|
|
|
14
|
|
|
namespace Phalcon\Storage\Adapter; |
|
15
|
|
|
|
|
16
|
|
|
use DateInterval; |
|
17
|
|
|
use FilesystemIterator; |
|
18
|
|
|
use Iterator; |
|
19
|
|
|
use Phalcon\Factory\Exception as FactoryException; |
|
20
|
|
|
use Phalcon\Helper\Arr; |
|
21
|
|
|
use Phalcon\Helper\Str; |
|
22
|
|
|
use Phalcon\Storage\Exception; |
|
23
|
|
|
use Phalcon\Storage\SerializerFactory; |
|
24
|
|
|
use RecursiveDirectoryIterator; |
|
25
|
|
|
use RecursiveIteratorIterator; |
|
26
|
|
|
|
|
27
|
|
|
use function fclose; |
|
28
|
|
|
use function file_exists; |
|
29
|
|
|
use function file_get_contents; |
|
30
|
|
|
use function file_put_contents; |
|
31
|
|
|
use function flock; |
|
32
|
|
|
use function fopen; |
|
33
|
|
|
use function is_array; |
|
34
|
|
|
use function is_dir; |
|
35
|
|
|
use function mkdir; |
|
36
|
|
|
use function restore_error_handler; |
|
37
|
|
|
use function serialize; |
|
38
|
|
|
use function set_error_handler; |
|
39
|
|
|
use function str_replace; |
|
40
|
|
|
use function time; |
|
41
|
|
|
use function unlink; |
|
42
|
|
|
use function unserialize; |
|
43
|
|
|
|
|
44
|
|
|
use const E_NOTICE; |
|
45
|
|
|
use const LOCK_EX; |
|
46
|
|
|
use const LOCK_SH; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Stream adapter |
|
50
|
|
|
* |
|
51
|
|
|
* @property string $storageDir |
|
52
|
|
|
* @property array $options |
|
53
|
|
|
*/ |
|
54
|
|
|
class Stream extends AbstractAdapter |
|
55
|
|
|
{ |
|
56
|
|
|
/** |
|
57
|
|
|
* @var string |
|
58
|
|
|
*/ |
|
59
|
|
|
protected $storageDir = ""; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* @var array |
|
63
|
|
|
*/ |
|
64
|
|
|
protected $options = []; |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Stream constructor. |
|
68
|
|
|
* |
|
69
|
|
|
* @param SerializerFactory $factory |
|
70
|
|
|
* @param array $options = [ |
|
71
|
|
|
* 'storageDir' => '', |
|
72
|
|
|
* 'defaultSerializer' => 'Php', |
|
73
|
|
|
* 'lifetime' => 3600, |
|
74
|
|
|
* 'prefix' => '' |
|
75
|
|
|
* ] |
|
76
|
|
|
* |
|
77
|
|
|
* @throws Exception |
|
78
|
|
|
* @throws FactoryException |
|
79
|
|
|
*/ |
|
80
|
40 |
|
public function __construct( |
|
81
|
|
|
SerializerFactory $factory, |
|
82
|
|
|
array $options = [] |
|
83
|
|
|
) { |
|
84
|
40 |
|
$storageDir = Arr::get($options, "storageDir", ""); |
|
85
|
40 |
|
if (empty($storageDir)) { |
|
86
|
2 |
|
throw new Exception( |
|
87
|
2 |
|
"The 'storageDir' must be specified in the options" |
|
88
|
|
|
); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Lets set some defaults and options here |
|
93
|
|
|
*/ |
|
94
|
38 |
|
$this->storageDir = Str::dirSeparator($storageDir); |
|
95
|
38 |
|
$this->prefix = "ph-strm"; |
|
96
|
38 |
|
$this->options = $options; |
|
97
|
|
|
|
|
98
|
38 |
|
parent::__construct($factory, $options); |
|
99
|
|
|
|
|
100
|
38 |
|
$this->initSerializer(); |
|
101
|
38 |
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* Flushes/clears the cache |
|
105
|
|
|
*/ |
|
106
|
8 |
|
public function clear(): bool |
|
107
|
|
|
{ |
|
108
|
8 |
|
$result = true; |
|
109
|
8 |
|
$directory = Str::dirSeparator($this->storageDir); |
|
110
|
8 |
|
$iterator = $this->getIterator($directory); |
|
111
|
|
|
|
|
112
|
8 |
|
foreach ($iterator as $file) { |
|
113
|
8 |
|
if ($file->isFile() && !unlink($file->getPathName())) { |
|
114
|
|
|
$result = false; |
|
115
|
|
|
} |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
8 |
|
return $result; |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* Decrements a stored number |
|
123
|
|
|
* |
|
124
|
|
|
* @param string $key |
|
125
|
|
|
* @param int $value |
|
126
|
|
|
* |
|
127
|
|
|
* @return bool|int |
|
128
|
|
|
*/ |
|
129
|
2 |
|
public function decrement(string $key, int $value = 1) |
|
130
|
|
|
{ |
|
131
|
2 |
|
if (!$this->has($key)) { |
|
132
|
2 |
|
return false; |
|
|
|
|
|
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
2 |
|
$data = $this->get($key); |
|
136
|
2 |
|
$data = (int) $data - $value; |
|
137
|
|
|
|
|
138
|
2 |
|
return $this->set($key, $data); |
|
|
|
|
|
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* Reads data from the adapter |
|
143
|
|
|
* |
|
144
|
|
|
* @param string $key |
|
145
|
|
|
* |
|
146
|
|
|
* @return bool |
|
147
|
|
|
*/ |
|
148
|
7 |
|
public function delete(string $key): bool |
|
149
|
|
|
{ |
|
150
|
7 |
|
if (!$this->has($key)) { |
|
151
|
4 |
|
return false; |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
5 |
|
$filepath = $this->getFilepath($key); |
|
155
|
|
|
|
|
156
|
5 |
|
return unlink($filepath); |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
|
/** |
|
160
|
|
|
* Reads data from the adapter |
|
161
|
|
|
* |
|
162
|
|
|
* @param string $key |
|
163
|
|
|
* @param mixed|null $defaultValue |
|
164
|
|
|
* |
|
165
|
|
|
* @return mixed|null |
|
166
|
|
|
*/ |
|
167
|
8 |
|
public function get(string $key, $defaultValue = null) |
|
168
|
|
|
{ |
|
169
|
8 |
|
$filepath = $this->getFilepath($key); |
|
170
|
|
|
|
|
171
|
8 |
|
if (!file_exists($filepath)) { |
|
172
|
2 |
|
return $defaultValue; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
8 |
|
$payload = $this->getPayload($filepath); |
|
176
|
|
|
|
|
177
|
8 |
|
if (empty($payload)) { |
|
178
|
2 |
|
return $defaultValue; |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
8 |
|
if ($this->isExpired($payload)) { |
|
182
|
2 |
|
return $defaultValue; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
6 |
|
$content = Arr::get($payload, "content", null); |
|
186
|
|
|
|
|
187
|
6 |
|
return $this->getUnserializedData($content, $defaultValue); |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* Always returns null |
|
192
|
|
|
* |
|
193
|
|
|
* @return null |
|
194
|
|
|
*/ |
|
195
|
2 |
|
public function getAdapter() |
|
196
|
|
|
{ |
|
197
|
2 |
|
return $this->adapter; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* Stores data in the adapter |
|
202
|
|
|
* |
|
203
|
|
|
* @param string $prefix |
|
204
|
|
|
* |
|
205
|
|
|
* @return array |
|
206
|
|
|
*/ |
|
207
|
4 |
|
public function getKeys(string $prefix = ""): array |
|
208
|
|
|
{ |
|
209
|
4 |
|
$files = []; |
|
210
|
4 |
|
$directory = $this->getDir(); |
|
211
|
|
|
|
|
212
|
4 |
|
if (!file_exists($directory)) { |
|
213
|
1 |
|
return []; |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
4 |
|
$iterator = $this->getIterator($directory); |
|
217
|
|
|
|
|
218
|
4 |
|
foreach ($iterator as $file) { |
|
219
|
4 |
|
if ($file->isFile()) { |
|
220
|
4 |
|
$files[] = $this->prefix . $file->getFilename(); |
|
221
|
|
|
} |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
4 |
|
return $this->getFilteredKeys($files, $prefix); |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* Checks if an element exists in the cache and is not expired |
|
229
|
|
|
* |
|
230
|
|
|
* @param string $key |
|
231
|
|
|
* |
|
232
|
|
|
* @return bool |
|
233
|
|
|
*/ |
|
234
|
19 |
|
public function has(string $key): bool |
|
235
|
|
|
{ |
|
236
|
19 |
|
$filepath = $this->getFilepath($key); |
|
237
|
|
|
|
|
238
|
19 |
|
if (!file_exists($filepath)) { |
|
239
|
14 |
|
return false; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
17 |
|
$payload = $this->getPayload($filepath); |
|
243
|
|
|
|
|
244
|
17 |
|
if (empty($payload)) { |
|
245
|
|
|
return false; |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
17 |
|
return !$this->isExpired($payload); |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* Increments a stored number |
|
253
|
|
|
* |
|
254
|
|
|
* @param string $key |
|
255
|
|
|
* @param int $value |
|
256
|
|
|
* |
|
257
|
|
|
* @return bool|int |
|
258
|
|
|
* @throws \Exception |
|
259
|
|
|
*/ |
|
260
|
2 |
|
public function increment(string $key, int $value = 1) |
|
261
|
|
|
{ |
|
262
|
2 |
|
if (!$this->has($key)) { |
|
263
|
2 |
|
return false; |
|
|
|
|
|
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
2 |
|
$data = $this->get($key); |
|
267
|
2 |
|
$data = (int) $data + $value; |
|
268
|
|
|
|
|
269
|
2 |
|
return $this->set($key, $data); |
|
|
|
|
|
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Stores data in the adapter |
|
274
|
|
|
* |
|
275
|
|
|
* @param string $key |
|
276
|
|
|
* @param mixed $value |
|
277
|
|
|
* @param DateInterval|int|null $ttl |
|
278
|
|
|
* |
|
279
|
|
|
* @return bool |
|
280
|
|
|
* @throws \Exception |
|
281
|
|
|
*/ |
|
282
|
24 |
|
public function set(string $key, $value, $ttl = null): bool |
|
283
|
|
|
{ |
|
284
|
|
|
$payload = [ |
|
285
|
24 |
|
"created" => time(), |
|
286
|
24 |
|
"ttl" => $this->getTtl($ttl), |
|
287
|
24 |
|
"content" => $this->getSerializedData($value), |
|
288
|
|
|
]; |
|
289
|
24 |
|
$payload = serialize($payload); |
|
290
|
24 |
|
$directory = $this->getDir($key); |
|
291
|
|
|
|
|
292
|
24 |
|
if (!is_dir($directory)) { |
|
293
|
16 |
|
mkdir($directory, 0777, true); |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
24 |
|
return false !== file_put_contents($directory . $key, $payload, LOCK_EX); |
|
297
|
|
|
} |
|
298
|
|
|
|
|
299
|
|
|
/** |
|
300
|
|
|
* Returns the folder based on the storageDir and the prefix |
|
301
|
|
|
* |
|
302
|
|
|
* @param string $key |
|
303
|
|
|
* |
|
304
|
|
|
* @return string |
|
305
|
|
|
*/ |
|
306
|
26 |
|
private function getDir(string $key = ""): string |
|
307
|
|
|
{ |
|
308
|
26 |
|
$dirPrefix = Str::dirSeparator($this->storageDir . $this->prefix); |
|
309
|
26 |
|
$dirFromFile = Str::dirFromFile( |
|
310
|
26 |
|
str_replace($this->prefix, "", $key) |
|
311
|
|
|
); |
|
312
|
|
|
|
|
313
|
26 |
|
return Str::dirSeparator($dirPrefix . $dirFromFile); |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* Returns the full path to the file |
|
318
|
|
|
* |
|
319
|
|
|
* @param string $key |
|
320
|
|
|
* |
|
321
|
|
|
* @return string |
|
322
|
|
|
*/ |
|
323
|
23 |
|
private function getFilepath(string $key): string |
|
324
|
|
|
{ |
|
325
|
23 |
|
return $this->getDir($key) . str_replace($this->prefix, "", $key); |
|
326
|
|
|
} |
|
327
|
|
|
|
|
328
|
|
|
/** |
|
329
|
|
|
* Returns an iterator for the directory contents |
|
330
|
|
|
* |
|
331
|
|
|
* @param string $dir |
|
332
|
|
|
* |
|
333
|
|
|
* @return Iterator |
|
334
|
|
|
*/ |
|
335
|
8 |
|
private function getIterator(string $dir): Iterator |
|
336
|
|
|
{ |
|
337
|
8 |
|
return new RecursiveIteratorIterator( |
|
338
|
8 |
|
new RecursiveDirectoryIterator( |
|
339
|
8 |
|
$dir, |
|
340
|
8 |
|
FilesystemIterator::SKIP_DOTS |
|
341
|
|
|
), |
|
342
|
8 |
|
RecursiveIteratorIterator::CHILD_FIRST |
|
343
|
|
|
); |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
/** |
|
347
|
|
|
* Gets the file contents and returns an array or an error if something |
|
348
|
|
|
* went wrong |
|
349
|
|
|
* |
|
350
|
|
|
* @param string $filepath |
|
351
|
|
|
* |
|
352
|
|
|
* @return array |
|
353
|
|
|
*/ |
|
354
|
21 |
|
private function getPayload(string $filepath): array |
|
355
|
|
|
{ |
|
356
|
21 |
|
$warning = false; |
|
357
|
21 |
|
$payload = false; |
|
358
|
21 |
|
$pointer = fopen($filepath, 'r'); |
|
359
|
|
|
|
|
360
|
|
|
/** |
|
361
|
|
|
* Cannot open file |
|
362
|
|
|
*/ |
|
363
|
21 |
|
if (false === $pointer) { |
|
364
|
|
|
return []; |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
21 |
|
if (flock($pointer, LOCK_SH)) { |
|
368
|
21 |
|
$payload = file_get_contents($filepath); |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
21 |
|
fclose($pointer); |
|
372
|
|
|
|
|
373
|
|
|
/** |
|
374
|
|
|
* No results |
|
375
|
|
|
*/ |
|
376
|
21 |
|
if (false === $payload) { |
|
377
|
|
|
return []; |
|
378
|
|
|
} |
|
379
|
|
|
|
|
380
|
21 |
|
set_error_handler( |
|
381
|
|
|
function () use (&$warning) { |
|
382
|
2 |
|
$warning = true; |
|
383
|
21 |
|
}, |
|
384
|
21 |
|
E_NOTICE |
|
385
|
|
|
); |
|
386
|
|
|
|
|
387
|
21 |
|
$payload = unserialize($payload); |
|
388
|
|
|
|
|
389
|
21 |
|
restore_error_handler(); |
|
390
|
|
|
|
|
391
|
21 |
|
if ($warning || !is_array($payload)) { |
|
392
|
2 |
|
return []; |
|
393
|
|
|
} |
|
394
|
|
|
|
|
395
|
21 |
|
return $payload; |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
/** |
|
399
|
|
|
* Returns if the cache has expired for this item or not |
|
400
|
|
|
* |
|
401
|
|
|
* @param array $payload |
|
402
|
|
|
* |
|
403
|
|
|
* @return bool |
|
404
|
|
|
*/ |
|
405
|
21 |
|
private function isExpired(array $payload): bool |
|
406
|
|
|
{ |
|
407
|
21 |
|
$created = Arr::get($payload, "created", time()); |
|
408
|
21 |
|
$ttl = Arr::get($payload, "ttl", 3600); |
|
409
|
|
|
|
|
410
|
21 |
|
return ($created + $ttl) < time(); |
|
411
|
|
|
} |
|
412
|
|
|
} |
|
413
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.