* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ namespace PHP_CodeSniffer; use Composer\Autoload\ClassLoader; use Exception; if (class_exists('PHP_CodeSniffer\Autoload', false) === false) { class Autoload { /** * The composer autoloader. * * @var \Composer\Autoload\ClassLoader */ private static $composerAutoloader = null; /** * A mapping of file names to class names. * * @var array */ private static $loadedClasses = []; /** * A mapping of class names to file names. * * @var array */ private static $loadedFiles = []; /** * A list of additional directories to search during autoloading. * * This is typically a list of coding standard directories. * * @var string[] */ private static $searchPaths = []; /** * Loads a class. * * This method only loads classes that exist in the PHP_CodeSniffer namespace. * All other classes are ignored and loaded by subsequent autoloaders. * * @param string $class The name of the class to load. * * @return bool */ public static function load($class) { // Include the composer autoloader if there is one, but re-register it // so this autoloader runs before the composer one as we need to include // all files so we can figure out what the class/interface/trait name is. if (self::$composerAutoloader === null) { // Make sure we don't try to load any of Composer's classes // while the autoloader is being setup. if (strpos($class, 'Composer\\') === 0) { return false; } if ( strpos(__DIR__, 'phar://') !== 0 && @file_exists(__DIR__ . '/../../autoload.php') === true ) { self::$composerAutoloader = include __DIR__ . '/../../autoload.php'; if (self::$composerAutoloader instanceof ClassLoader) { self::$composerAutoloader->unregister(); self::$composerAutoloader->register(); } else { // Something went wrong, so keep going without the autoloader // although namespaced sniffs might error. self::$composerAutoloader = false; } } else { self::$composerAutoloader = false; } }//end if $ds = DIRECTORY_SEPARATOR; $path = false; if (substr($class, 0, 16) === 'PHP_CodeSniffer\\') { if (substr($class, 0, 22) === 'PHP_CodeSniffer\Tests\\') { $isInstalled = !is_dir(__DIR__ . $ds . 'tests'); if ($isInstalled === false) { $path = __DIR__ . $ds . 'tests'; } else { $path = '@test_dir@' . $ds . 'PHP_CodeSniffer' . $ds . 'CodeSniffer'; } $path .= $ds . substr(str_replace('\\', $ds, $class), 22) . '.php'; } else { $path = __DIR__ . $ds . 'src' . $ds . substr(str_replace('\\', $ds, $class), 16) . '.php'; } } // See if the composer autoloader knows where the class is. if ($path === false && self::$composerAutoloader !== false) { $path = self::$composerAutoloader->findFile($class); } // See if the class is inside one of our alternate search paths. if ($path === false) { foreach (self::$searchPaths as $searchPath => $nsPrefix) { $className = $class; if ($nsPrefix !== '' && substr($class, 0, strlen($nsPrefix)) === $nsPrefix) { $className = substr($class, (strlen($nsPrefix) + 1)); } $path = $searchPath . $ds . str_replace('\\', $ds, $className) . '.php'; if (is_file($path) === true) { break; } $path = false; } } if ($path !== false && is_file($path) === true) { self::loadFile($path); return true; } return false; }//end load() /** * Includes a file and tracks what class or interface was loaded as a result. * * @param string $path The path of the file to load. * * @return string The fully qualified name of the class in the loaded file. */ public static function loadFile($path) { if (strpos(__DIR__, 'phar://') !== 0) { $path = realpath($path); if ($path === false) { return false; } } if (isset(self::$loadedClasses[$path]) === true) { return self::$loadedClasses[$path]; } $classesBeforeLoad = [ 'classes' => get_declared_classes(), 'interfaces' => get_declared_interfaces(), 'traits' => get_declared_traits(), ]; include $path; $classesAfterLoad = [ 'classes' => get_declared_classes(), 'interfaces' => get_declared_interfaces(), 'traits' => get_declared_traits(), ]; $className = self::determineLoadedClass($classesBeforeLoad, $classesAfterLoad); self::$loadedClasses[$path] = $className; self::$loadedFiles[$className] = $path; return self::$loadedClasses[$path]; }//end loadFile() /** * Determine which class was loaded based on the before and after lists of loaded classes. * * @param array $classesBeforeLoad The classes/interfaces/traits before the file was included. * @param array $classesAfterLoad The classes/interfaces/traits after the file was included. * * @return string The fully qualified name of the class in the loaded file. */ public static function determineLoadedClass($classesBeforeLoad, $classesAfterLoad) { $className = null; $newClasses = array_diff($classesAfterLoad['classes'], $classesBeforeLoad['classes']); if (PHP_VERSION_ID < 70400) { $newClasses = array_reverse($newClasses); } // Since PHP 7.4 get_declared_classes() does not guarantee any order, making // it impossible to use order to determine which is the parent an which is the child. // Let's reduce the list of candidates by removing all the classes known to be "parents". // That way, at the end, only the "main" class just included will remain. $newClasses = array_reduce( $newClasses, function ($remaining, $current) { return array_diff($remaining, class_parents($current)); }, $newClasses ); foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; break; } } if ($className === null) { $newClasses = array_reverse(array_diff($classesAfterLoad['interfaces'], $classesBeforeLoad['interfaces'])); foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; break; } } } if ($className === null) { $newClasses = array_reverse(array_diff($classesAfterLoad['traits'], $classesBeforeLoad['traits'])); foreach ($newClasses as $name) { if (isset(self::$loadedFiles[$name]) === false) { $className = $name; break; } } } return $className; }//end determineLoadedClass() /** * Adds a directory to search during autoloading. * * @param string $path The path to the directory to search. * @param string $nsPrefix The namespace prefix used by files under this path. * * @return void */ public static function addSearchPath($path, $nsPrefix = '') { self::$searchPaths[$path] = rtrim(trim((string) $nsPrefix), '\\'); }//end addSearchPath() /** * Retrieve the namespaces and paths registered by external standards. * * @return array */ public static function getSearchPaths() { return self::$searchPaths; }//end getSearchPaths() /** * Gets the class name for the given file path. * * @param string $path The name of the file. * * @throws \Exception If the file path has not been loaded. * @return string */ public static function getLoadedClassName($path) { if (isset(self::$loadedClasses[$path]) === false) { throw new Exception("Cannot get class name for $path; file has not been included"); } return self::$loadedClasses[$path]; }//end getLoadedClassName() /** * Gets the file path for the given class name. * * @param string $class The name of the class. * * @throws \Exception If the class name has not been loaded. * @return string */ public static function getLoadedFileName($class) { if (isset(self::$loadedFiles[$class]) === false) { throw new Exception("Cannot get file name for $class; class has not been included"); } return self::$loadedFiles[$class]; }//end getLoadedFileName() /** * Gets the mapping of file names to class names. * * @return array */ public static function getLoadedClasses() { return self::$loadedClasses; }//end getLoadedClasses() /** * Gets the mapping of class names to file names. * * @return array */ public static function getLoadedFiles() { return self::$loadedFiles; }//end getLoadedFiles() }//end class // Register the autoloader before any existing autoloaders to ensure // it gets a chance to hear about every autoload request, and record // the file and class name for it. spl_autoload_register(__NAMESPACE__ . '\Autoload::load', true, true); }//end if