Completed
Push — orm-dm-info ( abf332 )
by Nikolaos
02:10
created

Stream::getPayload()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7.3329

Importance

Changes 0
Metric Value
dl 0
loc 43
ccs 18
cts 27
cp 0.6667
rs 8.6097
c 0
b 0
f 0
cc 6
nc 7
nop 1
crap 7.3329
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Phalcon\Storage\Adapter\...terInterface::decrement of type integer.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
133
        }
134
135 2
        $data = $this->get($key);
136 2
        $data = (int) $data - $value;
137
138 2
        return $this->set($key, $data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->set($key, $data); (boolean) is incompatible with the return type declared by the interface Phalcon\Storage\Adapter\...terInterface::decrement of type integer.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Phalcon\Storage\Adapter\...terInterface::increment of type integer.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
264
        }
265
266 2
        $data = $this->get($key);
267 2
        $data = (int) $data + $value;
268
269 2
        return $this->set($key, $data);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->set($key, $data); (boolean) is incompatible with the return type declared by the interface Phalcon\Storage\Adapter\...terInterface::increment of type integer.

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:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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