autoload.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. <?php
  2. /**
  3. * Autoloads files for PHP_CodeSniffer and tracks what has been loaded.
  4. *
  5. * Due to different namespaces being used for custom coding standards,
  6. * the autoloader keeps track of what class is loaded after a file is included,
  7. * even if the file is ultimately included by another autoloader (such as composer).
  8. *
  9. * This allows PHP_CodeSniffer to request the class name after loading a class
  10. * when it only knows the filename, without having to parse the file to find it.
  11. *
  12. * @author Greg Sherwood <gsherwood@squiz.net>
  13. * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
  14. * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
  15. */
  16. namespace PHP_CodeSniffer;
  17. use Composer\Autoload\ClassLoader;
  18. use Exception;
  19. if (class_exists('PHP_CodeSniffer\Autoload', false) === false) {
  20. class Autoload
  21. {
  22. /**
  23. * The composer autoloader.
  24. *
  25. * @var \Composer\Autoload\ClassLoader|false|null The autoloader object or FALSE if no Composer autoloader could
  26. * be found. NULL when this hasn't been determined yet.
  27. */
  28. private static $composerAutoloader = null;
  29. /**
  30. * A mapping of file names to class names.
  31. *
  32. * @var array<string, string>
  33. */
  34. private static $loadedClasses = [];
  35. /**
  36. * A mapping of class names to file names.
  37. *
  38. * @var array<string, string>
  39. */
  40. private static $loadedFiles = [];
  41. /**
  42. * A list of additional directories to search during autoloading.
  43. *
  44. * This is typically a list of coding standard directories.
  45. *
  46. * @var string[]
  47. */
  48. private static $searchPaths = [];
  49. /**
  50. * Loads a class.
  51. *
  52. * This method only loads classes that exist in the PHP_CodeSniffer namespace.
  53. * All other classes are ignored and loaded by subsequent autoloaders.
  54. *
  55. * @param string $class The name of the class to load.
  56. *
  57. * @return bool
  58. */
  59. public static function load($class)
  60. {
  61. // Include the composer autoloader if there is one, but re-register it
  62. // so this autoloader runs before the composer one as we need to include
  63. // all files so we can figure out what the class/interface/trait name is.
  64. if (self::$composerAutoloader === null) {
  65. // Make sure we don't try to load any of Composer's classes
  66. // while the autoloader is being setup.
  67. if (strpos($class, 'Composer\\') === 0) {
  68. return false;
  69. }
  70. if (strpos(__DIR__, 'phar://') !== 0
  71. && @file_exists(__DIR__.'/../../autoload.php') === true
  72. ) {
  73. self::$composerAutoloader = include __DIR__.'/../../autoload.php';
  74. if (self::$composerAutoloader instanceof ClassLoader) {
  75. self::$composerAutoloader->unregister();
  76. self::$composerAutoloader->register();
  77. } else {
  78. // Something went wrong, so keep going without the autoloader
  79. // although namespaced sniffs might error.
  80. self::$composerAutoloader = false;
  81. }
  82. } else {
  83. self::$composerAutoloader = false;
  84. }
  85. }//end if
  86. $ds = DIRECTORY_SEPARATOR;
  87. $path = false;
  88. if (substr($class, 0, 16) === 'PHP_CodeSniffer\\') {
  89. if (substr($class, 0, 22) === 'PHP_CodeSniffer\Tests\\') {
  90. $isInstalled = !is_dir(__DIR__.$ds.'tests');
  91. if ($isInstalled === false) {
  92. $path = __DIR__.$ds.'tests';
  93. } else {
  94. $path = '@test_dir@'.$ds.'PHP_CodeSniffer'.$ds.'CodeSniffer';
  95. }
  96. $path .= $ds.substr(str_replace('\\', $ds, $class), 22).'.php';
  97. } else {
  98. $path = __DIR__.$ds.'src'.$ds.substr(str_replace('\\', $ds, $class), 16).'.php';
  99. }
  100. }
  101. // See if the composer autoloader knows where the class is.
  102. if ($path === false && self::$composerAutoloader !== false) {
  103. $path = self::$composerAutoloader->findFile($class);
  104. }
  105. // See if the class is inside one of our alternate search paths.
  106. if ($path === false) {
  107. foreach (self::$searchPaths as $searchPath => $nsPrefix) {
  108. $className = $class;
  109. if ($nsPrefix !== '' && substr($class, 0, strlen($nsPrefix)) === $nsPrefix) {
  110. $className = substr($class, (strlen($nsPrefix) + 1));
  111. }
  112. $path = $searchPath.$ds.str_replace('\\', $ds, $className).'.php';
  113. if (is_file($path) === true) {
  114. break;
  115. }
  116. $path = false;
  117. }
  118. }
  119. if ($path !== false && is_file($path) === true) {
  120. self::loadFile($path);
  121. return true;
  122. }
  123. return false;
  124. }//end load()
  125. /**
  126. * Includes a file and tracks what class or interface was loaded as a result.
  127. *
  128. * @param string $path The path of the file to load.
  129. *
  130. * @return string The fully qualified name of the class in the loaded file.
  131. */
  132. public static function loadFile($path)
  133. {
  134. if (strpos(__DIR__, 'phar://') !== 0) {
  135. $path = realpath($path);
  136. if ($path === false) {
  137. return false;
  138. }
  139. }
  140. if (isset(self::$loadedClasses[$path]) === true) {
  141. return self::$loadedClasses[$path];
  142. }
  143. $classesBeforeLoad = [
  144. 'classes' => get_declared_classes(),
  145. 'interfaces' => get_declared_interfaces(),
  146. 'traits' => get_declared_traits(),
  147. ];
  148. include $path;
  149. $classesAfterLoad = [
  150. 'classes' => get_declared_classes(),
  151. 'interfaces' => get_declared_interfaces(),
  152. 'traits' => get_declared_traits(),
  153. ];
  154. $className = self::determineLoadedClass($classesBeforeLoad, $classesAfterLoad);
  155. self::$loadedClasses[$path] = $className;
  156. self::$loadedFiles[$className] = $path;
  157. return self::$loadedClasses[$path];
  158. }//end loadFile()
  159. /**
  160. * Determine which class was loaded based on the before and after lists of loaded classes.
  161. *
  162. * @param array $classesBeforeLoad The classes/interfaces/traits before the file was included.
  163. * @param array $classesAfterLoad The classes/interfaces/traits after the file was included.
  164. *
  165. * @return string The fully qualified name of the class in the loaded file.
  166. */
  167. public static function determineLoadedClass($classesBeforeLoad, $classesAfterLoad)
  168. {
  169. $className = null;
  170. $newClasses = array_diff($classesAfterLoad['classes'], $classesBeforeLoad['classes']);
  171. if (PHP_VERSION_ID < 70400) {
  172. $newClasses = array_reverse($newClasses);
  173. }
  174. // Since PHP 7.4 get_declared_classes() does not guarantee any order, making
  175. // it impossible to use order to determine which is the parent and which is the child.
  176. // Let's reduce the list of candidates by removing all the classes known to be "parents".
  177. // That way, at the end, only the "main" class just included will remain.
  178. $newClasses = array_reduce(
  179. $newClasses,
  180. static function ($remaining, $current) {
  181. return array_diff($remaining, class_parents($current));
  182. },
  183. $newClasses
  184. );
  185. foreach ($newClasses as $name) {
  186. if (isset(self::$loadedFiles[$name]) === false) {
  187. $className = $name;
  188. break;
  189. }
  190. }
  191. if ($className === null) {
  192. $newClasses = array_reverse(array_diff($classesAfterLoad['interfaces'], $classesBeforeLoad['interfaces']));
  193. foreach ($newClasses as $name) {
  194. if (isset(self::$loadedFiles[$name]) === false) {
  195. $className = $name;
  196. break;
  197. }
  198. }
  199. }
  200. if ($className === null) {
  201. $newClasses = array_reverse(array_diff($classesAfterLoad['traits'], $classesBeforeLoad['traits']));
  202. foreach ($newClasses as $name) {
  203. if (isset(self::$loadedFiles[$name]) === false) {
  204. $className = $name;
  205. break;
  206. }
  207. }
  208. }
  209. return $className;
  210. }//end determineLoadedClass()
  211. /**
  212. * Adds a directory to search during autoloading.
  213. *
  214. * @param string $path The path to the directory to search.
  215. * @param string $nsPrefix The namespace prefix used by files under this path.
  216. *
  217. * @return void
  218. */
  219. public static function addSearchPath($path, $nsPrefix='')
  220. {
  221. self::$searchPaths[$path] = rtrim(trim((string) $nsPrefix), '\\');
  222. }//end addSearchPath()
  223. /**
  224. * Retrieve the namespaces and paths registered by external standards.
  225. *
  226. * @return array
  227. */
  228. public static function getSearchPaths()
  229. {
  230. return self::$searchPaths;
  231. }//end getSearchPaths()
  232. /**
  233. * Gets the class name for the given file path.
  234. *
  235. * @param string $path The name of the file.
  236. *
  237. * @throws \Exception If the file path has not been loaded.
  238. * @return string
  239. */
  240. public static function getLoadedClassName($path)
  241. {
  242. if (isset(self::$loadedClasses[$path]) === false) {
  243. throw new Exception("Cannot get class name for $path; file has not been included");
  244. }
  245. return self::$loadedClasses[$path];
  246. }//end getLoadedClassName()
  247. /**
  248. * Gets the file path for the given class name.
  249. *
  250. * @param string $class The name of the class.
  251. *
  252. * @throws \Exception If the class name has not been loaded.
  253. * @return string
  254. */
  255. public static function getLoadedFileName($class)
  256. {
  257. if (isset(self::$loadedFiles[$class]) === false) {
  258. throw new Exception("Cannot get file name for $class; class has not been included");
  259. }
  260. return self::$loadedFiles[$class];
  261. }//end getLoadedFileName()
  262. /**
  263. * Gets the mapping of file names to class names.
  264. *
  265. * @return array<string, string>
  266. */
  267. public static function getLoadedClasses()
  268. {
  269. return self::$loadedClasses;
  270. }//end getLoadedClasses()
  271. /**
  272. * Gets the mapping of class names to file names.
  273. *
  274. * @return array<string, string>
  275. */
  276. public static function getLoadedFiles()
  277. {
  278. return self::$loadedFiles;
  279. }//end getLoadedFiles()
  280. }//end class
  281. // Register the autoloader before any existing autoloaders to ensure
  282. // it gets a chance to hear about every autoload request, and record
  283. // the file and class name for it.
  284. spl_autoload_register(__NAMESPACE__.'\Autoload::load', true, true);
  285. }//end if