• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 15253296250

26 May 2025 11:55AM UTC coverage: 78.632% (+0.3%) from 78.375%
15253296250

Pull #1105

github

web-flow
Merge d9441d98f into caf806050
Pull Request #1105: Skip tests when 'git' command is not available

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Util/Cache.php
1
<?php
2
/**
3
 * Function for caching between runs.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Util;
11

12
use FilesystemIterator;
13
use PHP_CodeSniffer\Autoload;
14
use PHP_CodeSniffer\Config;
15
use PHP_CodeSniffer\Ruleset;
16
use PHP_CodeSniffer\Util\Writers\StatusWriter;
17
use RecursiveCallbackFilterIterator;
18
use RecursiveDirectoryIterator;
19
use RecursiveIteratorIterator;
20

21
class Cache
22
{
23

24
    /**
25
     * The filesystem location of the cache file.
26
     *
27
     * @var string
28
     */
29
    private static $path = '';
30

31
    /**
32
     * The cached data.
33
     *
34
     * @var array<string, mixed>
35
     */
36
    private static $cache = [];
37

38

39
    /**
40
     * Loads existing cache data for the run, if any.
41
     *
42
     * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
43
     * @param \PHP_CodeSniffer\Config  $config  The config data for the run.
44
     *
45
     * @return void
46
     */
47
    public static function load(Ruleset $ruleset, Config $config)
×
48
    {
49
        // Look at every loaded sniff class so far and use their file contents
50
        // to generate a hash for the code used during the run.
51
        // At this point, the loaded class list contains the core PHPCS code
52
        // and all sniffs that have been loaded as part of the run.
53
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
54
            StatusWriter::writeNewline();
×
55
            StatusWriter::write('Generating loaded file list for code hash', 1);
×
56
        }
57

58
        $codeHashFiles = [];
×
59

60
        $classes = array_keys(Autoload::getLoadedClasses());
×
61
        sort($classes);
×
62

63
        $installDir     = dirname(__DIR__);
×
64
        $installDirLen  = strlen($installDir);
×
65
        $standardDir    = $installDir.DIRECTORY_SEPARATOR.'Standards';
×
66
        $standardDirLen = strlen($standardDir);
×
67
        foreach ($classes as $file) {
×
68
            if (substr($file, 0, $standardDirLen) !== $standardDir) {
×
69
                if (substr($file, 0, $installDirLen) === $installDir) {
×
70
                    // We are only interested in sniffs here.
71
                    continue;
×
72
                }
73

74
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
75
                    StatusWriter::write("=> external file: $file", 2);
×
76
                }
77
            } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
78
                StatusWriter::write("=> internal sniff: $file", 2);
×
79
            }
80

81
            $codeHashFiles[] = $file;
×
82
        }
83

84
        // Add the content of the used rulesets to the hash so that sniff setting
85
        // changes in the ruleset invalidate the cache.
86
        $rulesets = $ruleset->paths;
×
87
        sort($rulesets);
×
88
        foreach ($rulesets as $file) {
×
89
            if (substr($file, 0, $standardDirLen) !== $standardDir) {
×
90
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
91
                    StatusWriter::write("=> external ruleset: $file", 2);
×
92
                }
93
            } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
94
                StatusWriter::write("=> internal ruleset: $file", 2);
×
95
            }
96

97
            $codeHashFiles[] = $file;
×
98
        }
99

100
        // Go through the core PHPCS code and add those files to the file
101
        // hash. This ensures that core PHPCS changes will also invalidate the cache.
102
        // Note that we ignore sniffs here, and any files that don't affect
103
        // the outcome of the run.
104
        $di     = new RecursiveDirectoryIterator(
×
105
            $installDir,
×
106
            (FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS)
×
107
        );
