Test Failed
Push — master ( 3f03af...9d5adc )
by
unknown
11:45
created

IOHelperTrait::mkdir()   C

Complexity

Conditions 15
Paths 9

Size

Total Lines 32
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 13
nc 9
nop 2
dl 0
loc 32
rs 5.9166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 *
5
 * This file is part of Phpfastcache.
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files.
10
 *
11
 * @author Georges.L (Geolim4)  <[email protected]>
12
 * @author Contributors  https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phpfastcache\Core\Pool\IO;
18
19
use Phpfastcache\Config\IOConfigurationOptionInterface;
20
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
21
use Phpfastcache\Entities\DriverStatistic;
22
use Phpfastcache\Event\Event;
23
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
24
use Phpfastcache\Exceptions\PhpfastcacheIOException;
25
use Phpfastcache\Util\Directory;
26
use Phpfastcache\Util\SapiDetector;
27
28
/**
29
 * @method IOConfigurationOptionInterface getConfig()
30
 */
31
trait IOHelperTrait
32
{
33
    use TaggableCacheItemPoolTrait;
34
35
    /**
36
     * @var array<string, string>
37
     */
38
    public array $tmp = [];
39
40
    /**
41
     * Provide a generic getStats() method
42
     * for files-based drivers
43
     * @return DriverStatistic
44
     * @throws PhpfastcacheIOException
45
     * @throws PhpfastcacheInvalidArgumentException
46
     */
47
    public function getStats(): DriverStatistic
48
    {
49
        $stat = new DriverStatistic();
50
        $path = $this->getFilePath(false);
51
52
        if (!is_dir($path)) {
53
            throw new PhpfastcacheIOException("Can't read PATH:" . $path);
54
        }
55
        $stat->setSize(Directory::dirSize($path))
56
            ->setInfo('Number of files used to build the cache: ' . Directory::getFileCount($path))
57
            ->setRawData(
58
                [
59
                    'tmp' => $this->tmp,
60
                ]
61
            );
62
63
        if ($this->getConfig()->isUseStaticItemCaching()) {
64
            $stat->setData(implode(', ', \array_keys($this->itemInstances)));
0 ignored issues
show
Deprecated Code introduced by
The function Phpfastcache\Entities\DriverStatistic::setData() has been deprecated: as of phpfastcache 9.2.3, will be removed as of v10 ( Ignorable by Annotation )

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

64
            /** @scrutinizer ignore-deprecated */ $stat->setData(implode(', ', \array_keys($this->itemInstances)));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
65
        } else {
66
            $stat->setData('No data available since static item caching option (useStaticItemCaching) is disabled.');
0 ignored issues
show
Deprecated Code introduced by
The function Phpfastcache\Entities\DriverStatistic::setData() has been deprecated: as of phpfastcache 9.2.3, will be removed as of v10 ( Ignorable by Annotation )

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

66
            /** @scrutinizer ignore-deprecated */ $stat->setData('No data available since static item caching option (useStaticItemCaching) is disabled.');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
67
        }
68
69
        return $stat;
70
    }
71
72
    /**
73
     * @param string|bool $keyword
74
     * @param bool $skip
75
     * @return string
76
     * @throws PhpfastcacheIOException
77
     * @throws PhpfastcacheInvalidArgumentException
78
     */
79
    protected function getFilePath(string|bool $keyword, bool $skip = false): string
80
    {
81
        $path = $this->getPath();
82
83
        if ($keyword === false) {
84
            return $path;
85
        }
86
87
        $filename = $this->encodeFilename($keyword);
0 ignored issues
show
Bug introduced by
It seems like $keyword can also be of type true; however, parameter $keyword of Phpfastcache\Core\Pool\I...Trait::encodeFilename() 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

87
        $filename = $this->encodeFilename(/** @scrutinizer ignore-type */ $keyword);
Loading history...
88
        $folder = \substr($filename, 0, 2) . DIRECTORY_SEPARATOR . \substr($filename, 2, 2);
89
        $path = \rtrim($path, '/\\') . DIRECTORY_SEPARATOR . $folder;
90
91
        /**
92
         * Skip Create Sub Folders;
93
         */
94
        if (!$skip && !\is_dir($path) && @!\mkdir($path, $this->getDefaultChmod(), true) && !\is_dir($path)) {
95
            throw new PhpfastcacheIOException(
96
                'Path "' . $path . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
97
            );
98
        }
99
100
        return $path . \DIRECTORY_SEPARATOR . $filename . '.' . $this->getConfig()->getCacheFileExtension();
101
    }
102
103
    /**
104
     * @param bool $readonly
105
     * @return string
106
     * @throws PhpfastcacheIOException
107
     * @throws PhpfastcacheInvalidArgumentException
108
     */
109
    public function getPath(bool $readonly = false): string
110
    {
111
        $tmpDir = \rtrim(\ini_get('upload_tmp_dir') ?: \sys_get_temp_dir(), '\\/') . DIRECTORY_SEPARATOR . 'phpfastcache';
112
        $httpHost = $this->getConfig()->getSuperGlobalAccessor()('SERVER', 'HTTP_HOST');
113
        $securityKey = $this->buildSecurityKey($httpHost);
114
115
        /**
116
         * Extends the temporary directory
117
         * with the security key and the driver name
118
         */
119
        $tmpDir = \rtrim($tmpDir, '/') . DIRECTORY_SEPARATOR;
120
121
        if (empty($this->getConfig()->getPath())) {
122
            $path = $tmpDir;
123
        } else {
124
            $path = \rtrim($this->getConfig()->getPath(), '/') . DIRECTORY_SEPARATOR;
125
        }
126
127
        $pathSuffix = $securityKey . DIRECTORY_SEPARATOR . $this->getDriverName();
128
        $fullPath = Directory::getAbsolutePath($path . $pathSuffix);
129
        $fullPathTmp = Directory::getAbsolutePath($tmpDir . $pathSuffix);
130
131
        $this->mkdir($fullPath, $fullPathTmp);
132
133
        /**
134
         * In readonly mode we only attempt
135
         * to verify if the directory exists
136
         * or not, if it does not then we
137
         * return the temp dir
138
         */
139
        if ($readonly) {
140
            if ($this->getConfig()->isAutoTmpFallback() && (!@\file_exists($fullPath) || !@\is_writable($fullPath))) {
141
                return $fullPathTmp;
142
            }
143
            return $fullPath;
144
        }
145
146
        return realpath($fullPath);
147
    }
148
149
    protected function buildSecurityKey(?string $httpHost): string
150
    {
151
        $securityKey = $this->getConfig()->getSecurityKey();
152
        if (!$securityKey || \mb_strtolower($securityKey) === 'auto') {
153
            if (isset($httpHost)) {
154
                $securityKey = \preg_replace('/^www./', '', \strtolower(\str_replace(':', '_', $httpHost)));
155
            } else {
156
                $securityKey = (SapiDetector::isWebScript() ? 'web' : 'cli');
157
            }
158
        }
159
160
        if (!empty($securityKey)) {
161
            $securityKey .= '/';
162
        }
163
164
        return static::cleanFileName($securityKey);
165
    }
166
167
    /**
168
     * @throws PhpfastcacheIOException
169
     */
170
    protected function mkdir(string $fullPath, string $fullPathTmp): void
171
    {
172
        $fullPathHash = $this->getConfig()->getDefaultFileNameHashFunction()($fullPath);
173
174
        if (!isset($this->tmp[$fullPathHash]) || (!@\file_exists($fullPath) || !@\is_writable($fullPath))) {
175
            if (!@\file_exists($fullPath)) {
176
                if (@mkdir($fullPath, $this->getDefaultChmod(), true) === false && !\is_dir($fullPath)) {
177
                    throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.');
178
                }
179
            } elseif (!@\is_writable($fullPath) && !@\chmod($fullPath, $this->getDefaultChmod()) && $this->getConfig()->isAutoTmpFallback()) {
180
                /**
181
                 * Switch back to tmp dir
182
                 * again if the path is not writable
183
                 */
184
                $fullPath = $fullPathTmp;
185
                if (!@\file_exists($fullPath) && @\mkdir($fullPath, $this->getDefaultChmod(), true) && !\is_dir($fullPath)) {
186
                    throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.');
187
                }
188
            }
189
190
            /**
191
             * In case there is no directory
192
             * writable including the temporary
193
             * one, we must throw an exception
194
             */
195
            if (!@\file_exists($fullPath) || !@\is_writable($fullPath)) {
196
                throw new PhpfastcacheIOException(
197
                    'Path "' . $fullPath . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
198
                );
199
            }
200
201
            $this->tmp[$fullPathHash] = $fullPath;
202
        }
203
    }
204
205
    /**
206
     * @param string $filename
207
     * @return string
208
     */
209
    protected static function cleanFileName(string $filename): string
210
    {
211
        $regex = [
212
            '/[\?\[\]\/\\\=\<\>\:\;\,\'\"\&\$\#\*\(\)\|\~\`\!\{\}]/',
213
            '/\.$/',
214
            '/^\./',
215
        ];
216
        $replace = ['-', '', ''];
217
218
        return \trim(\preg_replace($regex, $replace, \trim($filename)), '-');
219
    }
220
221
    /**
222
     * @return int
223
     */
224
    protected function getDefaultChmod(): int
225
    {
226
        if (!$this->getConfig()->getDefaultChmod()) {
227
            return 0777;
228
        }
229
230
        return $this->getConfig()->getDefaultChmod();
231
    }
232
233
    /**
234
     * @param string $keyword
235
     * @return string
236
     */
237
    protected function encodeFilename(string $keyword): string
238
    {
239
        return $this->getConfig()->getDefaultFileNameHashFunction()($keyword);
240
    }
241
242
    /**
243
     * @param string $file
244
     * @return string
245
     * @throws PhpfastcacheIOException
246
     */
247
    protected function readFile(string $file): string
248
    {
249
        if (!\is_readable($file)) {
250
            throw new PhpfastcacheIOException("Cannot read file located at: $file");
251
        }
252
        if (\function_exists('file_get_contents')) {
253
            return (string)\file_get_contents($file);
254
        }
255
256
        $string = '';
257
258
        $fileHandle = @\fopen($file, 'rb');
259
        while (!\feof($fileHandle)) {
0 ignored issues
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $stream of feof() does only seem to accept resource, 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

259
        while (!\feof(/** @scrutinizer ignore-type */ $fileHandle)) {
Loading history...
260
            $line = \fgets($fileHandle);
0 ignored issues
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $stream of fgets() does only seem to accept resource, 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

260
            $line = \fgets(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
261
            $string .= $line;
262
        }
263
        \fclose($fileHandle);
0 ignored issues
show
Bug introduced by
It seems like $fileHandle can also be of type false; however, parameter $stream of fclose() does only seem to accept resource, 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

263
        \fclose(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
264
265
        return $string;
266
    }
267
268
    /********************
269
     *
270
     * PSR-6 Extended Methods
271
     *
272
     *******************/
273
274
    /**
275
     * @param string $file
276
     * @param string $data
277
     * @param bool $secureFileManipulation
278
     * @return bool
279
     * @throws PhpfastcacheIOException
280
     * @throws \Exception
281
     */
282
    protected function writeFile(string $file, string $data, bool $secureFileManipulation = false): bool
283
    {
284
        $this->eventManager->dispatch(Event::CACHE_WRITE_FILE_ON_DISK, $this, $file, $secureFileManipulation);
285
286
        if ($secureFileManipulation) {
287
            $tmpFilename = Directory::getAbsolutePath(
288
                dirname($file) . \DIRECTORY_SEPARATOR . 'tmp_' . $this->getConfig()->getDefaultFileNameHashFunction()(
289
                    \bin2hex(\random_bytes(16))
290
                )
291
            ) . '.' . $this->getConfig()->getCacheFileExtension() . \random_int(1000, 9999);
292
293
            $handle = \fopen($tmpFilename, 'w+b');
294
            if (\is_resource($handle)) {
295
                \flock($handle, \LOCK_EX);
296
                $octetWritten = fwrite($handle, $data);
297
                \flock($handle, \LOCK_UN);
298
                \fclose($handle);
299
            }
300
301
            if (!\rename($tmpFilename, $file)) {
302
                throw new PhpfastcacheIOException(\sprintf('Failed to rename %s to %s', $tmpFilename, $file));
303
            }
304
        } else {
305
            $handle = \fopen($file, 'w+b');
306
            if (\is_resource($handle)) {
307
                $octetWritten = \fwrite($handle, $data);
308
                \fclose($handle);
309
            }
310
        }
311
312
        return (bool)($octetWritten ?? false);
313
    }
314
}
315