ClassLoader.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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 class loader
  14. *
  15. * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
  16. *
  17. * $loader = new \Composer\Autoload\ClassLoader();
  18. *
  19. * // register classes with namespaces
  20. * $loader->add('Symfony\Component', __DIR__.'/component');
  21. * $loader->add('Symfony', __DIR__.'/framework');
  22. *
  23. * // activate the autoloader
  24. * $loader->register();
  25. *
  26. * // to enable searching the include path (eg. for PEAR packages)
  27. * $loader->setUseIncludePath(true);
  28. *
  29. * In this example, if you try to use a class in the Symfony\Component
  30. * namespace or one of its children (Symfony\Component\Console for instance),
  31. * the autoloader will first look for the class under the component/
  32. * directory, and it will then fallback to the framework/ directory if not
  33. * found before giving up.
  34. *
  35. * This class is loosely based on the Symfony UniversalClassLoader.
  36. *
  37. * @author Fabien Potencier <fabien@symfony.com>
  38. * @author Jordi Boggiano <j.boggiano@seld.be>
  39. */
  40. class ClassLoader
  41. {
  42. private $prefixes = array();
  43. private $fallbackDirs = array();
  44. private $useIncludePath = false;
  45. private $classMap = array();
  46. public function getPrefixes()
  47. {
  48. return call_user_func_array('array_merge', $this->prefixes);
  49. }
  50. public function getFallbackDirs()
  51. {
  52. return $this->fallbackDirs;
  53. }
  54. public function getClassMap()
  55. {
  56. return $this->classMap;
  57. }
  58. /**
  59. * @param array $classMap Class to filename map
  60. */
  61. public function addClassMap(array $classMap)
  62. {
  63. if ($this->classMap) {
  64. $this->classMap = array_merge($this->classMap, $classMap);
  65. } else {
  66. $this->classMap = $classMap;
  67. }
  68. }
  69. /**
  70. * Registers a set of classes, merging with any others previously set.
  71. *
  72. * @param string $prefix The classes prefix
  73. * @param array|string $paths The location(s) of the classes
  74. * @param bool $prepend Prepend the location(s)
  75. */
  76. public function add($prefix, $paths, $prepend = false)
  77. {
  78. if (!$prefix) {
  79. if ($prepend) {
  80. $this->fallbackDirs = array_merge(
  81. (array) $paths,
  82. $this->fallbackDirs
  83. );
  84. } else {
  85. $this->fallbackDirs = array_merge(
  86. $this->fallbackDirs,
  87. (array) $paths
  88. );
  89. }
  90. return;
  91. }
  92. $first = $prefix[0];
  93. if (!isset($this->prefixes[$first][$prefix])) {
  94. $this->prefixes[$first][$prefix] = (array) $paths;
  95. return;
  96. }
  97. if ($prepend) {
  98. $this->prefixes[$first][$prefix] = array_merge(
  99. (array) $paths,
  100. $this->prefixes[$first][$prefix]
  101. );
  102. } else {
  103. $this->prefixes[$first][$prefix] = array_merge(
  104. $this->prefixes[$first][$prefix],
  105. (array) $paths
  106. );
  107. }
  108. }
  109. /**
  110. * Registers a set of classes, replacing any others previously set.
  111. *
  112. * @param string $prefix The classes prefix
  113. * @param array|string $paths The location(s) of the classes
  114. */
  115. public function set($prefix, $paths)
  116. {
  117. if (!$prefix) {
  118. $this->fallbackDirs = (array) $paths;
  119. return;
  120. }
  121. $this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths;
  122. }
  123. /**
  124. * Turns on searching the include path for class files.
  125. *
  126. * @param bool $useIncludePath
  127. */
  128. public function setUseIncludePath($useIncludePath)
  129. {
  130. $this->useIncludePath = $useIncludePath;
  131. }
  132. /**
  133. * Can be used to check if the autoloader uses the include path to check
  134. * for classes.
  135. *
  136. * @return bool
  137. */
  138. public function getUseIncludePath()
  139. {
  140. return $this->useIncludePath;
  141. }
  142. /**
  143. * Registers this instance as an autoloader.
  144. *
  145. * @param bool $prepend Whether to prepend the autoloader or not
  146. */
  147. public function register($prepend = false)
  148. {
  149. spl_autoload_register(array($this, 'loadClass'), true, $prepend);
  150. }
  151. /**
  152. * Unregisters this instance as an autoloader.
  153. */
  154. public function unregister()
  155. {
  156. spl_autoload_unregister(array($this, 'loadClass'));
  157. }
  158. /**
  159. * Loads the given class or interface.
  160. *
  161. * @param string $class The name of the class
  162. * @return bool|null True if loaded, null otherwise
  163. */
  164. public function loadClass($class)
  165. {
  166. if ($file = $this->findFile($class)) {
  167. include $file;
  168. return true;
  169. }
  170. }
  171. /**
  172. * Finds the path to the file where the class is defined.
  173. *
  174. * @param string $class The name of the class
  175. *
  176. * @return string|false The path if found, false otherwise
  177. */
  178. public function findFile($class)
  179. {
  180. // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
  181. if ('\\' == $class[0]) {
  182. $class = substr($class, 1);
  183. }
  184. if (isset($this->classMap[$class])) {
  185. return $this->classMap[$class];
  186. }
  187. if (false !== $pos = strrpos($class, '\\')) {
  188. // namespaced class name
  189. $classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
  190. $className = substr($class, $pos + 1);
  191. } else {
  192. // PEAR-like class name
  193. $classPath = null;
  194. $className = $class;
  195. }
  196. $classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php';
  197. $first = $class[0];
  198. if (isset($this->prefixes[$first])) {
  199. foreach ($this->prefixes[$first] as $prefix => $dirs) {
  200. if (0 === strpos($class, $prefix)) {
  201. foreach ($dirs as $dir) {
  202. if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
  203. return $dir . DIRECTORY_SEPARATOR . $classPath;
  204. }
  205. }
  206. }
  207. }
  208. }
  209. foreach ($this->fallbackDirs as $dir) {
  210. if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) {
  211. return $dir . DIRECTORY_SEPARATOR . $classPath;
  212. }
  213. }
  214. if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) {
  215. return $file;
  216. }
  217. return $this->classMap[$class] = false;
  218. }
  219. }