108
        $filter = new RecursiveCallbackFilterIterator(
×
109
            $di,
×
110
            static function ($file, $key, $iterator) {
111
                // Skip non-php files.
112
                $filename = $file->getFilename();
×
113
                if ($file->isFile() === true && substr($filename, -4) !== '.php') {
×
114
                    return false;
×
115
                }
116

117
                $filePath = Common::realpath($key);
×
118
                if ($filePath === false) {
×
119
                    return false;
×
120
                }
121

122
                if ($iterator->hasChildren() === true
×
123
                    && ($filename === 'Standards'
×
124
                    || $filename === 'Exceptions'
×
125
                    || $filename === 'Reports'
×
126
                    || $filename === 'Generators')
×
127
                ) {
128
                    return false;
×
129
                }
130

131
                return true;
×
132
            }
×
133
        );
134

135
        $iterator = new RecursiveIteratorIterator($filter);
×
136
        foreach ($iterator as $file) {
×
137
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
138
                StatusWriter::write("=> core file: $file", 2);
×
139
            }
140

141
            $codeHashFiles[] = $file->getPathname();
×
142
        }
143

144
        $codeHash = '';
×
145
        sort($codeHashFiles);
×
146
        foreach ($codeHashFiles as $file) {
×
147
            $codeHash .= md5_file($file);
×
148
        }
149

150
        $codeHash = md5($codeHash);
×
151

152
        // Along with the code hash, use various settings that can affect
153
        // the results of a run to create a new hash. This hash will be used
154
        // in the cache file name.
155
        $rulesetHash       = md5(var_export($ruleset->ignorePatterns, true).var_export($ruleset->includePatterns, true));
×
156
        $phpExtensionsHash = md5(var_export(get_loaded_extensions(), true));
×
157
        $configData        = [
158
            'phpVersion'    => PHP_VERSION_ID,
×
159
            'phpExtensions' => $phpExtensionsHash,
×
160
            'tabWidth'      => $config->tabWidth,
×
161
            'encoding'      => $config->encoding,
×
162
            'recordErrors'  => $config->recordErrors,
×
163
            'annotations'   => $config->annotations,
×
164
            'configData'    => Config::getAllConfigData(),
×
165
            'codeHash'      => $codeHash,
×
166
            'rulesetHash'   => $rulesetHash,
×
167
        ];
168

169
        $configString = var_export($configData, true);
×
170
        $cacheHash    = substr(sha1($configString), 0, 12);
×
171

172
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
173
            StatusWriter::write('Generating cache key data', 1);
×
174
            foreach ($configData as $key => $value) {
×
175
                if (is_array($value) === true) {
×
176
                    StatusWriter::write("=> $key:", 2);
×
177
                    foreach ($value as $subKey => $subValue) {
×
178
                        StatusWriter::write("=> $subKey: $subValue", 3);
×
179
                    }
180

181
                    continue;
×
182
                }
183

184
                if ($value === true || $value === false) {
×
185
                    $value = (int) $value;
×
186
                }
187

188
                StatusWriter::write("=> $key: $value", 2);
×
189
            }
190

191
            StatusWriter::write("=> cacheHash: $cacheHash", 2);
×
192
        }//end if
193

194
        if ($config->cacheFile !== null) {
×
195
            $cacheFile = $config->cacheFile;
×
196
        } else {
197
            // Determine the common paths for all files being checked.
198
            // We can use this to locate an existing cache file, or to
199
            // determine where to create a new one.
200
            if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
201
                StatusWriter::write('Checking possible cache file paths', 1);
×
202
            }
203

204
            $paths = [];
×
205
            foreach ($config->files as $file) {
×
206
                $file = Common::realpath($file);
×
207
                while ($file !== DIRECTORY_SEPARATOR) {
×
208
                    if (isset($paths[$file]) === false) {
×
209
                        $paths[$file] = 1;
×
210
                    } else {
211
                        $paths[$file]++;
×
212
                    }
213

214
                    $lastFile = $file;
×
215
                    $file     = dirname($file);
×
216
                    if ($file === $lastFile) {
×
217
                        // Just in case something went wrong,
218
                        // we don't want to end up in an infinite loop.
219
                        break;
×
220
                    }
221
                }
222
            }
