FilesystemAdapter::keys()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace IQParts\Cache\Adapter;
4
5
use IQParts\Cache\Serializer\SerializerInterface;
6
7
class FilesystemAdapter implements CacheAdapterInterface
8
{
9
    /**
10
     * @var string
11
     */
12
    private $directory;
13
    /**
14
     * @var null|int
15
     */
16
    private $chmod;
17
    /**
18
     * @var string
19
     */
20
    private $directorySeparator;
21
    /**
22
     * @var int|null
23
     */
24
    private $ttl;
25
    /**
26
     * @var array
27
     */
28
    private $timeToLive;
29
    /**
30
     * @var SerializerInterface
31
     */
32
    private $serializer;
33
    /**
34
     * @var array
35
     */
36
    private $index;
37
    /**
38
     * @var string
39
     */
40
    private $indexLocation;
41
    /**
42
     * @var string
43
     */
44
    private $ttlLocation;
45
46
    /**
47
     * FilesystemAdapter constructor.
48
     * @param string $directory
49
     * @param SerializerInterface $serializer
50
     * @param int|null $chmod
51
     * @param int|null $ttl
52
     * @param string|null $directorySeparator
53
     */
54
    public function __construct(
55
        string $directory,
56
        SerializerInterface $serializer,
57
        int $chmod = null,
58
        int $ttl = null,
59
        string $directorySeparator = null
60
    )
61
    {
62
        $this->indexLocation = $directory . '/' . md5($directory) . 'index';
63
        $this->ttlLocation = $directory . '/' . md5($directory) . 'ttl';
64
        $this->directory = $directory;
65
        $this->serializer = $serializer;
66
        $this->directorySeparator = $directorySeparator;
67
        $this->chmod = $chmod;
68
        $this->ttl = $ttl;
69
70
        if (file_exists($this->indexLocation)) {
71
            $this->index = json_decode(file_get_contents($this->indexLocation), true);
72
        } else {
73
            $this->index = [];
74
        }
75
76
        if (file_exists($this->ttlLocation)) {
77
            $this->timeToLive = json_decode(file_get_contents($this->ttlLocation), true);
78
        } else {
79
            $this->timeToLive = [];
80
        }
81
    }
82
83
    /**
84
     * @param string $key
85
     * @return mixed
86
     */
87
    public function get(string $key)
88
    {
89
        $file = $this->getFilename($key);
90
        if ($this->exists($file)) {
91
            if (isset($this->timeToLive[$key])) {
92
                if ($this->ttl($key) > 0) {
93
                    return $this->serializer->deserialize(file_get_contents($file));
94
                }
95
            } else {
96
                return $this->serializer->deserialize(file_get_contents($file));
97
            }
98
99
        }
100
        return null;
101
    }
102
103
    /**
104
     * @param string $key
105
     * @param $value
106
     * @param int|null $ttl
107
     * @return void
108
     */
109
    public function set(string $key, $value, int $ttl = null): void
110
    {
111
        $file = $this->getFilename($key);
112
        file_put_contents($file, $this->serializer->serialize($value));
113
        if ($this->chmod !== null) {
114
            chmod($file, $this->chmod);
115
        }
116
        if ($ttl !== null) {
117
            $this->timeToLive[$key] = $ttl;
118
        } else if ($this->ttl !== null) {
119
            $this->timeToLive[$key] = $this->ttl;
120
        }
121
122
        $this->index[$key] = $file;
123
        $this->saveIndex();
124
    }
125
126
    /**
127
     * @param string $key
128
     */
129
    public function delete(string $key)
130
    {
131
        if (strpos($key, '*') !== false) {
132
            $this->deleteGlob($key);
133
            $this->saveIndex();
134
        } else {
135
            $file = $this->getFilename($key);
136
            if ($this->exists($file)) {
137
                unlink($file);
138
            }
139
            unset($this->index[$key], $this->timeToLive[$key]);
140
            $this->saveIndex();
141
        }
142
    }
143
144
    /**
145
     * @param string $key
146
     * @return mixed
147
     */
148
    public function keys($key = '*')
149
    {
150
        $matches = [];
151
        foreach ($this->index as $name => $index) {
152
            if (fnmatch($key, $name)) {
153
                $matches[] = $name;
154
            }
155
        }
156
        return $matches;
157
    }
158
159
    /**
160
     * @param $key
161
     * @return int
162
     */
163
    public function ttl($key): int
164
    {
165
        $file = $this->getFilename($key);
166
167
        if (isset($this->timeToLive[$key])) {
168
            return max(filemtime($file) + $this->timeToLive[$key] - time(), 0);
169
        }
170
171
        return self::NO_TTL;
172
    }
173
174
    /**
175
     * @param $pattern
176
     */
177
    private function deleteGlob($pattern): void
178
    {
179
        foreach ($this->index as $key => $value) {
180
            if (fnmatch($pattern, $key)) {
181
                $this->delete($key);
182
            }
183
        }
184
    }
185
186
    /**
187
     * @param $file
188
     * @return bool
189
     */
190
    private function exists(string $file): bool
191
    {
192
        return file_exists($file);
193
    }
194
195
    /**
196
     * @param $key
197
     * @return string
198
     */
199
    private function getFilename(string $key): string
200
    {
201
        [$directory, $file] = $this->getDirectoryAndFile($key);
202
        return $directory . '/' . md5($file);
203
    }
204
205
    /**
206
     * @param string $key
207
     * @return array
208
     */
209
    private function getDirectoryAndFile(string $key): array
210
    {
211
        $directory = $this->directory;
212
        if ($this->directorySeparator !== null) {
213
            while (($position = strpos($key, $this->directorySeparator)) !== false) {
214
                $dirName = md5(substr($key, 0, $position));
215
                $directory = $directory . '/' . $dirName;
216
                $this->createSubDirectoryIfNotExists($directory);
217
                $key = substr($key, $position + 1);
218
            }
219
        }
220
        return [$directory, $key];
221
    }
222
223
    /**
224
     * @param $directory
225
     */
226
    private function createSubDirectoryIfNotExists($directory): void
227
    {
228
        if (file_exists($directory) === false) {
229
            if (!mkdir($directory) && !is_dir($directory)) {
230
                throw new \RuntimeException(sprintf('Directory "%s" was not created', $directory));
231
            }
232
            if ($this->chmod !== null) {
233
                chmod($directory, $this->chmod);
234
            }
235
        }
236
    }
237
238
    private function saveIndex(): void
239
    {
240
        file_put_contents($this->indexLocation, json_encode($this->index));
241
        file_put_contents($this->ttlLocation, json_encode($this->timeToLive));
242
    }
243
}