File indexing completed on 2025-05-04 05:32:15
0001 <?php 0002 0003 /* 0004 * This file is part of Composer. 0005 * 0006 * (c) Nils Adermann <naderman@naderman.de> 0007 * Jordi Boggiano <j.boggiano@seld.be> 0008 * 0009 * For the full copyright and license information, please view the LICENSE 0010 * file that was distributed with this source code. 0011 */ 0012 0013 namespace Composer\Autoload; 0014 0015 /** 0016 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 0017 * 0018 * $loader = new \Composer\Autoload\ClassLoader(); 0019 * 0020 * // register classes with namespaces 0021 * $loader->add('Symfony\Component', __DIR__.'/component'); 0022 * $loader->add('Symfony', __DIR__.'/framework'); 0023 * 0024 * // activate the autoloader 0025 * $loader->register(); 0026 * 0027 * // to enable searching the include path (eg. for PEAR packages) 0028 * $loader->setUseIncludePath(true); 0029 * 0030 * In this example, if you try to use a class in the Symfony\Component 0031 * namespace or one of its children (Symfony\Component\Console for instance), 0032 * the autoloader will first look for the class under the component/ 0033 * directory, and it will then fallback to the framework/ directory if not 0034 * found before giving up. 0035 * 0036 * This class is loosely based on the Symfony UniversalClassLoader. 0037 * 0038 * @author Fabien Potencier <fabien@symfony.com> 0039 * @author Jordi Boggiano <j.boggiano@seld.be> 0040 * @see http://www.php-fig.org/psr/psr-0/ 0041 * @see http://www.php-fig.org/psr/psr-4/ 0042 */ 0043 class ClassLoader 0044 { 0045 // PSR-4 0046 private $prefixLengthsPsr4 = array(); 0047 private $prefixDirsPsr4 = array(); 0048 private $fallbackDirsPsr4 = array(); 0049 0050 // PSR-0 0051 private $prefixesPsr0 = array(); 0052 private $fallbackDirsPsr0 = array(); 0053 0054 private $useIncludePath = false; 0055 private $classMap = array(); 0056 0057 private $classMapAuthoritative = false; 0058 0059 public function getPrefixes() 0060 { 0061 if (!empty($this->prefixesPsr0)) { 0062 return call_user_func_array('array_merge', $this->prefixesPsr0); 0063 } 0064 0065 return array(); 0066 } 0067 0068 public function getPrefixesPsr4() 0069 { 0070 return $this->prefixDirsPsr4; 0071 } 0072 0073 public function getFallbackDirs() 0074 { 0075 return $this->fallbackDirsPsr0; 0076 } 0077 0078 public function getFallbackDirsPsr4() 0079 { 0080 return $this->fallbackDirsPsr4; 0081 } 0082 0083 public function getClassMap() 0084 { 0085 return $this->classMap; 0086 } 0087 0088 /** 0089 * @param array $classMap Class to filename map 0090 */ 0091 public function addClassMap(array $classMap) 0092 { 0093 if ($this->classMap) { 0094 $this->classMap = array_merge($this->classMap, $classMap); 0095 } else { 0096 $this->classMap = $classMap; 0097 } 0098 } 0099 0100 /** 0101 * Registers a set of PSR-0 directories for a given prefix, either 0102 * appending or prepending to the ones previously set for this prefix. 0103 * 0104 * @param string $prefix The prefix 0105 * @param array|string $paths The PSR-0 root directories 0106 * @param bool $prepend Whether to prepend the directories 0107 */ 0108 public function add($prefix, $paths, $prepend = false) 0109 { 0110 if (!$prefix) { 0111 if ($prepend) { 0112 $this->fallbackDirsPsr0 = array_merge( 0113 (array) $paths, 0114 $this->fallbackDirsPsr0 0115 ); 0116 } else { 0117 $this->fallbackDirsPsr0 = array_merge( 0118 $this->fallbackDirsPsr0, 0119 (array) $paths 0120 ); 0121 } 0122 0123 return; 0124 } 0125 0126 $first = $prefix[0]; 0127 if (!isset($this->prefixesPsr0[$first][$prefix])) { 0128 $this->prefixesPsr0[$first][$prefix] = (array) $paths; 0129 0130 return; 0131 } 0132 if ($prepend) { 0133 $this->prefixesPsr0[$first][$prefix] = array_merge( 0134 (array) $paths, 0135 $this->prefixesPsr0[$first][$prefix] 0136 ); 0137 } else { 0138 $this->prefixesPsr0[$first][$prefix] = array_merge( 0139 $this->prefixesPsr0[$first][$prefix], 0140 (array) $paths 0141 ); 0142 } 0143 } 0144 0145 /** 0146 * Registers a set of PSR-4 directories for a given namespace, either 0147 * appending or prepending to the ones previously set for this namespace. 0148 * 0149 * @param string $prefix The prefix/namespace, with trailing '\\' 0150 * @param array|string $paths The PSR-4 base directories 0151 * @param bool $prepend Whether to prepend the directories 0152 * 0153 * @throws \InvalidArgumentException 0154 */ 0155 public function addPsr4($prefix, $paths, $prepend = false) 0156 { 0157 if (!$prefix) { 0158 // Register directories for the root namespace. 0159 if ($prepend) { 0160 $this->fallbackDirsPsr4 = array_merge( 0161 (array) $paths, 0162 $this->fallbackDirsPsr4 0163 ); 0164 } else { 0165 $this->fallbackDirsPsr4 = array_merge( 0166 $this->fallbackDirsPsr4, 0167 (array) $paths 0168 ); 0169 } 0170 } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 0171 // Register directories for a new namespace. 0172 $length = strlen($prefix); 0173 if ('\\' !== $prefix[$length - 1]) { 0174 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 0175 } 0176 $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 0177 $this->prefixDirsPsr4[$prefix] = (array) $paths; 0178 } elseif ($prepend) { 0179 // Prepend directories for an already registered namespace. 0180 $this->prefixDirsPsr4[$prefix] = array_merge( 0181 (array) $paths, 0182 $this->prefixDirsPsr4[$prefix] 0183 ); 0184 } else { 0185 // Append directories for an already registered namespace. 0186 $this->prefixDirsPsr4[$prefix] = array_merge( 0187 $this->prefixDirsPsr4[$prefix], 0188 (array) $paths 0189 ); 0190 } 0191 } 0192 0193 /** 0194 * Registers a set of PSR-0 directories for a given prefix, 0195 * replacing any others previously set for this prefix. 0196 * 0197 * @param string $prefix The prefix 0198 * @param array|string $paths The PSR-0 base directories 0199 */ 0200 public function set($prefix, $paths) 0201 { 0202 if (!$prefix) { 0203 $this->fallbackDirsPsr0 = (array) $paths; 0204 } else { 0205 $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 0206 } 0207 } 0208 0209 /** 0210 * Registers a set of PSR-4 directories for a given namespace, 0211 * replacing any others previously set for this namespace. 0212 * 0213 * @param string $prefix The prefix/namespace, with trailing '\\' 0214 * @param array|string $paths The PSR-4 base directories 0215 * 0216 * @throws \InvalidArgumentException 0217 */ 0218 public function setPsr4($prefix, $paths) 0219 { 0220 if (!$prefix) { 0221 $this->fallbackDirsPsr4 = (array) $paths; 0222 } else { 0223 $length = strlen($prefix); 0224 if ('\\' !== $prefix[$length - 1]) { 0225 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 0226 } 0227 $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 0228 $this->prefixDirsPsr4[$prefix] = (array) $paths; 0229 } 0230 } 0231 0232 /** 0233 * Turns on searching the include path for class files. 0234 * 0235 * @param bool $useIncludePath 0236 */ 0237 public function setUseIncludePath($useIncludePath) 0238 { 0239 $this->useIncludePath = $useIncludePath; 0240 } 0241 0242 /** 0243 * Can be used to check if the autoloader uses the include path to check 0244 * for classes. 0245 * 0246 * @return bool 0247 */ 0248 public function getUseIncludePath() 0249 { 0250 return $this->useIncludePath; 0251 } 0252 0253 /** 0254 * Turns off searching the prefix and fallback directories for classes 0255 * that have not been registered with the class map. 0256 * 0257 * @param bool $classMapAuthoritative 0258 */ 0259 public function setClassMapAuthoritative($classMapAuthoritative) 0260 { 0261 $this->classMapAuthoritative = $classMapAuthoritative; 0262 } 0263 0264 /** 0265 * Should class lookup fail if not found in the current class map? 0266 * 0267 * @return bool 0268 */ 0269 public function isClassMapAuthoritative() 0270 { 0271 return $this->classMapAuthoritative; 0272 } 0273 0274 /** 0275 * Registers this instance as an autoloader. 0276 * 0277 * @param bool $prepend Whether to prepend the autoloader or not 0278 */ 0279 public function register($prepend = false) 0280 { 0281 spl_autoload_register(array($this, 'loadClass'), true, $prepend); 0282 } 0283 0284 /** 0285 * Unregisters this instance as an autoloader. 0286 */ 0287 public function unregister() 0288 { 0289 spl_autoload_unregister(array($this, 'loadClass')); 0290 } 0291 0292 /** 0293 * Loads the given class or interface. 0294 * 0295 * @param string $class The name of the class 0296 * @return bool|null True if loaded, null otherwise 0297 */ 0298 public function loadClass($class) 0299 { 0300 if ($file = $this->findFile($class)) { 0301 includeFile($file); 0302 0303 return true; 0304 } 0305 } 0306 0307 /** 0308 * Finds the path to the file where the class is defined. 0309 * 0310 * @param string $class The name of the class 0311 * 0312 * @return string|false The path if found, false otherwise 0313 */ 0314 public function findFile($class) 0315 { 0316 // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 0317 if ('\\' == $class[0]) { 0318 $class = substr($class, 1); 0319 } 0320 0321 // class map lookup 0322 if (isset($this->classMap[$class])) { 0323 return $this->classMap[$class]; 0324 } 0325 if ($this->classMapAuthoritative) { 0326 return false; 0327 } 0328 0329 $file = $this->findFileWithExtension($class, '.php'); 0330 0331 // Search for Hack files if we are running on HHVM 0332 if ($file === null && defined('HHVM_VERSION')) { 0333 $file = $this->findFileWithExtension($class, '.hh'); 0334 } 0335 0336 if ($file === null) { 0337 // Remember that this class does not exist. 0338 return $this->classMap[$class] = false; 0339 } 0340 0341 return $file; 0342 } 0343 0344 private function findFileWithExtension($class, $ext) 0345 { 0346 // PSR-4 lookup 0347 $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 0348 0349 $first = $class[0]; 0350 if (isset($this->prefixLengthsPsr4[$first])) { 0351 foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { 0352 if (0 === strpos($class, $prefix)) { 0353 foreach ($this->prefixDirsPsr4[$prefix] as $dir) { 0354 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 0355 return $file; 0356 } 0357 } 0358 } 0359 } 0360 } 0361 0362 // PSR-4 fallback dirs 0363 foreach ($this->fallbackDirsPsr4 as $dir) { 0364 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 0365 return $file; 0366 } 0367 } 0368 0369 // PSR-0 lookup 0370 if (false !== $pos = strrpos($class, '\\')) { 0371 // namespaced class name 0372 $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 0373 . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 0374 } else { 0375 // PEAR-like class name 0376 $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 0377 } 0378 0379 if (isset($this->prefixesPsr0[$first])) { 0380 foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 0381 if (0 === strpos($class, $prefix)) { 0382 foreach ($dirs as $dir) { 0383 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 0384 return $file; 0385 } 0386 } 0387 } 0388 } 0389 } 0390 0391 // PSR-0 fallback dirs 0392 foreach ($this->fallbackDirsPsr0 as $dir) { 0393 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 0394 return $file; 0395 } 0396 } 0397 0398 // PSR-0 include paths. 0399 if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 0400 return $file; 0401 } 0402 } 0403 } 0404 0405 /** 0406 * Scope isolated include. 0407 * 0408 * Prevents access to $this/self from included files. 0409 */ 0410 function includeFile($file) 0411 { 0412 include $file; 0413 }