You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
12 KiB

8 years ago
  1. <?php
  2. /*
  3. * This file is part of Composer.
  4. *
  5. * (c) Nils Adermann <naderman@naderman.de>
  6. * Jordi Boggiano <j.boggiano@seld.be>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Composer\Autoload;
  12. /**
  13. * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
  14. *
  15. * $loader = new \Composer\Autoload\ClassLoader();
  16. *
  17. * // register classes with namespaces
  18. * $loader->add('Symfony\Component', __DIR__.'/component');
  19. * $loader->add('Symfony', __DIR__.'/framework');
  20. *
  21. * // activate the autoloader
  22. * $loader->register();
  23. *
  24. * // to enable searching the include path (eg. for PEAR packages)
  25. * $loader->setUseIncludePath(true);
  26. *
  27. * In this example, if you try to use a class in the Symfony\Component
  28. * namespace or one of its children (Symfony\Component\Console for instance),
  29. * the autoloader will first look for the class under the component/
  30. * directory, and it will then fallback to the framework/ directory if not
  31. * found before giving up.
  32. *
  33. * This class is loosely based on the Symfony UniversalClassLoader.
  34. *
  35. * @author Fabien Potencier <fabien@symfony.com>
  36. * @author Jordi Boggiano <j.boggiano@seld.be>
  37. * @see http://www.php-fig.org/psr/psr-0/
  38. * @see http://www.php-fig.org/psr/psr-4/
  39. */
  40. class ClassLoader
  41. {
  42. // PSR-4
  43. private $prefixLengthsPsr4 = array();
  44. private $prefixDirsPsr4 = array();
  45. private $fallbackDirsPsr4 = array();
  46. // PSR-0
  47. private $prefixesPsr0 = array();
  48. private $fallbackDirsPsr0 = array();
  49. private $useIncludePath = false;
  50. private $classMap = array();
  51. private $classMapAuthoritative = false;
  52. private $missingClasses = array();
  53. public function getPrefixes()
  54. {
  55. if (!empty($this->prefixesPsr0)) {
  56. return call_user_func_array('array_merge', $this->prefixesPsr0);
  57. }
  58. return array();
  59. }
  60. public function getPrefixesPsr4()
  61. {
  62. return $this->prefixDirsPsr4;
  63. }
  64. public function getFallbackDirs()
  65. {
  66. return $this->fallbackDirsPsr0;
  67. }
  68. public function getFallbackDirsPsr4()
  69. {
  70. return $this->fallbackDirsPsr4;
  71. }
  72. public function getClassMap()
  73. {
  74. return $this->classMap;
  75. }
  76. /**
  77. * @param array $classMap Class to filename map
  78. */
  79. public function addClassMap(array $classMap)
  80. {
  81. if ($this->classMap) {
  82. $this->classMap = array_merge($this->classMap, $classMap);
  83. } else {
  84. $this->classMap = $classMap;
  85. }
  86. }
  87. /**
  88. * Registers a set of PSR-0 directories for a given prefix, either
  89. * appending or prepending to the ones previously set for this prefix.
  90. *
  91. * @param string $prefix The prefix
  92. * @param array|string $paths The PSR-0 root directories
  93. * @param bool $prepend Whether to prepend the directories
  94. */
  95. public function add($prefix, $paths, $prepend = false)
  96. {
  97. if (!$prefix) {
  98. if ($prepend) {
  99. $this->fallbackDirsPsr0 = array_merge(
  100. (array) $paths,
  101. $this->fallbackDirsPsr0
  102. );
  103. } else {
  104. $this->fallbackDirsPsr0 = array_merge(
  105. $this->fallbackDirsPsr0,
  106. (array) $paths
  107. );
  108. }
  109. return;
  110. }
  111. $first = $prefix[0];
  112. if (!isset($this->prefixesPsr0[$first][$prefix])) {
  113. $this->prefixesPsr0[$first][$prefix] = (array) $paths;
  114. return;
  115. }
  116. if ($prepend) {
  117. $this->prefixesPsr0[$first][$prefix] = array_merge(
  118. (array) $paths,
  119. $this->prefixesPsr0[$first][$prefix]
  120. );
  121. } else {
  122. $this->prefixesPsr0[$first][$prefix] = array_merge(
  123. $this->prefixesPsr0[$first][$prefix],
  124. (array) $paths
  125. );
  126. }
  127. }
  128. /**
  129. * Registers a set of PSR-4 directories for a given namespace, either
  130. * appending or prepending to the ones previously set for this namespace.
  131. *
  132. * @param string $prefix The prefix/namespace, with trailing '\\'
  133. * @param array|string $paths The PSR-4 base directories
  134. * @param bool $prepend Whether to prepend the directories
  135. *
  136. * @throws \InvalidArgumentException
  137. */
  138. public function addPsr4($prefix, $paths, $prepend = false)
  139. {
  140. if (!$prefix) {
  141. // Register directories for the root namespace.
  142. if ($prepend) {
  143. $this->fallbackDirsPsr4 = array_merge(
  144. (array) $paths,
  145. $this->fallbackDirsPsr4
  146. );
  147. } else {
  148. $this->fallbackDirsPsr4 = array_merge(
  149. $this->fallbackDirsPsr4,
  150. (array) $paths
  151. );
  152. }
  153. } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
  154. // Register directories for a new namespace.
  155. $length = strlen($prefix);
  156. if ('\\' !== $prefix[$length - 1]) {
  157. throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
  158. }
  159. $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
  160. $this->prefixDirsPsr4[$prefix] = (array) $paths;
  161. } elseif ($prepend) {
  162. // Prepend directories for an already registered namespace.
  163. $this->prefixDirsPsr4[$prefix] = array_merge(
  164. (array) $paths,
  165. $this->prefixDirsPsr4[$prefix]
  166. );
  167. } else {
  168. // Append directories for an already registered namespace.
  169. $this->prefixDirsPsr4[$prefix] = array_merge(
  170. $this->prefixDirsPsr4[$prefix],
  171. (array) $paths
  172. );
  173. }
  174. }
  175. /**
  176. * Registers a set of PSR-0 directories for a given prefix,
  177. * replacing any others previously set for this prefix.
  178. *
  179. * @param string $prefix The prefix
  180. * @param array|string $paths The PSR-0 base directories
  181. */
  182. public function set($prefix, $paths)
  183. {
  184. if (!$prefix) {
  185. $this->fallbackDirsPsr0 = (array) $paths;
  186. } else {
  187. $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
  188. }
  189. }
  190. /**
  191. * Registers a set of PSR-4 directories for a given namespace,
  192. * replacing any others previously set for this namespace.
  193. *
  194. * @param string $prefix The prefix/namespace, with trailing '\\'
  195. * @param array|string $paths The PSR-4 base directories
  196. *
  197. * @throws \InvalidArgumentException
  198. */
  199. public function setPsr4($prefix, $paths)
  200. {
  201. if (!$prefix) {
  202. $this->fallbackDirsPsr4 = (array) $paths;
  203. } else {
  204. $length = strlen($prefix);
  205. if ('\\' !== $prefix[$length - 1]) {
  206. throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
  207. }
  208. $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
  209. $this->prefixDirsPsr4[$prefix] = (array) $paths;
  210. }
  211. }
  212. /**
  213. * Turns on searching the include path for class files.
  214. *
  215. * @param bool $useIncludePath
  216. */
  217. public function setUseIncludePath($useIncludePath)
  218. {
  219. $this->useIncludePath = $useIncludePath;
  220. }
  221. /**
  222. * Can be used to check if the autoloader uses the include path to check
  223. * for classes.
  224. *
  225. * @return bool
  226. */
  227. public function getUseIncludePath()
  228. {
  229. return $this->useIncludePath;
  230. }
  231. /**
  232. * Turns off searching the prefix and fallback directories for classes
  233. * that have not been registered with the class map.
  234. *
  235. * @param bool $classMapAuthoritative
  236. */
  237. public function setClassMapAuthoritative($classMapAuthoritative)
  238. {
  239. $this->classMapAuthoritative = $classMapAuthoritative;
  240. }
  241. /**
  242. * Should class lookup fail if not found in the current class map?
  243. *
  244. * @return bool
  245. */
  246. public function isClassMapAuthoritative()
  247. {
  248. return $this->classMapAuthoritative;
  249. }
  250. /**
  251. * Registers this instance as an autoloader.
  252. *
  253. * @param bool $prepend Whether to prepend the autoloader or not
  254. */
  255. public function register($prepend = false)
  256. {
  257. spl_autoload_register(array($this, 'loadClass'), true, $prepend);
  258. }
  259. /**
  260. * Unregisters this instance as an autoloader.
  261. */
  262. public function unregister()
  263. {
  264. spl_autoload_unregister(array($this, 'loadClass'));
  265. }
  266. /**
  267. * Loads the given class or interface.
  268. *
  269. * @param string $class The name of the class
  270. * @return bool|null True if loaded, null otherwise
  271. */
  272. public function loadClass($class)
  273. {
  274. if ($file = $this->findFile($class)) {
  275. includeFile($file);
  276. return true;
  277. }
  278. }
  279. /**
  280. * Finds the path to the file where the class is defined.
  281. *
  282. * @param string $class The name of the class
  283. *
  284. * @return string|false The path if found, false otherwise
  285. */
  286. public function findFile($class)
  287. {
  288. // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
  289. if ('\\' == $class[0]) {
  290. $class = substr($class, 1);
  291. }
  292. // class map lookup
  293. if (isset($this->classMap[$class])) {
  294. return $this->classMap[$class];
  295. }
  296. if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
  297. return false;
  298. }
  299. $file = $this->findFileWithExtension($class, '.php');
  300. // Search for Hack files if we are running on HHVM
  301. if (false === $file && defined('HHVM_VERSION')) {
  302. $file = $this->findFileWithExtension($class, '.hh');
  303. }
  304. if (false === $file) {
  305. // Remember that this class does not exist.
  306. $this->missingClasses[$class] = true;
  307. }
  308. return $file;
  309. }
  310. private function findFileWithExtension($class, $ext)
  311. {
  312. // PSR-4 lookup
  313. $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
  314. $first = $class[0];
  315. if (isset($this->prefixLengthsPsr4[$first])) {
  316. foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
  317. if (0 === strpos($class, $prefix)) {
  318. foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
  319. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
  320. return $file;
  321. }
  322. }
  323. }
  324. }
  325. }
  326. // PSR-4 fallback dirs
  327. foreach ($this->fallbackDirsPsr4 as $dir) {
  328. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
  329. return $file;
  330. }
  331. }
  332. // PSR-0 lookup
  333. if (false !== $pos = strrpos($class, '\\')) {
  334. // namespaced class name
  335. $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
  336. . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
  337. } else {
  338. // PEAR-like class name
  339. $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
  340. }
  341. if (isset($this->prefixesPsr0[$first])) {
  342. foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
  343. if (0 === strpos($class, $prefix)) {
  344. foreach ($dirs as $dir) {
  345. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
  346. return $file;
  347. }
  348. }
  349. }
  350. }
  351. }
  352. // PSR-0 fallback dirs
  353. foreach ($this->fallbackDirsPsr0 as $dir) {
  354. if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
  355. return $file;
  356. }
  357. }
  358. // PSR-0 include paths.
  359. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
  360. return $file;
  361. }
  362. return false;
  363. }
  364. }
  365. /**
  366. * Scope isolated include.
  367. *
  368. * Prevents access to $this/self from included files.
  369. */
  370. function includeFile($file)
  371. {
  372. include $file;
  373. }