223

224
            ksort($paths);
×
225
            $paths = array_reverse($paths);
×
226

227
            $numFiles = count($config->files);
×
228

229
            $cacheFile = null;
×
230
            $cacheDir  = getenv('XDG_CACHE_HOME');
×
231
            if ($cacheDir === false || is_dir($cacheDir) === false) {
×
232
                $cacheDir = sys_get_temp_dir();
×
233
            }
234

235
            foreach ($paths as $file => $count) {
×
236
                if ($count !== $numFiles) {
×
237
                    unset($paths[$file]);
×
238
                    continue;
×
239
                }
240

241
                $fileHash = substr(sha1($file), 0, 12);
×
242
                $testFile = $cacheDir.DIRECTORY_SEPARATOR."phpcs.$fileHash.$cacheHash.cache";
×
243
                if ($cacheFile === null) {
×
244
                    // This will be our default location if we can't find
245
                    // an existing file.
246
                    $cacheFile = $testFile;
×
247
                }
248

249
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
250
                    StatusWriter::write("=> $testFile", 2);
×
251
                    StatusWriter::write(" * based on shared location: $file *", 3);
×
252
                }
253

254
                if (file_exists($testFile) === true) {
×
255
                    $cacheFile = $testFile;
×
256
                    break;
×
257
                }
258
            }//end foreach
259

260
            if ($cacheFile === null) {
×
261
                // Unlikely, but just in case $paths is empty for some reason.
262
                $cacheFile = $cacheDir.DIRECTORY_SEPARATOR."phpcs.$cacheHash.cache";
×
263
            }
264
        }//end if
265

266
        self::$path = $cacheFile;
×
267
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
268
            StatusWriter::write('=> Using cache file: '.self::$path, 1);
×
269
        }
270

271
        if (file_exists(self::$path) === true) {
×
272
            self::$cache = json_decode(file_get_contents(self::$path), true);
×
273

274
            // Verify the contents of the cache file.
275
            if (self::$cache['config'] !== $configData) {
×
276
                self::$cache = [];
×
277
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
278
                    StatusWriter::write('* cache was invalid and has been cleared *', 1);
×
279
                }
280
            }
281
        } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
×
282
            StatusWriter::write('* cache file does not exist *', 1);
×
283
        }
284

285
        self::$cache['config'] = $configData;
×
286

287
    }//end load()
288

289

290
    /**
291
     * Saves the current cache to the filesystem.
292
     *
293
     * @return void
294
     */
295
    public static function save()
×
296
    {
297
        file_put_contents(self::$path, json_encode(self::$cache));
×
298

299
    }//end save()
300

301

302
    /**
303
     * Retrieves a single entry from the cache.
304
     *
305
     * @param string $key The key of the data to get. If NULL,
306
     *                    everything in the cache is returned.
307
     *
308
     * @return mixed
309
     */
310
    public static function get($key=null)
×
311
    {
312
        if ($key === null) {
×
313
            return self::$cache;
×
314
        }
315

316
        if (isset(self::$cache[$key]) === true) {
×
317
            return self::$cache[$key];
×
318
        }
319

320
        return false;
×
321

322
    }//end get()
323

324

325
    /**
326
     * Retrieves a single entry from the cache.
327
     *
328
     * @param string $key   The key of the data to set. If NULL,
329
     *                      sets the entire cache.
330
     * @param mixed  $value The value to set.
331
     *
332
     * @return void
333
     */
334
    public static function set($key, $value)
×
335
    {
336
        if ($key === null) {
×
337
            self::$cache = $value;
×
338
        } else {
339
            self::$cache[$key] = $value;
×
340
        }
341

342
    }//end set()
343

344

345
    /**
346
     * Retrieves the number of cache entries.
347
     *
348
     * @return int
349
     */
350
    public static function getSize()
×
351
    {
352
        return (count(self::$cache) - 1);
×
353

354
    }//end getSize()
355

356

357
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc