Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * Function for caching between runs.
|
Chris@17
|
4 *
|
Chris@17
|
5 * @author Greg Sherwood <gsherwood@squiz.net>
|
Chris@17
|
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
Chris@17
|
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
Chris@17
|
8 */
|
Chris@17
|
9
|
Chris@17
|
10 namespace PHP_CodeSniffer\Util;
|
Chris@17
|
11
|
Chris@17
|
12 use PHP_CodeSniffer\Autoload;
|
Chris@17
|
13 use PHP_CodeSniffer\Config;
|
Chris@17
|
14 use PHP_CodeSniffer\Ruleset;
|
Chris@17
|
15
|
Chris@17
|
16 class Cache
|
Chris@17
|
17 {
|
Chris@17
|
18
|
Chris@17
|
19 /**
|
Chris@17
|
20 * The filesystem location of the cache file.
|
Chris@17
|
21 *
|
Chris@17
|
22 * @var void
|
Chris@17
|
23 */
|
Chris@17
|
24 private static $path = '';
|
Chris@17
|
25
|
Chris@17
|
26 /**
|
Chris@17
|
27 * The cached data.
|
Chris@17
|
28 *
|
Chris@17
|
29 * @var array<string, mixed>
|
Chris@17
|
30 */
|
Chris@17
|
31 private static $cache = [];
|
Chris@17
|
32
|
Chris@17
|
33
|
Chris@17
|
34 /**
|
Chris@17
|
35 * Loads existing cache data for the run, if any.
|
Chris@17
|
36 *
|
Chris@17
|
37 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
|
Chris@17
|
38 * @param \PHP_CodeSniffer\Config $config The config data for the run.
|
Chris@17
|
39 *
|
Chris@17
|
40 * @return void
|
Chris@17
|
41 */
|
Chris@17
|
42 public static function load(Ruleset $ruleset, Config $config)
|
Chris@17
|
43 {
|
Chris@17
|
44 // Look at every loaded sniff class so far and use their file contents
|
Chris@17
|
45 // to generate a hash for the code used during the run.
|
Chris@17
|
46 // At this point, the loaded class list contains the core PHPCS code
|
Chris@17
|
47 // and all sniffs that have been loaded as part of the run.
|
Chris@17
|
48 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
49 echo PHP_EOL."\tGenerating loaded file list for code hash".PHP_EOL;
|
Chris@17
|
50 }
|
Chris@17
|
51
|
Chris@17
|
52 $codeHashFiles = [];
|
Chris@17
|
53
|
Chris@17
|
54 $classes = array_keys(Autoload::getLoadedClasses());
|
Chris@17
|
55 sort($classes);
|
Chris@17
|
56
|
Chris@17
|
57 $installDir = dirname(__DIR__);
|
Chris@17
|
58 $installDirLen = strlen($installDir);
|
Chris@17
|
59 $standardDir = $installDir.DIRECTORY_SEPARATOR.'Standards';
|
Chris@17
|
60 $standardDirLen = strlen($standardDir);
|
Chris@17
|
61 foreach ($classes as $file) {
|
Chris@17
|
62 if (substr($file, 0, $standardDirLen) !== $standardDir) {
|
Chris@17
|
63 if (substr($file, 0, $installDirLen) === $installDir) {
|
Chris@17
|
64 // We are only interested in sniffs here.
|
Chris@17
|
65 continue;
|
Chris@17
|
66 }
|
Chris@17
|
67
|
Chris@17
|
68 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
69 echo "\t\t=> external file: $file".PHP_EOL;
|
Chris@17
|
70 }
|
Chris@17
|
71 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
72 echo "\t\t=> internal sniff: $file".PHP_EOL;
|
Chris@17
|
73 }
|
Chris@17
|
74
|
Chris@17
|
75 $codeHashFiles[] = $file;
|
Chris@17
|
76 }
|
Chris@17
|
77
|
Chris@17
|
78 // Add the content of the used rulesets to the hash so that sniff setting
|
Chris@17
|
79 // changes in the ruleset invalidate the cache.
|
Chris@17
|
80 $rulesets = $ruleset->paths;
|
Chris@17
|
81 sort($rulesets);
|
Chris@17
|
82 foreach ($rulesets as $file) {
|
Chris@17
|
83 if (substr($file, 0, $standardDirLen) !== $standardDir) {
|
Chris@17
|
84 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
85 echo "\t\t=> external ruleset: $file".PHP_EOL;
|
Chris@17
|
86 }
|
Chris@17
|
87 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
88 echo "\t\t=> internal ruleset: $file".PHP_EOL;
|
Chris@17
|
89 }
|
Chris@17
|
90
|
Chris@17
|
91 $codeHashFiles[] = $file;
|
Chris@17
|
92 }
|
Chris@17
|
93
|
Chris@17
|
94 // Go through the core PHPCS code and add those files to the file
|
Chris@17
|
95 // hash. This ensures that core PHPCS changes will also invalidate the cache.
|
Chris@17
|
96 // Note that we ignore sniffs here, and any files that don't affect
|
Chris@17
|
97 // the outcome of the run.
|
Chris@17
|
98 $di = new \RecursiveDirectoryIterator($installDir);
|
Chris@17
|
99 $filter = new \RecursiveCallbackFilterIterator(
|
Chris@17
|
100 $di,
|
Chris@17
|
101 function ($file, $key, $iterator) {
|
Chris@17
|
102 // Skip hidden files.
|
Chris@17
|
103 $filename = $file->getFilename();
|
Chris@17
|
104 if (substr($filename, 0, 1) === '.') {
|
Chris@17
|
105 return false;
|
Chris@17
|
106 }
|
Chris@17
|
107
|
Chris@17
|
108 $filePath = Common::realpath($file->getPathname());
|
Chris@17
|
109 if ($filePath === false) {
|
Chris@17
|
110 return false;
|
Chris@17
|
111 }
|
Chris@17
|
112
|
Chris@17
|
113 if (is_dir($filePath) === true
|
Chris@17
|
114 && ($filename === 'Standards'
|
Chris@17
|
115 || $filename === 'Exceptions'
|
Chris@17
|
116 || $filename === 'Reports'
|
Chris@17
|
117 || $filename === 'Generators')
|
Chris@17
|
118 ) {
|
Chris@17
|
119 return false;
|
Chris@17
|
120 }
|
Chris@17
|
121
|
Chris@17
|
122 return true;
|
Chris@17
|
123 }
|
Chris@17
|
124 );
|
Chris@17
|
125
|
Chris@17
|
126 $iterator = new \RecursiveIteratorIterator($filter);
|
Chris@17
|
127 foreach ($iterator as $file) {
|
Chris@17
|
128 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
129 echo "\t\t=> core file: $file".PHP_EOL;
|
Chris@17
|
130 }
|
Chris@17
|
131
|
Chris@17
|
132 $codeHashFiles[] = $file->getPathname();
|
Chris@17
|
133 }
|
Chris@17
|
134
|
Chris@17
|
135 $codeHash = '';
|
Chris@17
|
136 sort($codeHashFiles);
|
Chris@17
|
137 foreach ($codeHashFiles as $file) {
|
Chris@17
|
138 $codeHash .= md5_file($file);
|
Chris@17
|
139 }
|
Chris@17
|
140
|
Chris@17
|
141 $codeHash = md5($codeHash);
|
Chris@17
|
142
|
Chris@17
|
143 // Along with the code hash, use various settings that can affect
|
Chris@17
|
144 // the results of a run to create a new hash. This hash will be used
|
Chris@17
|
145 // in the cache file name.
|
Chris@17
|
146 $rulesetHash = md5(var_export($ruleset->ignorePatterns, true).var_export($ruleset->includePatterns, true));
|
Chris@17
|
147 $configData = [
|
Chris@17
|
148 'phpVersion' => PHP_VERSION_ID,
|
Chris@17
|
149 'tabWidth' => $config->tabWidth,
|
Chris@17
|
150 'encoding' => $config->encoding,
|
Chris@17
|
151 'recordErrors' => $config->recordErrors,
|
Chris@17
|
152 'annotations' => $config->annotations,
|
Chris@17
|
153 'configData' => Config::getAllConfigData(),
|
Chris@17
|
154 'codeHash' => $codeHash,
|
Chris@17
|
155 'rulesetHash' => $rulesetHash,
|
Chris@17
|
156 ];
|
Chris@17
|
157
|
Chris@17
|
158 $configString = var_export($configData, true);
|
Chris@17
|
159 $cacheHash = substr(sha1($configString), 0, 12);
|
Chris@17
|
160
|
Chris@17
|
161 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
162 echo "\tGenerating cache key data".PHP_EOL;
|
Chris@17
|
163 foreach ($configData as $key => $value) {
|
Chris@17
|
164 if (is_array($value) === true) {
|
Chris@17
|
165 echo "\t\t=> $key:".PHP_EOL;
|
Chris@17
|
166 foreach ($value as $subKey => $subValue) {
|
Chris@17
|
167 echo "\t\t\t=> $subKey: $subValue".PHP_EOL;
|
Chris@17
|
168 }
|
Chris@17
|
169
|
Chris@17
|
170 continue;
|
Chris@17
|
171 }
|
Chris@17
|
172
|
Chris@17
|
173 if ($value === true || $value === false) {
|
Chris@17
|
174 $value = (int) $value;
|
Chris@17
|
175 }
|
Chris@17
|
176
|
Chris@17
|
177 echo "\t\t=> $key: $value".PHP_EOL;
|
Chris@17
|
178 }
|
Chris@17
|
179
|
Chris@17
|
180 echo "\t\t=> cacheHash: $cacheHash".PHP_EOL;
|
Chris@17
|
181 }//end if
|
Chris@17
|
182
|
Chris@17
|
183 if ($config->cacheFile !== null) {
|
Chris@17
|
184 $cacheFile = $config->cacheFile;
|
Chris@17
|
185 } else {
|
Chris@17
|
186 // Determine the common paths for all files being checked.
|
Chris@17
|
187 // We can use this to locate an existing cache file, or to
|
Chris@17
|
188 // determine where to create a new one.
|
Chris@17
|
189 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
190 echo "\tChecking possible cache file paths".PHP_EOL;
|
Chris@17
|
191 }
|
Chris@17
|
192
|
Chris@17
|
193 $paths = [];
|
Chris@17
|
194 foreach ($config->files as $file) {
|
Chris@17
|
195 $file = Common::realpath($file);
|
Chris@17
|
196 while ($file !== DIRECTORY_SEPARATOR) {
|
Chris@17
|
197 if (isset($paths[$file]) === false) {
|
Chris@17
|
198 $paths[$file] = 1;
|
Chris@17
|
199 } else {
|
Chris@17
|
200 $paths[$file]++;
|
Chris@17
|
201 }
|
Chris@17
|
202
|
Chris@17
|
203 $lastFile = $file;
|
Chris@17
|
204 $file = dirname($file);
|
Chris@17
|
205 if ($file === $lastFile) {
|
Chris@17
|
206 // Just in case something went wrong,
|
Chris@17
|
207 // we don't want to end up in an infinite loop.
|
Chris@17
|
208 break;
|
Chris@17
|
209 }
|
Chris@17
|
210 }
|
Chris@17
|
211 }
|
Chris@17
|
212
|
Chris@17
|
213 ksort($paths);
|
Chris@17
|
214 $paths = array_reverse($paths);
|
Chris@17
|
215
|
Chris@17
|
216 $numFiles = count($config->files);
|
Chris@17
|
217
|
Chris@17
|
218 $cacheFile = null;
|
Chris@17
|
219 $cacheDir = getenv('XDG_CACHE_HOME');
|
Chris@17
|
220 if ($cacheDir === false || is_dir($cacheDir) === false) {
|
Chris@17
|
221 $cacheDir = sys_get_temp_dir();
|
Chris@17
|
222 }
|
Chris@17
|
223
|
Chris@17
|
224 foreach ($paths as $file => $count) {
|
Chris@17
|
225 if ($count !== $numFiles) {
|
Chris@17
|
226 unset($paths[$file]);
|
Chris@17
|
227 continue;
|
Chris@17
|
228 }
|
Chris@17
|
229
|
Chris@17
|
230 $fileHash = substr(sha1($file), 0, 12);
|
Chris@17
|
231 $testFile = $cacheDir.DIRECTORY_SEPARATOR."phpcs.$fileHash.$cacheHash.cache";
|
Chris@17
|
232 if ($cacheFile === null) {
|
Chris@17
|
233 // This will be our default location if we can't find
|
Chris@17
|
234 // an existing file.
|
Chris@17
|
235 $cacheFile = $testFile;
|
Chris@17
|
236 }
|
Chris@17
|
237
|
Chris@17
|
238 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
239 echo "\t\t=> $testFile".PHP_EOL;
|
Chris@17
|
240 echo "\t\t\t * based on shared location: $file *".PHP_EOL;
|
Chris@17
|
241 }
|
Chris@17
|
242
|
Chris@17
|
243 if (file_exists($testFile) === true) {
|
Chris@17
|
244 $cacheFile = $testFile;
|
Chris@17
|
245 break;
|
Chris@17
|
246 }
|
Chris@17
|
247 }//end foreach
|
Chris@17
|
248
|
Chris@17
|
249 if ($cacheFile === null) {
|
Chris@17
|
250 // Unlikely, but just in case $paths is empty for some reason.
|
Chris@17
|
251 $cacheFile = $cacheDir.DIRECTORY_SEPARATOR."phpcs.$cacheHash.cache";
|
Chris@17
|
252 }
|
Chris@17
|
253 }//end if
|
Chris@17
|
254
|
Chris@17
|
255 self::$path = $cacheFile;
|
Chris@17
|
256 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
257 echo "\t=> Using cache file: ".self::$path.PHP_EOL;
|
Chris@17
|
258 }
|
Chris@17
|
259
|
Chris@17
|
260 if (file_exists(self::$path) === true) {
|
Chris@17
|
261 self::$cache = json_decode(file_get_contents(self::$path), true);
|
Chris@17
|
262
|
Chris@17
|
263 // Verify the contents of the cache file.
|
Chris@17
|
264 if (self::$cache['config'] !== $configData) {
|
Chris@17
|
265 self::$cache = [];
|
Chris@17
|
266 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
267 echo "\t* cache was invalid and has been cleared *".PHP_EOL;
|
Chris@17
|
268 }
|
Chris@17
|
269 }
|
Chris@17
|
270 } else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
271 echo "\t* cache file does not exist *".PHP_EOL;
|
Chris@17
|
272 }
|
Chris@17
|
273
|
Chris@17
|
274 self::$cache['config'] = $configData;
|
Chris@17
|
275
|
Chris@17
|
276 }//end load()
|
Chris@17
|
277
|
Chris@17
|
278
|
Chris@17
|
279 /**
|
Chris@17
|
280 * Saves the current cache to the filesystem.
|
Chris@17
|
281 *
|
Chris@17
|
282 * @return void
|
Chris@17
|
283 */
|
Chris@17
|
284 public static function save()
|
Chris@17
|
285 {
|
Chris@17
|
286 file_put_contents(self::$path, json_encode(self::$cache));
|
Chris@17
|
287
|
Chris@17
|
288 }//end save()
|
Chris@17
|
289
|
Chris@17
|
290
|
Chris@17
|
291 /**
|
Chris@17
|
292 * Retrieves a single entry from the cache.
|
Chris@17
|
293 *
|
Chris@17
|
294 * @param string $key The key of the data to get. If NULL,
|
Chris@17
|
295 * everything in the cache is returned.
|
Chris@17
|
296 *
|
Chris@17
|
297 * @return mixed
|
Chris@17
|
298 */
|
Chris@17
|
299 public static function get($key=null)
|
Chris@17
|
300 {
|
Chris@17
|
301 if ($key === null) {
|
Chris@17
|
302 return self::$cache;
|
Chris@17
|
303 }
|
Chris@17
|
304
|
Chris@17
|
305 if (isset(self::$cache[$key]) === true) {
|
Chris@17
|
306 return self::$cache[$key];
|
Chris@17
|
307 }
|
Chris@17
|
308
|
Chris@17
|
309 return false;
|
Chris@17
|
310
|
Chris@17
|
311 }//end get()
|
Chris@17
|
312
|
Chris@17
|
313
|
Chris@17
|
314 /**
|
Chris@17
|
315 * Retrieves a single entry from the cache.
|
Chris@17
|
316 *
|
Chris@17
|
317 * @param string $key The key of the data to set. If NULL,
|
Chris@17
|
318 * sets the entire cache.
|
Chris@17
|
319 * @param mixed $value The value to set.
|
Chris@17
|
320 *
|
Chris@17
|
321 * @return void
|
Chris@17
|
322 */
|
Chris@17
|
323 public static function set($key, $value)
|
Chris@17
|
324 {
|
Chris@17
|
325 if ($key === null) {
|
Chris@17
|
326 self::$cache = $value;
|
Chris@17
|
327 } else {
|
Chris@17
|
328 self::$cache[$key] = $value;
|
Chris@17
|
329 }
|
Chris@17
|
330
|
Chris@17
|
331 }//end set()
|
Chris@17
|
332
|
Chris@17
|
333
|
Chris@17
|
334 /**
|
Chris@17
|
335 * Retrieves the number of cache entries.
|
Chris@17
|
336 *
|
Chris@17
|
337 * @return int
|
Chris@17
|
338 */
|
Chris@17
|
339 public static function getSize()
|
Chris@17
|
340 {
|
Chris@17
|
341 return (count(self::$cache) - 1);
|
Chris@17
|
342
|
Chris@17
|
343 }//end getSize()
|
Chris@17
|
344
|
Chris@17
|
345
|
Chris@17
|
346 }//end class
|