Stream::getAdapter()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1.037

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 3
cp 0.6667
crap 1.037
rs 10
c 1
b 0
f 0
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 80
    public function __construct(
81
        SerializerFactory $factory,
82
        array $options = []
83
    ) {
84 80
        $storageDir = Arr::get($options, "storageDir", "");
85 80
        if (empty($storageDir)) {
86 4
            throw new Exception(
87 4
                "The 'storageDir' must be specified in the options"
88
            );
89
        }
90
91
        /**
92
         * Lets set some defaults and options here
93
         */
94 76
        $this->storageDir = Str::dirSeparator($storageDir);
95 76
        $this->prefix     = "ph-strm";
96 76
        $this->options    = $options;
97
98 76
        parent::__construct($factory, $options);
99
100 76
        $this->initSerializer();
101 76
    }
102
103
    /**
104
     * Flushes/clears the cache
105
     */
106 16
    public function clear(): bool
107
    {
108 16
        $result    = true;
109 16
        $directory = Str::dirSeparator($this->storageDir);
110 16
        $iterator  = $this->getIterator($directory);
111
112 16
        foreach ($iterator as $file) {
113 16
            if ($file->isFile() && !unlink($file->getPathName())) {
114
                $result = false;
115
            }
116
        }
117
118 16
        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 4
    public function decrement(string $key, int $value = 1)
130
    {
131 4
        if (!$this->has($key)) {
132 4
            return false;
133
        }
134
135 4
        $data = $this->get($key);
136 4
        $data = (int) $data - $value;
137
138 4
        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 14
    public function delete(string $key): bool
149
    {
150 14
        if (!$this->has($key)) {
151 8
            return false;
152
        }
153
154 10
        $filepath = $this->getFilepath($key);
155
156 10
        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 16
    public function get(string $key, $defaultValue = null)
168
    {
169 16
        $filepath = $this->getFilepath($key);
170
171 16
        if (!file_exists($filepath)) {
172 4
            return $defaultValue;
173
        }
174
175 16
        $payload = $this->getPayload($filepath);
176
177 16
        if (empty($payload)) {
178 4
            return $defaultValue;
179
        }
180
181 16
        if ($this->isExpired($payload)) {
182 4
            return $defaultValue;
183
        }
184
185 12
        $content = Arr::get($payload, "content", null);
186
187 12
        return $this->getUnserializedData($content, $defaultValue);
188
    }
189
190
    /**
191
     * Always returns null
192
     *
193
     * @return null
194
     */
195 4
    public function getAdapter()
196
    {
197 4
        return $this->adapter;
198
    }
199
200
    /**
201
     * Stores data in the adapter
202
     *
203
     * @param string $prefix
204
     *
205
     * @return array
206
     */
207 8
    public function getKeys(string $prefix = ""): array
208
    {
209 8
        $files     = [];
210 8
        $directory = $this->getDir();
211
212 8
        if (!file_exists($directory)) {
213 2
            return [];
214
        }
215
216 8
        $iterator = $this->getIterator($directory);
217
218 8
        foreach ($iterator as $file) {
219 8
            if ($file->isFile()) {
220 8
                $files[] = $this->prefix . $file->getFilename();
221
            }
222
        }
223
224 8
        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 38
    public function has(string $key): bool
235
    {
236 38
        $filepath = $this->getFilepath($key);
237
238 38
        if (!file_exists($filepath)) {
239 28
            return false;
240
        }
241
242 34
        $payload = $this->getPayload($filepath);
243
244 34
        if (empty($payload)) {
245
            return false;
246
        }
247
248 34
        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 4
    public function increment(string $key, int $value = 1)
261
    {
262 4
        if (!$this->has($key)) {
263 4
            return false;
264
        }
265
266 4
        $data = $this->get($key);
267 4
        $data = (int) $data + $value;
268
269 4
        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 48
    public function set(string $key, $value, $ttl = null): bool
283
    {
284
        $payload   = [
285 48
            "created" => time(),
286 48
            "ttl"     => $this->getTtl($ttl),
287 48
            "content" => $this->getSerializedData($value),
288
        ];
289 48
        $payload   = serialize($payload);
290 48
        $directory = $this->getDir($key);
291
292 48
        if (!is_dir($directory)) {
293 32
            mkdir($directory, 0777, true);
294
        }
295
296 48
        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 52
    private function getDir(string $key = ""): string
307
    {
308 52
        $dirPrefix   = Str::dirSeparator($this->storageDir . $this->prefix);
309 52
        $dirFromFile = Str::dirFromFile(
310 52
            str_replace($this->prefix, "", $key)
311
        );
312
313 52
        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 46
    private function getFilepath(string $key): string
324
    {
325 46
        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 16
    private function getIterator(string $dir): Iterator
336
    {
337 16
        return new RecursiveIteratorIterator(
338 16
            new RecursiveDirectoryIterator(
339 16
                $dir,
340 16
                FilesystemIterator::SKIP_DOTS
341
            ),
342 16
            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 42
    private function getPayload(string $filepath): array
355
    {
356 42
        $warning = false;
357 42
        $payload = false;
358 42
        $pointer = fopen($filepath, 'r');
359
360
        /**
361
         * Cannot open file
362
         */
363 42
        if (false === $pointer) {
364
            return [];
365
        }
366
367 42
        if (flock($pointer, LOCK_SH)) {
368 42
            $payload = file_get_contents($filepath);
369
        }
370
371 42
        fclose($pointer);
372
373
        /**
374
         * No results
375
         */
376 42
        if (false === $payload) {
377
            return [];
378
        }
379
380 42
        set_error_handler(
381
            function () use (&$warning) {
382 4
                $warning = true;
383 42
            },
384 42
            E_NOTICE
385
        );
386
387 42
        $payload = unserialize($payload);
388
389 42
        restore_error_handler();
390
391 42
        if ($warning || !is_array($payload)) {
392 4
            return [];
393
        }
394
395 42
        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 42
    private function isExpired(array $payload): bool
406
    {
407 42
        $created = Arr::get($payload, "created", time());
408 42
        $ttl     = Arr::get($payload, "ttl", 3600);
409
410 42
        return ($created + $ttl) < time();
411
    }
412
}
413