Passed
Pull Request — master (#104)
by Ankit
01:47
created

FileStore::sharedGet()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 8
nop 1
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 4
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace TusPhp\Cache;
4
5
use Carbon\Carbon;
6
use TusPhp\Config;
7
8
class FileStore extends AbstractCache
9
{
10
    /** @var string */
11
    protected $cacheDir;
12
13
    /** @var string */
14
    protected $cacheFile;
15
16
    /**
17
     * FileStore constructor.
18
     *
19
     * @param string|null $cacheDir
20
     * @param string|null $cacheFile
21
     */
22 2
    public function __construct(string $cacheDir = null, string $cacheFile = null)
23
    {
24 2
        $cacheDir  = $cacheDir ?? Config::get('file.dir');
25 2
        $cacheFile = $cacheFile ?? Config::get('file.name');
26
27 2
        $this->setCacheDir($cacheDir);
1 ignored issue
show
Bug introduced by
It seems like $cacheDir can also be of type null; however, parameter $path of TusPhp\Cache\FileStore::setCacheDir() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

27
        $this->setCacheDir(/** @scrutinizer ignore-type */ $cacheDir);
Loading history...
28 2
        $this->setCacheFile($cacheFile);
1 ignored issue
show
Bug introduced by
It seems like $cacheFile can also be of type null; however, parameter $file of TusPhp\Cache\FileStore::setCacheFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

28
        $this->setCacheFile(/** @scrutinizer ignore-type */ $cacheFile);
Loading history...
29 2
    }
30
31
    /**
32
     * Set cache dir.
33
     *
34
     * @param string $path
35
     *
36
     * @return self
37
     */
38 1
    public function setCacheDir(string $path) : self
39
    {
40 1
        $this->cacheDir = $path;
41
42 1
        return $this;
43
    }
44
45
    /**
46
     * Get cache dir.
47
     *
48
     * @return string
49
     */
50 1
    public function getCacheDir() : string
51
    {
52 1
        return $this->cacheDir;
53
    }
54
55
    /**
56
     * Set cache file.
57
     *
58
     * @param string $file
59
     *
60
     * @return self
61
     */
62 1
    public function setCacheFile(string $file) : self
63
    {
64 1
        $this->cacheFile = $file;
65
66 1
        return $this;
67
    }
68
69
    /**
70
     * Get cache file.
71
     *
72
     * @return string
73
     */
74 5
    public function getCacheFile() : string
75
    {
76 5
        return $this->cacheDir . $this->cacheFile;
77
    }
78
79
    /**
80
     * Create cache dir if not exists.
81
     *
82
     * @return void
83
     */
84 2
    protected function createCacheDir()
85
    {
86 2
        if ( ! file_exists($this->cacheDir)) {
87 2
            mkdir($this->cacheDir);
88
        }
89 2
    }
90
91
    /**
92
     * Create cache file and add required meta.
93
     *
94
     * @return void
95
     */
96 2
    protected function createCacheFile()
97
    {
98 2
        $this->createCacheDir();
99
100 2
        $cacheFilePath = $this->getCacheFile();
101
102 2
        if ( ! file_exists($cacheFilePath)) {
103 2
            touch($cacheFilePath);
104
        }
105 2
    }
106
107
    /**
108
     * {@inheritDoc}
109
     */
110 8
    public function get(string $key, bool $withExpired = false)
111
    {
112 8
        $key      = $this->getActualCacheKey($key);
113 8
        $contents = $this->getCacheContents();
114
115 8
        if (empty($contents[$key])) {
116 3
            return null;
117
        }
118
119 5
        if ($withExpired) {
120 1
            return $contents[$key];
121
        }
122
123 5
        return $this->isValid($key) ? $contents[$key] : null;
124
    }
125
126
    /**
127
     * Get contents of a file with shared access.
128
     *
129
     * @param string $path
130
     *
131
     * @return string
132
     */
133 2
    public function sharedGet(string $path) : string
134
    {
135 2
        $contents  = '';
136 2
        $chunkSize = 1048576; // 1 mb.
137 2
        $handle    = @fopen($path, 'r');
138
139 2
        if (false === $handle) {
140 1
            return $contents;
141
        }
142
143
        try {
144 1
            if (flock($handle, LOCK_SH)) {
145 1
                while ( ! feof($handle)) {
146 1
                    $contents .= fread($handle, $chunkSize);
147
                }
148
            }
149 1
        } finally {
150 1
            fclose($handle);
151
        }
152
153 1
        return $contents;
154
    }
155
156
    /**
157
     * Write the contents of a file with exclusive lock.
158
     *
159
     * @param  string $path
160
     * @param  string $contents
161
     *
162
     * @return int
163
     */
164 3
    public function put(string $path, string $contents) : int
165
    {
166 3
        return file_put_contents($path, $contents, LOCK_EX);
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172 8
    public function set(string $key, $value)
173
    {
174 8
        $cacheKey  = $this->getActualCacheKey($key);
175 8
        $cacheFile = $this->getCacheFile();
176
177 8
        if ( ! file_exists($cacheFile) || ! $this->isValid($cacheKey)) {
178 8
            $this->createCacheFile();
179
        }
180
181 8
        $contents = json_decode($this->sharedGet($cacheFile), true) ?? [];
182
183 8
        if ( ! empty($contents[$cacheKey]) && is_array($value)) {
184 3
            $contents[$cacheKey] = $value + $contents[$cacheKey];
185
        } else {
186 8
            $contents[$cacheKey] = $value;
187
        }
188
189 8
        return $this->put($cacheFile, json_encode($contents));
190
    }
191
192
    /**
193
     * {@inheritDoc}
194
     */
195 1
    public function delete(string $key) : bool
196
    {
197 1
        $cacheKey = $this->getActualCacheKey($key);
198 1
        $contents = $this->getCacheContents();
199
200 1
        if (isset($contents[$cacheKey])) {
201 1
            unset($contents[$cacheKey]);
202
203 1
            return false !== $this->put($this->getCacheFile(), json_encode($contents));
204
        }
205
206 1
        return false;
207
    }
208
209
    /**
210
     * {@inheritDoc}
211
     */
212 2
    public function keys() : array
213
    {
214 2
        $contents = $this->getCacheContents();
215
216 2
        if (is_array($contents)) {
217 1
            return array_keys($this->getCacheContents());
0 ignored issues
show
Bug introduced by
It seems like $this->getCacheContents() can also be of type boolean; however, parameter $input of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

217
            return array_keys(/** @scrutinizer ignore-type */ $this->getCacheContents());
Loading history...
218
        }
219
220 1
        return [];
221
    }
222
223
    /**
224
     * Check if cache is still valid.
225
     *
226
     * @param string $key
227
     *
228
     * @return bool
229
     */
230 3
    public function isValid(string $key) : bool
231
    {
232 3
        $key  = $this->getActualCacheKey($key);
233 3
        $meta = $this->getCacheContents()[$key] ?? [];
234
235 3
        if (empty($meta['expires_at'])) {
236 1
            return false;
237
        }
238
239 2
        return Carbon::now() < Carbon::createFromFormat(self::RFC_7231, $meta['expires_at']);
240
    }
241
242
    /**
243
     * Get cache contents.
244
     *
245
     * @return array|bool
246
     */
247 3
    public function getCacheContents()
248
    {
249 3
        $cacheFile = $this->getCacheFile();
250
251 3
        if ( ! file_exists($cacheFile)) {
252 1
            return false;
253
        }
254
255 2
        return json_decode($this->sharedGet($cacheFile), true) ?? [];
256
    }
257
258
    /**
259
     * Get actual cache key with prefix.
260
     *
261
     * @param string $key
262
     *
263
     * @return string
264
     */
265 1
    public function getActualCacheKey(string $key) : string
266
    {
267 1
        $prefix = $this->getPrefix();
268
269 1
        if (false === strpos($key, $prefix)) {
270 1
            $key = $prefix . $key;
271
        }
272
273 1
        return $key;
274
    }
275
}
276