Completed
Push — master ( 3d22df...95226d )
by Georges
22s queued 12s
created

IOHelperTrait::readFile()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
c 0
b 0
f 0
nc 4
nop 1
dl 0
loc 19
rs 9.9
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
declare(strict_types=1);
15
16
namespace Phpfastcache\Core\Pool\IO;
17
18
use Phpfastcache\Core\Pool\ExtendedCacheItemPoolInterface;
19
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait;
20
use Phpfastcache\Entities\DriverStatistic;
21
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException;
22
use Phpfastcache\Exceptions\PhpfastcacheIOException;
23
use Phpfastcache\Util\Directory;
24
use Phpfastcache\Util\SapiDetector;
25
26
trait IOHelperTrait
27
{
28
    use TaggableCacheItemPoolTrait;
29
30
    /**
31
     * @var array
32
     */
33
    public array $tmp = [];
34
35
    /**
36
     * Provide a generic getStats() method
37
     * for files-based drivers
38
     * @return DriverStatistic
39
     * @throws PhpfastcacheIOException
40
     */
41
    public function getStats(): DriverStatistic
42
    {
43
        $stat = new DriverStatistic();
44
        $path = $this->getFilePath(false);
45
46
        if (!is_dir($path)) {
47
            throw new PhpfastcacheIOException("Can't read PATH:" . $path);
48
        }
49
        $stat->setRawData(
50
            [
51
                    'tmp' => $this->tmp,
52
                ]
53
        )
54
            ->setSize(Directory::dirSize($path))
55
            ->setInfo('Number of files used to build the cache: ' . Directory::getFileCount($path));
56
57
        if ($this->getConfig()->isUseStaticItemCaching()) {
58
            $stat->setData(implode(', ', \array_keys($this->itemInstances)));
59
        } else {
60
            $stat->setData('No data available since static item caching option (useStaticItemCaching) is disabled.');
61
        }
62
63
        return $stat;
64
    }
65
66
    /**
67
     * @param string|bool $keyword
68
     * @param bool $skip
69
     * @return string
70
     * @throws PhpfastcacheIOException
71
     */
72
    protected function getFilePath(string|bool $keyword, bool $skip = false): string
73
    {
74
        $path = $this->getPath();
75
76
        if ($keyword === false) {
77
            return $path;
78
        }
79
80
        $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

80
        $filename = $this->encodeFilename(/** @scrutinizer ignore-type */ $keyword);
Loading history...
81
        $folder = \substr($filename, 0, 2) . DIRECTORY_SEPARATOR . \substr($filename, 2, 2);
82
        $path = \rtrim($path, '/\\') . DIRECTORY_SEPARATOR . $folder;
83
84
        /**
85
         * Skip Create Sub Folders;
86
         */
87
        if (!$skip && !\is_dir($path) && @!\mkdir($path, $this->getDefaultChmod(), true) && !\is_dir($path)) {
88
            throw new PhpfastcacheIOException(
89
                'Path "' . $path . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
90
            );
91
        }
92
93
        return $path . \DIRECTORY_SEPARATOR . $filename . '.' . $this->getConfig()->getCacheFileExtension();
0 ignored issues
show
Bug introduced by
The method getCacheFileExtension() does not exist on Phpfastcache\Config\ConfigurationOption. It seems like you code against a sub-type of Phpfastcache\Config\ConfigurationOption such as Phpfastcache\Drivers\Files\Config or Phpfastcache\Drivers\Sqlite\Config or Phpfastcache\Drivers\Leveldb\Config. ( Ignorable by Annotation )

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

93
        return $path . \DIRECTORY_SEPARATOR . $filename . '.' . $this->getConfig()->/** @scrutinizer ignore-call */ getCacheFileExtension();
Loading history...
94
    }
95
96
    /**
97
     * @param bool $readonly
98
     * @return string
99
     * @throws PhpfastcacheIOException
100
     * @throws PhpfastcacheInvalidArgumentException
101
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
102
     * @SuppressWarnings(PHPMD.NPathComplexity)
103
     */
104
    public function getPath(bool $readonly = false): string
105
    {
106
        /**
107
         * Get the base system temporary directory
108
         */
109
        $tmpDir = \rtrim(\ini_get('upload_tmp_dir') ?: \sys_get_temp_dir(), '\\/') . DIRECTORY_SEPARATOR . 'phpfastcache';
110
111
        /**
112
         * Calculate the security key
113
         */
114
        {
115
            $httpHost = $this->getConfig()->getSuperGlobalAccessor()('SERVER', 'HTTP_HOST');
116
            $securityKey = $this->getConfig()->getSecurityKey();
0 ignored issues
show
Bug introduced by
The method getSecurityKey() does not exist on Phpfastcache\Config\ConfigurationOption. It seems like you code against a sub-type of Phpfastcache\Config\ConfigurationOption such as Phpfastcache\Drivers\Files\Config or Phpfastcache\Drivers\Sqlite\Config or Phpfastcache\Drivers\Leveldb\Config. ( Ignorable by Annotation )

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

116
            $securityKey = $this->getConfig()->/** @scrutinizer ignore-call */ getSecurityKey();
Loading history...
117
        if (!$securityKey || \mb_strtolower($securityKey) === 'auto') {
118
            if (isset($httpHost)) {
119
                $securityKey = \preg_replace('/^www./', '', \strtolower(\str_replace(':', '_', $httpHost)));
120
            } else {
121
                $securityKey = (SapiDetector::isWebScript() ? 'web' : 'cli');
122
            }
123
        }
124
125
        if ($securityKey !== '') {
126
            $securityKey .= '/';
127
        }
128
129
            $securityKey = static::cleanFileName($securityKey);
130
        }
131
132
        /**
133
         * Extends the temporary directory
134
         * with the security key and the driver name
135
         */
136
        $tmpDir = \rtrim($tmpDir, '/') . DIRECTORY_SEPARATOR;
137
138
        if (empty($this->getConfig()->getPath())) {
139
            $path = $tmpDir;
140
        } else {
141
            $path = \rtrim($this->getConfig()->getPath(), '/') . DIRECTORY_SEPARATOR;
142
        }
143
144
        $pathSuffix = $securityKey . DIRECTORY_SEPARATOR . $this->getDriverName();
145
        $fullPath = Directory::getAbsolutePath($path . $pathSuffix);
146
        $fullPathTmp = Directory::getAbsolutePath($tmpDir . $pathSuffix);
147
        $fullPathHash = $this->getConfig()->getDefaultFileNameHashFunction()($fullPath);
148
149
        /**
150
         * In readonly mode we only attempt
151
         * to verify if the directory exists
152
         * or not, if it does not then we
153
         * return the temp dir
154
         */
155
        if ($readonly === true) {
156
            if ($this->getConfig()->isAutoTmpFallback() && (!@\file_exists($fullPath) || !@\is_writable($fullPath))) {
157
                return $fullPathTmp;
158
            }
159
            return $fullPath;
160
        }
161
162
        if (!isset($this->tmp[$fullPathHash]) || (!@\file_exists($fullPath) || !@\is_writable($fullPath))) {
163
            if (!@\file_exists($fullPath)) {
164
                if (@mkdir($fullPath, $this->getDefaultChmod(), true) === false && !\is_dir($fullPath)) {
165
                    throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.');
166
                }
167
            } elseif (!@\is_writable($fullPath) && !@\chmod($fullPath, $this->getDefaultChmod()) && $this->getConfig()->isAutoTmpFallback()) {
168
                /**
169
                 * Switch back to tmp dir
170
                 * again if the path is not writable
171
                 */
172
                $fullPath = $fullPathTmp;
173
                if (!@\file_exists($fullPath) && @\mkdir($fullPath, $this->getDefaultChmod(), true) && !\is_dir($fullPath)) {
174
                    throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.');
175
                }
176
            }
177
178
            /**
179
             * In case there is no directory
180
             * writable including the temporary
181
             * one, we must throw an exception
182
             */
183
            if (!@\file_exists($fullPath) || !@\is_writable($fullPath)) {
184
                throw new PhpfastcacheIOException(
185
                    'Path "' . $fullPath . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !'
186
                );
187
            }
188
189
            $this->tmp[$fullPathHash] = $fullPath;
190
        }
191
192
        return realpath($fullPath);
193
    }
194
195
    /**
196
     * @param string $filename
197
     * @return string
198
     */
199
    protected static function cleanFileName(string $filename): string
200
    {
201
        $regex = [
202
            '/[\?\[\]\/\\\=\<\>\:\;\,\'\"\&\$\#\*\(\)\|\~\`\!\{\}]/',
203
            '/\.$/',
204
            '/^\./',
205
        ];
206
        $replace = ['-', '', ''];
207
208
        return \trim(\preg_replace($regex, $replace, \trim($filename)), '-');
209
    }
210
211
    /**
212
     * @return int
213
     */
214
    protected function getDefaultChmod(): int
215
    {
216
        if (!$this->getConfig()->getDefaultChmod()) {
0 ignored issues
show
Bug introduced by
The method getDefaultChmod() does not exist on Phpfastcache\Config\ConfigurationOption. Did you maybe mean getDefaultTtl()? ( Ignorable by Annotation )

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

216
        if (!$this->getConfig()->/** @scrutinizer ignore-call */ getDefaultChmod()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
217
            return 0777;
218
        }
219
220
        return $this->getConfig()->getDefaultChmod();
221
    }
222
223
    /**
224
     * @param string $keyword
225
     * @return string
226
     */
227
    protected function encodeFilename(string $keyword): string
228
    {
229
        return $this->getConfig()->getDefaultFileNameHashFunction()($keyword);
230
    }
231
232
    /**
233
     * @param string $file
234
     * @return string
235
     * @throws PhpfastcacheIOException
236
     */
237
    protected function readFile(string $file): string
238
    {
239
        if (!\is_readable($file)) {
240
            throw new PhpfastcacheIOException("Cannot read file located at: $file");
241
        }
242
        if (\function_exists('file_get_contents')) {
243
            return (string)\file_get_contents($file);
244
        }
245
246
        $string = '';
247
248
        $fileHandle = @\fopen($file, 'rb');
249
        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

249
        while (!\feof(/** @scrutinizer ignore-type */ $fileHandle)) {
Loading history...
250
            $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

250
            $line = \fgets(/** @scrutinizer ignore-type */ $fileHandle);
Loading history...
251
            $string .= $line;
252
        }
253
        \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

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