Passed
Branch php-5.6 (e849f3)
by compolom
03:47
created

FileCache::clear()   B

Complexity

Conditions 5
Paths 10

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 7
nc 10
nop 0
1
<?php
2
3
namespace Compolomus\Cache;
4
5
use DateInterval;
6
use DateTime;
7
use FilesystemIterator;
8
use LogicException;
9
use Psr\SimpleCache\CacheInterface;
10
use RecursiveDirectoryIterator;
11
use RecursiveIteratorIterator;
12
use RuntimeException;
13
use SplFileObject;
14
use Traversable;
15
16
class FileCache implements CacheInterface
17
{
18
19
    private $cachePath;
20
21
    /**
22
     * FileCache constructor.
23
     * @param null|string $cachePath
24
     */
25
    public function __construct($cachePath = null)
26
    {
27
        $this->cachePath = null !== $cachePath ? $cachePath : sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'cache';
28
        is_dir($this->cachePath) ?: mkdir($this->cachePath, 0775, true);
29
    }
30
31
    /**
32
     * Clear cache directory
33
     *
34
     * @return bool
35
     */
36
    public function clear()
37
    {
38
        $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cachePath,
39
            FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
40
        $iterator = iterator_count($iterator) ? $iterator : [];
41
        foreach ($iterator as $item) {
42
            $item->isDir() && !$item->isLink() ? rmdir($item->getPathname()) : unlink($item->getPathname());
43
        }
44
45
        return rmdir($this->cachePath);
46
    }
47
48
    /**
49
     * @param iterable $keys
50
     * @param mixed $default
51
     * @return array
52
     * @throws LogicException
53
     * @throws RuntimeException
54
     * @throws InvalidArgumentException
55
     */
56
    public function getMultiple($keys, $default = null)
57
    {
58
        $this->isTraversable($keys);
59
        $values = [];
60
        foreach ($keys as $key) {
61
            $values[$key] = $this->get($key) ?: $default;
62
        }
63
64
        return $values;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $values; (array) is incompatible with the return type declared by the interface Psr\SimpleCache\CacheInterface::getMultiple of type Psr\SimpleCache\iterable.

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...
65
    }
66
67
    /**
68
     * @param string $key
69
     * @param null $default
70
     * @return mixed
71
     * @throws LogicException
72
     * @throws RuntimeException
73
     * @throws InvalidArgumentException
74
     */
75
    public function get($key, $default = null)
76
    {
77
        $keyFile = $this->getFilename($key);
78
        $file = file_exists($keyFile) && $this->has($key) ? realpath($keyFile) : null;
79
80
        return $file ? unserialize((new SplFileObject($file))->fread(filesize($file)),
81
            ['allowed_classes' => false]) : $default;
82
    }
83
84
    /**
85
     * @param string $key
86
     * @return string
87
     * @throws InvalidArgumentException
88
     */
89
    private function getFilename($key)
90
    {
91
        $this->validateKey($key);
92
        $sha1 = sha1($key);
93
94
        return $this->cachePath . DIRECTORY_SEPARATOR
95
            . substr($sha1, 0, 2) . DIRECTORY_SEPARATOR
96
            . substr($sha1, 2, 2) . DIRECTORY_SEPARATOR
97
            . $sha1 . '.cache';
98
    }
99
100
    /**
101
     * @param $key
102
     * @return bool
103
     * @throws InvalidArgumentException
104
     */
105
    private function validateKey($key)
106
    {
107
        if (preg_match('#[{}()/\\\@:]#', $key)) {
108
            throw new InvalidArgumentException('Can\'t validate the specified key');
109
        }
110
111
        return true;
112
    }
113
114
    /**
115
     * Isset and life item
116
     *
117
     * @param string $key
118
     * @return bool
119
     * @throws InvalidArgumentException
120
     */
121
    public function has($key)
122
    {
123
        $filename = $this->getFilename($key);
124
        if (!file_exists($filename)) {
125
            return false;
126
        }
127
        if ($this->isLife(filemtime($filename))) {
128
            $this->delete($key);
129
            return false;
130
        }
131
132
        return true;
133
    }
134
135
    /**
136
     * @param $ttl int
137
     * @return bool
138
     */
139
    private function isLife($ttl)
140
    {
141
        return $ttl < time();
142
    }
143
144
    /**
145
     * @param string $key
146
     * @return bool
147
     * @throws InvalidArgumentException
148
     */
149
    public function delete($key)
150
    {
151
        return unlink($this->getFilename($key));
152
    }
153
154
    /**
155
     * @param array $keys
156
     * @param int $ttl
157
     * @throws InvalidArgumentException
158
     * @throws LogicException
159
     * @throws RuntimeException
160
     * @return bool
161
     */
162
    public function setMultiple($keys, $ttl = null)
163
    {
164
        $this->isTraversable($keys);
165
        $status = [];
166
        foreach ($keys as $key => $value) {
167
            $status[$key] = $this->set($key, $value, $ttl);
168
        }
169
170
        return \in_array(true, $status, true) ? false : true;
171
    }
172
173
    /**
174
     * @param string $key
175
     * @param mixed $value
176
     * @param null|int|\DateInterval $ttl
177
     * @throws InvalidArgumentException
178
     * @throws LogicException
179
     * @throws RuntimeException
180
     * @return bool
181
     */
182
    public function set($key, $value, $ttl = null)
183
    {
184
        $file = $this->getFilename($key);
185
        $dir = \dirname($file);
186
        is_dir($dir) ?: mkdir($dir, 0775, true);
187
188
        switch ($ttl) {
189
            case \is_int($ttl):
190
                $ttl += time();
191
                break;
192
            case ($ttl instanceof DateInterval):
193
                $ttl = (new DateTime())->add($ttl)->getTimestamp();
194
                break;
195
            case (null === $ttl):
196
            default:
197
                $ttl = time() + 15;
198
        }
199
200
        $data = new SplFileObject($file, 'wb');
201
        $data->fwrite(serialize($value));
202
        return touch($file, $ttl);
203
    }
204
205
    /**
206
     * @param array $keys
207
     * @return bool
208
     * @throws InvalidArgumentException
209
     */
210
    public function deleteMultiple($keys)
211
    {
212
        $this->isTraversable($keys);
213
        $status = [];
214
        foreach ($keys as $key) {
215
            $status[] = $this->delete($key);
216
        }
217
218
        return \in_array(true, $status, true) ? false : true;
219
    }
220
221
    /**
222
     * @param $array
223
     * @throws InvalidArgumentException
224
     */
225
    private function isTraversable($array)
226
    {
227
        if (!is_array($array) && !$array instanceof Traversable) {
228
            throw new InvalidArgumentException('Array must be either of type array or Traversable');
229
        }
230
    }
231
}
232