352 lines
11 KiB
PHP
352 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Function for caching between runs.
|
|
*
|
|
* @author Greg Sherwood <gsherwood@squiz.net>
|
|
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
|
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
|
*/
|
|
|
|
namespace PHP_CodeSniffer\Util;
|
|
|
|
use PHP_CodeSniffer\Autoload;
|
|
use PHP_CodeSniffer\Config;
|
|
use PHP_CodeSniffer\Ruleset;
|
|
|
|
class Cache
|
|
{
|
|
|
|
/**
|
|
* The filesystem location of the cache file.
|
|
*
|
|
* @var void
|
|
*/
|
|
private static $path = '';
|
|
|
|
/**
|
|
* The cached data.
|
|
*
|
|
* @var array<string, mixed>
|
|
*/
|
|
private static $cache = [];
|
|
|
|
|
|
/**
|
|
* Loads existing cache data for the run, if any.
|
|
*
|
|
* @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
|
|
* @param \PHP_CodeSniffer\Config $config The config data for the run.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function load(Ruleset $ruleset, Config $config)
|
|
{
|
|
// Look at every loaded sniff class so far and use their file contents
|
|
// to generate a hash for the code used during the run.
|
|
// At this point, the loaded class list contains the core PHPCS code
|
|
// and all sniffs that have been loaded as part of the run.
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo PHP_EOL."\tGenerating loaded file list for code hash".PHP_EOL;
|
|
}
|
|
|
|
$codeHashFiles = [];
|
|
|
|
$classes = array_keys(Autoload::getLoadedClasses());
|
|
sort($classes);
|
|
|
|
$installDir = dirname(__DIR__);
|
|
$installDirLen = strlen($installDir);
|
|
$standardDir = $installDir.DIRECTORY_SEPARATOR.'Standards';
|
|
$standardDirLen = strlen($standardDir);
|
|
foreach ($classes as $file) {
|
|
if (substr($file, 0, $standardDirLen) !== $standardDir) {
|
|
if (substr($file, 0, $installDirLen) === $installDir) {
|
|
// We are only interested in sniffs here.
|
|
continue;
|
|
}
|
|
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t\t=> external file: $file".PHP_EOL;
|
|
}
|
|
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t\t=> internal sniff: $file".PHP_EOL;
|
|
}
|
|
|
|
$codeHashFiles[] = $file;
|
|
}
|
|
|
|
// Add the content of the used rulesets to the hash so that sniff setting
|
|
// changes in the ruleset invalidate the cache.
|
|
$rulesets = $ruleset->paths;
|
|
sort($rulesets);
|
|
foreach ($rulesets as $file) {
|
|
if (substr($file, 0, $standardDirLen) !== $standardDir) {
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t\t=> external ruleset: $file".PHP_EOL;
|
|
}
|
|
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t\t=> internal ruleset: $file".PHP_EOL;
|
|
}
|
|
|
|
$codeHashFiles[] = $file;
|
|
}
|
|
|
|
// Go through the core PHPCS code and add those files to the file
|
|
// hash. This ensures that core PHPCS changes will also invalidate the cache.
|
|
// Note that we ignore sniffs here, and any files that don't affect
|
|
// the outcome of the run.
|
|
$di = new \RecursiveDirectoryIterator(
|
|
$installDir,
|
|
(\FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS)
|
|
);
|
|
$filter = new \RecursiveCallbackFilterIterator(
|
|
$di,
|
|
function ($file, $key, $iterator) {
|
|
// Skip non-php files.
|
|
$filename = $file->getFilename();
|
|
if ($file->isFile() === true && substr($filename, -4) !== '.php') {
|
|
return false;
|
|
}
|
|
|
|
$filePath = Common::realpath($key);
|
|
if ($filePath === false) {
|
|
return false;
|
|
}
|
|
|
|
if ($iterator->hasChildren() === true
|
|
&& ($filename === 'Standards'
|
|
|| $filename === 'Exceptions'
|
|
|| $filename === 'Reports'
|
|
|| $filename === 'Generators')
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
);
|
|
|
|
$iterator = new \RecursiveIteratorIterator($filter);
|
|
foreach ($iterator as $file) {
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t\t=> core file: $file".PHP_EOL;
|
|
}
|
|
|
|
$codeHashFiles[] = $file->getPathname();
|
|
}
|
|
|
|
$codeHash = '';
|
|
sort($codeHashFiles);
|
|
foreach ($codeHashFiles as $file) {
|
|
$codeHash .= md5_file($file);
|
|
}
|
|
|
|
$codeHash = md5($codeHash);
|
|
|
|
// Along with the code hash, use various settings that can affect
|
|
// the results of a run to create a new hash. This hash will be used
|
|
// in the cache file name.
|
|
$rulesetHash = md5(var_export($ruleset->ignorePatterns, true).var_export($ruleset->includePatterns, true));
|
|
$phpExtensionsHash = md5(var_export(get_loaded_extensions(), true));
|
|
$configData = [
|
|
'phpVersion' => PHP_VERSION_ID,
|
|
'phpExtensions' => $phpExtensionsHash,
|
|
'tabWidth' => $config->tabWidth,
|
|
'encoding' => $config->encoding,
|
|
'recordErrors' => $config->recordErrors,
|
|
'annotations' => $config->annotations,
|
|
'configData' => Config::getAllConfigData(),
|
|
'codeHash' => $codeHash,
|
|
'rulesetHash' => $rulesetHash,
|
|
];
|
|
|
|
$configString = var_export($configData, true);
|
|
$cacheHash = substr(sha1($configString), 0, 12);
|
|
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\tGenerating cache key data".PHP_EOL;
|
|
foreach ($configData as $key => $value) {
|
|
if (is_array($value) === true) {
|
|
echo "\t\t=> $key:".PHP_EOL;
|
|
foreach ($value as $subKey => $subValue) {
|
|
echo "\t\t\t=> $subKey: $subValue".PHP_EOL;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($value === true || $value === false) {
|
|
$value = (int) $value;
|
|
}
|
|
|
|
echo "\t\t=> $key: $value".PHP_EOL;
|
|
}
|
|
|
|
echo "\t\t=> cacheHash: $cacheHash".PHP_EOL;
|
|
}//end if
|
|
|
|
if ($config->cacheFile !== null) {
|
|
$cacheFile = $config->cacheFile;
|
|
} else {
|
|
// Determine the common paths for all files being checked.
|
|
// We can use this to locate an existing cache file, or to
|
|
// determine where to create a new one.
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\tChecking possible cache file paths".PHP_EOL;
|
|
}
|
|
|
|
$paths = [];
|
|
foreach ($config->files as $file) {
|
|
$file = Common::realpath($file);
|
|
while ($file !== DIRECTORY_SEPARATOR) {
|
|
if (isset($paths[$file]) === false) {
|
|
$paths[$file] = 1;
|
|
} else {
|
|
$paths[$file]++;
|
|
}
|
|
|
|
$lastFile = $file;
|
|
$file = dirname($file);
|
|
if ($file === $lastFile) {
|
|
// Just in case something went wrong,
|
|
// we don't want to end up in an infinite loop.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ksort($paths);
|
|
$paths = array_reverse($paths);
|
|
|
|
$numFiles = count($config->files);
|
|
|
|
$cacheFile = null;
|
|
$cacheDir = getenv('XDG_CACHE_HOME');
|
|
if ($cacheDir === false || is_dir($cacheDir) === false) {
|
|
$cacheDir = sys_get_temp_dir();
|
|
}
|
|
|
|
foreach ($paths as $file => $count) {
|
|
if ($count !== $numFiles) {
|
|
unset($paths[$file]);
|
|
continue;
|
|
}
|
|
|
|
$fileHash = substr(sha1($file), 0, 12);
|
|
$testFile = $cacheDir.DIRECTORY_SEPARATOR."phpcs.$fileHash.$cacheHash.cache";
|
|
if ($cacheFile === null) {
|
|
// This will be our default location if we can't find
|
|
// an existing file.
|
|
$cacheFile = $testFile;
|
|
}
|
|
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t\t=> $testFile".PHP_EOL;
|
|
echo "\t\t\t * based on shared location: $file *".PHP_EOL;
|
|
}
|
|
|
|
if (file_exists($testFile) === true) {
|
|
$cacheFile = $testFile;
|
|
break;
|
|
}
|
|
}//end foreach
|
|
|
|
if ($cacheFile === null) {
|
|
// Unlikely, but just in case $paths is empty for some reason.
|
|
$cacheFile = $cacheDir.DIRECTORY_SEPARATOR."phpcs.$cacheHash.cache";
|
|
}
|
|
}//end if
|
|
|
|
self::$path = $cacheFile;
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t=> Using cache file: ".self::$path.PHP_EOL;
|
|
}
|
|
|
|
if (file_exists(self::$path) === true) {
|
|
self::$cache = json_decode(file_get_contents(self::$path), true);
|
|
|
|
// Verify the contents of the cache file.
|
|
if (self::$cache['config'] !== $configData) {
|
|
self::$cache = [];
|
|
if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t* cache was invalid and has been cleared *".PHP_EOL;
|
|
}
|
|
}
|
|
} else if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
|
echo "\t* cache file does not exist *".PHP_EOL;
|
|
}
|
|
|
|
self::$cache['config'] = $configData;
|
|
|
|
}//end load()
|
|
|
|
|
|
/**
|
|
* Saves the current cache to the filesystem.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function save()
|
|
{
|
|
file_put_contents(self::$path, json_encode(self::$cache));
|
|
|
|
}//end save()
|
|
|
|
|
|
/**
|
|
* Retrieves a single entry from the cache.
|
|
*
|
|
* @param string $key The key of the data to get. If NULL,
|
|
* everything in the cache is returned.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public static function get($key=null)
|
|
{
|
|
if ($key === null) {
|
|
return self::$cache;
|
|
}
|
|
|
|
if (isset(self::$cache[$key]) === true) {
|
|
return self::$cache[$key];
|
|
}
|
|
|
|
return false;
|
|
|
|
}//end get()
|
|
|
|
|
|
/**
|
|
* Retrieves a single entry from the cache.
|
|
*
|
|
* @param string $key The key of the data to set. If NULL,
|
|
* sets the entire cache.
|
|
* @param mixed $value The value to set.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function set($key, $value)
|
|
{
|
|
if ($key === null) {
|
|
self::$cache = $value;
|
|
} else {
|
|
self::$cache[$key] = $value;
|
|
}
|
|
|
|
}//end set()
|
|
|
|
|
|
/**
|
|
* Retrieves the number of cache entries.
|
|
*
|
|
* @return int
|
|
*/
|
|
public static function getSize()
|
|
{
|
|
return (count(self::$cache) - 1);
|
|
|
|
}//end getSize()
|
|
|
|
|
|
}//end class
|