1605f8e8dSAndreas Gohr<?php 2605f8e8dSAndreas Gohr 3605f8e8dSAndreas Gohr/* 4605f8e8dSAndreas Gohr * This file is part of Composer. 5605f8e8dSAndreas Gohr * 6605f8e8dSAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 7605f8e8dSAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 8605f8e8dSAndreas Gohr * 9605f8e8dSAndreas Gohr * For the full copyright and license information, please view the LICENSE 10605f8e8dSAndreas Gohr * file that was distributed with this source code. 11605f8e8dSAndreas Gohr */ 12605f8e8dSAndreas Gohr 13605f8e8dSAndreas Gohrnamespace Composer\Autoload; 14605f8e8dSAndreas Gohr 15605f8e8dSAndreas Gohr/** 167a33d2f8SNiklas Keller * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17605f8e8dSAndreas Gohr * 18605f8e8dSAndreas Gohr * $loader = new \Composer\Autoload\ClassLoader(); 19605f8e8dSAndreas Gohr * 20605f8e8dSAndreas Gohr * // register classes with namespaces 21605f8e8dSAndreas Gohr * $loader->add('Symfony\Component', __DIR__.'/component'); 22605f8e8dSAndreas Gohr * $loader->add('Symfony', __DIR__.'/framework'); 23605f8e8dSAndreas Gohr * 24605f8e8dSAndreas Gohr * // activate the autoloader 25605f8e8dSAndreas Gohr * $loader->register(); 26605f8e8dSAndreas Gohr * 27605f8e8dSAndreas Gohr * // to enable searching the include path (eg. for PEAR packages) 28605f8e8dSAndreas Gohr * $loader->setUseIncludePath(true); 29605f8e8dSAndreas Gohr * 30605f8e8dSAndreas Gohr * In this example, if you try to use a class in the Symfony\Component 31605f8e8dSAndreas Gohr * namespace or one of its children (Symfony\Component\Console for instance), 32605f8e8dSAndreas Gohr * the autoloader will first look for the class under the component/ 33605f8e8dSAndreas Gohr * directory, and it will then fallback to the framework/ directory if not 34605f8e8dSAndreas Gohr * found before giving up. 35605f8e8dSAndreas Gohr * 36605f8e8dSAndreas Gohr * This class is loosely based on the Symfony UniversalClassLoader. 37605f8e8dSAndreas Gohr * 38605f8e8dSAndreas Gohr * @author Fabien Potencier <fabien@symfony.com> 39605f8e8dSAndreas Gohr * @author Jordi Boggiano <j.boggiano@seld.be> 406cb05674SAndreas Gohr * @see https://www.php-fig.org/psr/psr-0/ 416cb05674SAndreas Gohr * @see https://www.php-fig.org/psr/psr-4/ 42605f8e8dSAndreas Gohr */ 43605f8e8dSAndreas Gohrclass ClassLoader 44605f8e8dSAndreas Gohr{ 45*28e9760aSAndreas Gohr /** @var \Closure(string):void */ 46*28e9760aSAndreas Gohr private static $includeFile; 47*28e9760aSAndreas Gohr 48d3233986SAndreas Gohr /** @var ?string */ 496cb05674SAndreas Gohr private $vendorDir; 506cb05674SAndreas Gohr 51605f8e8dSAndreas Gohr // PSR-4 52d3233986SAndreas Gohr /** 53d3233986SAndreas Gohr * @var array[] 54d3233986SAndreas Gohr * @psalm-var array<string, array<string, int>> 55d3233986SAndreas Gohr */ 56605f8e8dSAndreas Gohr private $prefixLengthsPsr4 = array(); 57d3233986SAndreas Gohr /** 58d3233986SAndreas Gohr * @var array[] 59d3233986SAndreas Gohr * @psalm-var array<string, array<int, string>> 60d3233986SAndreas Gohr */ 61605f8e8dSAndreas Gohr private $prefixDirsPsr4 = array(); 62d3233986SAndreas Gohr /** 63d3233986SAndreas Gohr * @var array[] 64d3233986SAndreas Gohr * @psalm-var array<string, string> 65d3233986SAndreas Gohr */ 66605f8e8dSAndreas Gohr private $fallbackDirsPsr4 = array(); 67605f8e8dSAndreas Gohr 68605f8e8dSAndreas Gohr // PSR-0 69d3233986SAndreas Gohr /** 70d3233986SAndreas Gohr * @var array[] 71d3233986SAndreas Gohr * @psalm-var array<string, array<string, string[]>> 72d3233986SAndreas Gohr */ 73605f8e8dSAndreas Gohr private $prefixesPsr0 = array(); 74d3233986SAndreas Gohr /** 75d3233986SAndreas Gohr * @var array[] 76d3233986SAndreas Gohr * @psalm-var array<string, string> 77d3233986SAndreas Gohr */ 78605f8e8dSAndreas Gohr private $fallbackDirsPsr0 = array(); 79605f8e8dSAndreas Gohr 80d3233986SAndreas Gohr /** @var bool */ 81605f8e8dSAndreas Gohr private $useIncludePath = false; 82d3233986SAndreas Gohr 83d3233986SAndreas Gohr /** 84d3233986SAndreas Gohr * @var string[] 85d3233986SAndreas Gohr * @psalm-var array<string, string> 86d3233986SAndreas Gohr */ 87605f8e8dSAndreas Gohr private $classMap = array(); 88d3233986SAndreas Gohr 89d3233986SAndreas Gohr /** @var bool */ 90605f8e8dSAndreas Gohr private $classMapAuthoritative = false; 91d3233986SAndreas Gohr 92d3233986SAndreas Gohr /** 93d3233986SAndreas Gohr * @var bool[] 94d3233986SAndreas Gohr * @psalm-var array<string, bool> 95d3233986SAndreas Gohr */ 967a33d2f8SNiklas Keller private $missingClasses = array(); 97d3233986SAndreas Gohr 98d3233986SAndreas Gohr /** @var ?string */ 99e0dd796dSAndreas Gohr private $apcuPrefix; 100605f8e8dSAndreas Gohr 101d3233986SAndreas Gohr /** 102d3233986SAndreas Gohr * @var self[] 103d3233986SAndreas Gohr */ 1046cb05674SAndreas Gohr private static $registeredLoaders = array(); 1056cb05674SAndreas Gohr 106d3233986SAndreas Gohr /** 107d3233986SAndreas Gohr * @param ?string $vendorDir 108d3233986SAndreas Gohr */ 1096cb05674SAndreas Gohr public function __construct($vendorDir = null) 1106cb05674SAndreas Gohr { 1116cb05674SAndreas Gohr $this->vendorDir = $vendorDir; 112*28e9760aSAndreas Gohr self::initializeIncludeClosure(); 1136cb05674SAndreas Gohr } 1146cb05674SAndreas Gohr 115d3233986SAndreas Gohr /** 116d3233986SAndreas Gohr * @return string[] 117d3233986SAndreas Gohr */ 118605f8e8dSAndreas Gohr public function getPrefixes() 119605f8e8dSAndreas Gohr { 120605f8e8dSAndreas Gohr if (!empty($this->prefixesPsr0)) { 121a3bfbb3cSAndreas Gohr return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 122605f8e8dSAndreas Gohr } 123605f8e8dSAndreas Gohr 124605f8e8dSAndreas Gohr return array(); 125605f8e8dSAndreas Gohr } 126605f8e8dSAndreas Gohr 127d3233986SAndreas Gohr /** 128d3233986SAndreas Gohr * @return array[] 129d3233986SAndreas Gohr * @psalm-return array<string, array<int, string>> 130d3233986SAndreas Gohr */ 131605f8e8dSAndreas Gohr public function getPrefixesPsr4() 132605f8e8dSAndreas Gohr { 133605f8e8dSAndreas Gohr return $this->prefixDirsPsr4; 134605f8e8dSAndreas Gohr } 135605f8e8dSAndreas Gohr 136d3233986SAndreas Gohr /** 137d3233986SAndreas Gohr * @return array[] 138d3233986SAndreas Gohr * @psalm-return array<string, string> 139d3233986SAndreas Gohr */ 140605f8e8dSAndreas Gohr public function getFallbackDirs() 141605f8e8dSAndreas Gohr { 142605f8e8dSAndreas Gohr return $this->fallbackDirsPsr0; 143605f8e8dSAndreas Gohr } 144605f8e8dSAndreas Gohr 145d3233986SAndreas Gohr /** 146d3233986SAndreas Gohr * @return array[] 147d3233986SAndreas Gohr * @psalm-return array<string, string> 148d3233986SAndreas Gohr */ 149605f8e8dSAndreas Gohr public function getFallbackDirsPsr4() 150605f8e8dSAndreas Gohr { 151605f8e8dSAndreas Gohr return $this->fallbackDirsPsr4; 152605f8e8dSAndreas Gohr } 153605f8e8dSAndreas Gohr 154d3233986SAndreas Gohr /** 155d3233986SAndreas Gohr * @return string[] Array of classname => path 156d3233986SAndreas Gohr * @psalm-return array<string, string> 157d3233986SAndreas Gohr */ 158605f8e8dSAndreas Gohr public function getClassMap() 159605f8e8dSAndreas Gohr { 160605f8e8dSAndreas Gohr return $this->classMap; 161605f8e8dSAndreas Gohr } 162605f8e8dSAndreas Gohr 163605f8e8dSAndreas Gohr /** 164d3233986SAndreas Gohr * @param string[] $classMap Class to filename map 165d3233986SAndreas Gohr * @psalm-param array<string, string> $classMap 166d3233986SAndreas Gohr * 167d3233986SAndreas Gohr * @return void 168605f8e8dSAndreas Gohr */ 169605f8e8dSAndreas Gohr public function addClassMap(array $classMap) 170605f8e8dSAndreas Gohr { 171605f8e8dSAndreas Gohr if ($this->classMap) { 172605f8e8dSAndreas Gohr $this->classMap = array_merge($this->classMap, $classMap); 173605f8e8dSAndreas Gohr } else { 174605f8e8dSAndreas Gohr $this->classMap = $classMap; 175605f8e8dSAndreas Gohr } 176605f8e8dSAndreas Gohr } 177605f8e8dSAndreas Gohr 178605f8e8dSAndreas Gohr /** 179605f8e8dSAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, either 180605f8e8dSAndreas Gohr * appending or prepending to the ones previously set for this prefix. 181605f8e8dSAndreas Gohr * 182605f8e8dSAndreas Gohr * @param string $prefix The prefix 183d3233986SAndreas Gohr * @param string[]|string $paths The PSR-0 root directories 184605f8e8dSAndreas Gohr * @param bool $prepend Whether to prepend the directories 185d3233986SAndreas Gohr * 186d3233986SAndreas Gohr * @return void 187605f8e8dSAndreas Gohr */ 188605f8e8dSAndreas Gohr public function add($prefix, $paths, $prepend = false) 189605f8e8dSAndreas Gohr { 190605f8e8dSAndreas Gohr if (!$prefix) { 191605f8e8dSAndreas Gohr if ($prepend) { 192605f8e8dSAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 193605f8e8dSAndreas Gohr (array) $paths, 194605f8e8dSAndreas Gohr $this->fallbackDirsPsr0 195605f8e8dSAndreas Gohr ); 196605f8e8dSAndreas Gohr } else { 197605f8e8dSAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 198605f8e8dSAndreas Gohr $this->fallbackDirsPsr0, 199605f8e8dSAndreas Gohr (array) $paths 200605f8e8dSAndreas Gohr ); 201605f8e8dSAndreas Gohr } 202605f8e8dSAndreas Gohr 203605f8e8dSAndreas Gohr return; 204605f8e8dSAndreas Gohr } 205605f8e8dSAndreas Gohr 206605f8e8dSAndreas Gohr $first = $prefix[0]; 207605f8e8dSAndreas Gohr if (!isset($this->prefixesPsr0[$first][$prefix])) { 208605f8e8dSAndreas Gohr $this->prefixesPsr0[$first][$prefix] = (array) $paths; 209605f8e8dSAndreas Gohr 210605f8e8dSAndreas Gohr return; 211605f8e8dSAndreas Gohr } 212605f8e8dSAndreas Gohr if ($prepend) { 213605f8e8dSAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 214605f8e8dSAndreas Gohr (array) $paths, 215605f8e8dSAndreas Gohr $this->prefixesPsr0[$first][$prefix] 216605f8e8dSAndreas Gohr ); 217605f8e8dSAndreas Gohr } else { 218605f8e8dSAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 219605f8e8dSAndreas Gohr $this->prefixesPsr0[$first][$prefix], 220605f8e8dSAndreas Gohr (array) $paths 221605f8e8dSAndreas Gohr ); 222605f8e8dSAndreas Gohr } 223605f8e8dSAndreas Gohr } 224605f8e8dSAndreas Gohr 225605f8e8dSAndreas Gohr /** 226605f8e8dSAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, either 227605f8e8dSAndreas Gohr * appending or prepending to the ones previously set for this namespace. 228605f8e8dSAndreas Gohr * 229605f8e8dSAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 230d3233986SAndreas Gohr * @param string[]|string $paths The PSR-4 base directories 231605f8e8dSAndreas Gohr * @param bool $prepend Whether to prepend the directories 232605f8e8dSAndreas Gohr * 233605f8e8dSAndreas Gohr * @throws \InvalidArgumentException 234d3233986SAndreas Gohr * 235d3233986SAndreas Gohr * @return void 236605f8e8dSAndreas Gohr */ 237605f8e8dSAndreas Gohr public function addPsr4($prefix, $paths, $prepend = false) 238605f8e8dSAndreas Gohr { 239605f8e8dSAndreas Gohr if (!$prefix) { 240605f8e8dSAndreas Gohr // Register directories for the root namespace. 241605f8e8dSAndreas Gohr if ($prepend) { 242605f8e8dSAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 243605f8e8dSAndreas Gohr (array) $paths, 244605f8e8dSAndreas Gohr $this->fallbackDirsPsr4 245605f8e8dSAndreas Gohr ); 246605f8e8dSAndreas Gohr } else { 247605f8e8dSAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 248605f8e8dSAndreas Gohr $this->fallbackDirsPsr4, 249605f8e8dSAndreas Gohr (array) $paths 250605f8e8dSAndreas Gohr ); 251605f8e8dSAndreas Gohr } 252605f8e8dSAndreas Gohr } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 253605f8e8dSAndreas Gohr // Register directories for a new namespace. 254605f8e8dSAndreas Gohr $length = strlen($prefix); 255605f8e8dSAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 256605f8e8dSAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 257605f8e8dSAndreas Gohr } 258605f8e8dSAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 259605f8e8dSAndreas Gohr $this->prefixDirsPsr4[$prefix] = (array) $paths; 260605f8e8dSAndreas Gohr } elseif ($prepend) { 261605f8e8dSAndreas Gohr // Prepend directories for an already registered namespace. 262605f8e8dSAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 263605f8e8dSAndreas Gohr (array) $paths, 264605f8e8dSAndreas Gohr $this->prefixDirsPsr4[$prefix] 265605f8e8dSAndreas Gohr ); 266605f8e8dSAndreas Gohr } else { 267605f8e8dSAndreas Gohr // Append directories for an already registered namespace. 268605f8e8dSAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 269605f8e8dSAndreas Gohr $this->prefixDirsPsr4[$prefix], 270605f8e8dSAndreas Gohr (array) $paths 271605f8e8dSAndreas Gohr ); 272605f8e8dSAndreas Gohr } 273605f8e8dSAndreas Gohr } 274605f8e8dSAndreas Gohr 275605f8e8dSAndreas Gohr /** 276605f8e8dSAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, 277605f8e8dSAndreas Gohr * replacing any others previously set for this prefix. 278605f8e8dSAndreas Gohr * 279605f8e8dSAndreas Gohr * @param string $prefix The prefix 280d3233986SAndreas Gohr * @param string[]|string $paths The PSR-0 base directories 281d3233986SAndreas Gohr * 282d3233986SAndreas Gohr * @return void 283605f8e8dSAndreas Gohr */ 284605f8e8dSAndreas Gohr public function set($prefix, $paths) 285605f8e8dSAndreas Gohr { 286605f8e8dSAndreas Gohr if (!$prefix) { 287605f8e8dSAndreas Gohr $this->fallbackDirsPsr0 = (array) $paths; 288605f8e8dSAndreas Gohr } else { 289605f8e8dSAndreas Gohr $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 290605f8e8dSAndreas Gohr } 291605f8e8dSAndreas Gohr } 292605f8e8dSAndreas Gohr 293605f8e8dSAndreas Gohr /** 294605f8e8dSAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, 295605f8e8dSAndreas Gohr * replacing any others previously set for this namespace. 296605f8e8dSAndreas Gohr * 297605f8e8dSAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 298d3233986SAndreas Gohr * @param string[]|string $paths The PSR-4 base directories 299605f8e8dSAndreas Gohr * 300605f8e8dSAndreas Gohr * @throws \InvalidArgumentException 301d3233986SAndreas Gohr * 302d3233986SAndreas Gohr * @return void 303605f8e8dSAndreas Gohr */ 304605f8e8dSAndreas Gohr public function setPsr4($prefix, $paths) 305605f8e8dSAndreas Gohr { 306605f8e8dSAndreas Gohr if (!$prefix) { 307605f8e8dSAndreas Gohr $this->fallbackDirsPsr4 = (array) $paths; 308605f8e8dSAndreas Gohr } else { 309605f8e8dSAndreas Gohr $length = strlen($prefix); 310605f8e8dSAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 311605f8e8dSAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 312605f8e8dSAndreas Gohr } 313605f8e8dSAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 314605f8e8dSAndreas Gohr $this->prefixDirsPsr4[$prefix] = (array) $paths; 315605f8e8dSAndreas Gohr } 316605f8e8dSAndreas Gohr } 317605f8e8dSAndreas Gohr 318605f8e8dSAndreas Gohr /** 319605f8e8dSAndreas Gohr * Turns on searching the include path for class files. 320605f8e8dSAndreas Gohr * 321605f8e8dSAndreas Gohr * @param bool $useIncludePath 322d3233986SAndreas Gohr * 323d3233986SAndreas Gohr * @return void 324605f8e8dSAndreas Gohr */ 325605f8e8dSAndreas Gohr public function setUseIncludePath($useIncludePath) 326605f8e8dSAndreas Gohr { 327605f8e8dSAndreas Gohr $this->useIncludePath = $useIncludePath; 328605f8e8dSAndreas Gohr } 329605f8e8dSAndreas Gohr 330605f8e8dSAndreas Gohr /** 331605f8e8dSAndreas Gohr * Can be used to check if the autoloader uses the include path to check 332605f8e8dSAndreas Gohr * for classes. 333605f8e8dSAndreas Gohr * 334605f8e8dSAndreas Gohr * @return bool 335605f8e8dSAndreas Gohr */ 336605f8e8dSAndreas Gohr public function getUseIncludePath() 337605f8e8dSAndreas Gohr { 338605f8e8dSAndreas Gohr return $this->useIncludePath; 339605f8e8dSAndreas Gohr } 340605f8e8dSAndreas Gohr 341605f8e8dSAndreas Gohr /** 342605f8e8dSAndreas Gohr * Turns off searching the prefix and fallback directories for classes 343605f8e8dSAndreas Gohr * that have not been registered with the class map. 344605f8e8dSAndreas Gohr * 345605f8e8dSAndreas Gohr * @param bool $classMapAuthoritative 346d3233986SAndreas Gohr * 347d3233986SAndreas Gohr * @return void 348605f8e8dSAndreas Gohr */ 349605f8e8dSAndreas Gohr public function setClassMapAuthoritative($classMapAuthoritative) 350605f8e8dSAndreas Gohr { 351605f8e8dSAndreas Gohr $this->classMapAuthoritative = $classMapAuthoritative; 352605f8e8dSAndreas Gohr } 353605f8e8dSAndreas Gohr 354605f8e8dSAndreas Gohr /** 355605f8e8dSAndreas Gohr * Should class lookup fail if not found in the current class map? 356605f8e8dSAndreas Gohr * 357605f8e8dSAndreas Gohr * @return bool 358605f8e8dSAndreas Gohr */ 359605f8e8dSAndreas Gohr public function isClassMapAuthoritative() 360605f8e8dSAndreas Gohr { 361605f8e8dSAndreas Gohr return $this->classMapAuthoritative; 362605f8e8dSAndreas Gohr } 363605f8e8dSAndreas Gohr 364605f8e8dSAndreas Gohr /** 365e0dd796dSAndreas Gohr * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 366e0dd796dSAndreas Gohr * 367e0dd796dSAndreas Gohr * @param string|null $apcuPrefix 368d3233986SAndreas Gohr * 369d3233986SAndreas Gohr * @return void 370e0dd796dSAndreas Gohr */ 371e0dd796dSAndreas Gohr public function setApcuPrefix($apcuPrefix) 372e0dd796dSAndreas Gohr { 373e43cd7e1SAndreas Gohr $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 374e0dd796dSAndreas Gohr } 375e0dd796dSAndreas Gohr 376e0dd796dSAndreas Gohr /** 377e0dd796dSAndreas Gohr * The APCu prefix in use, or null if APCu caching is not enabled. 378e0dd796dSAndreas Gohr * 379e0dd796dSAndreas Gohr * @return string|null 380e0dd796dSAndreas Gohr */ 381e0dd796dSAndreas Gohr public function getApcuPrefix() 382e0dd796dSAndreas Gohr { 383e0dd796dSAndreas Gohr return $this->apcuPrefix; 384e0dd796dSAndreas Gohr } 385e0dd796dSAndreas Gohr 386e0dd796dSAndreas Gohr /** 387605f8e8dSAndreas Gohr * Registers this instance as an autoloader. 388605f8e8dSAndreas Gohr * 389605f8e8dSAndreas Gohr * @param bool $prepend Whether to prepend the autoloader or not 390d3233986SAndreas Gohr * 391d3233986SAndreas Gohr * @return void 392605f8e8dSAndreas Gohr */ 393605f8e8dSAndreas Gohr public function register($prepend = false) 394605f8e8dSAndreas Gohr { 395605f8e8dSAndreas Gohr spl_autoload_register(array($this, 'loadClass'), true, $prepend); 3966cb05674SAndreas Gohr 3976cb05674SAndreas Gohr if (null === $this->vendorDir) { 3986cb05674SAndreas Gohr return; 3996cb05674SAndreas Gohr } 4006cb05674SAndreas Gohr 4016cb05674SAndreas Gohr if ($prepend) { 4026cb05674SAndreas Gohr self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 4036cb05674SAndreas Gohr } else { 4046cb05674SAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 4056cb05674SAndreas Gohr self::$registeredLoaders[$this->vendorDir] = $this; 4066cb05674SAndreas Gohr } 407605f8e8dSAndreas Gohr } 408605f8e8dSAndreas Gohr 409605f8e8dSAndreas Gohr /** 410605f8e8dSAndreas Gohr * Unregisters this instance as an autoloader. 411d3233986SAndreas Gohr * 412d3233986SAndreas Gohr * @return void 413605f8e8dSAndreas Gohr */ 414605f8e8dSAndreas Gohr public function unregister() 415605f8e8dSAndreas Gohr { 416605f8e8dSAndreas Gohr spl_autoload_unregister(array($this, 'loadClass')); 4176cb05674SAndreas Gohr 4186cb05674SAndreas Gohr if (null !== $this->vendorDir) { 4196cb05674SAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 4206cb05674SAndreas Gohr } 421605f8e8dSAndreas Gohr } 422605f8e8dSAndreas Gohr 423605f8e8dSAndreas Gohr /** 424605f8e8dSAndreas Gohr * Loads the given class or interface. 425605f8e8dSAndreas Gohr * 426605f8e8dSAndreas Gohr * @param string $class The name of the class 427d3233986SAndreas Gohr * @return true|null True if loaded, null otherwise 428605f8e8dSAndreas Gohr */ 429605f8e8dSAndreas Gohr public function loadClass($class) 430605f8e8dSAndreas Gohr { 431605f8e8dSAndreas Gohr if ($file = $this->findFile($class)) { 432*28e9760aSAndreas Gohr $includeFile = self::$includeFile; 433*28e9760aSAndreas Gohr $includeFile($file); 434605f8e8dSAndreas Gohr 435605f8e8dSAndreas Gohr return true; 436605f8e8dSAndreas Gohr } 437d3233986SAndreas Gohr 438d3233986SAndreas Gohr return null; 439605f8e8dSAndreas Gohr } 440605f8e8dSAndreas Gohr 441605f8e8dSAndreas Gohr /** 442605f8e8dSAndreas Gohr * Finds the path to the file where the class is defined. 443605f8e8dSAndreas Gohr * 444605f8e8dSAndreas Gohr * @param string $class The name of the class 445605f8e8dSAndreas Gohr * 446605f8e8dSAndreas Gohr * @return string|false The path if found, false otherwise 447605f8e8dSAndreas Gohr */ 448605f8e8dSAndreas Gohr public function findFile($class) 449605f8e8dSAndreas Gohr { 450605f8e8dSAndreas Gohr // class map lookup 451605f8e8dSAndreas Gohr if (isset($this->classMap[$class])) { 452605f8e8dSAndreas Gohr return $this->classMap[$class]; 453605f8e8dSAndreas Gohr } 4547a33d2f8SNiklas Keller if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 455605f8e8dSAndreas Gohr return false; 456605f8e8dSAndreas Gohr } 457e0dd796dSAndreas Gohr if (null !== $this->apcuPrefix) { 458e0dd796dSAndreas Gohr $file = apcu_fetch($this->apcuPrefix.$class, $hit); 459e0dd796dSAndreas Gohr if ($hit) { 460e0dd796dSAndreas Gohr return $file; 461e0dd796dSAndreas Gohr } 462e0dd796dSAndreas Gohr } 463605f8e8dSAndreas Gohr 464605f8e8dSAndreas Gohr $file = $this->findFileWithExtension($class, '.php'); 465605f8e8dSAndreas Gohr 466605f8e8dSAndreas Gohr // Search for Hack files if we are running on HHVM 4677a33d2f8SNiklas Keller if (false === $file && defined('HHVM_VERSION')) { 468605f8e8dSAndreas Gohr $file = $this->findFileWithExtension($class, '.hh'); 469605f8e8dSAndreas Gohr } 470605f8e8dSAndreas Gohr 471e0dd796dSAndreas Gohr if (null !== $this->apcuPrefix) { 472e0dd796dSAndreas Gohr apcu_add($this->apcuPrefix.$class, $file); 473e0dd796dSAndreas Gohr } 474e0dd796dSAndreas Gohr 4757a33d2f8SNiklas Keller if (false === $file) { 476605f8e8dSAndreas Gohr // Remember that this class does not exist. 4777a33d2f8SNiklas Keller $this->missingClasses[$class] = true; 478605f8e8dSAndreas Gohr } 479605f8e8dSAndreas Gohr 480605f8e8dSAndreas Gohr return $file; 481605f8e8dSAndreas Gohr } 482605f8e8dSAndreas Gohr 4836cb05674SAndreas Gohr /** 4846cb05674SAndreas Gohr * Returns the currently registered loaders indexed by their corresponding vendor directories. 4856cb05674SAndreas Gohr * 4866cb05674SAndreas Gohr * @return self[] 4876cb05674SAndreas Gohr */ 4886cb05674SAndreas Gohr public static function getRegisteredLoaders() 4896cb05674SAndreas Gohr { 4906cb05674SAndreas Gohr return self::$registeredLoaders; 4916cb05674SAndreas Gohr } 4926cb05674SAndreas Gohr 493d3233986SAndreas Gohr /** 494d3233986SAndreas Gohr * @param string $class 495d3233986SAndreas Gohr * @param string $ext 496d3233986SAndreas Gohr * @return string|false 497d3233986SAndreas Gohr */ 498605f8e8dSAndreas Gohr private function findFileWithExtension($class, $ext) 499605f8e8dSAndreas Gohr { 500605f8e8dSAndreas Gohr // PSR-4 lookup 501605f8e8dSAndreas Gohr $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 502605f8e8dSAndreas Gohr 503605f8e8dSAndreas Gohr $first = $class[0]; 504605f8e8dSAndreas Gohr if (isset($this->prefixLengthsPsr4[$first])) { 505e0dd796dSAndreas Gohr $subPath = $class; 506e0dd796dSAndreas Gohr while (false !== $lastPos = strrpos($subPath, '\\')) { 507e0dd796dSAndreas Gohr $subPath = substr($subPath, 0, $lastPos); 508e0dd796dSAndreas Gohr $search = $subPath . '\\'; 509e0dd796dSAndreas Gohr if (isset($this->prefixDirsPsr4[$search])) { 510e43cd7e1SAndreas Gohr $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 511e0dd796dSAndreas Gohr foreach ($this->prefixDirsPsr4[$search] as $dir) { 512e43cd7e1SAndreas Gohr if (file_exists($file = $dir . $pathEnd)) { 513605f8e8dSAndreas Gohr return $file; 514605f8e8dSAndreas Gohr } 515605f8e8dSAndreas Gohr } 516605f8e8dSAndreas Gohr } 517605f8e8dSAndreas Gohr } 518605f8e8dSAndreas Gohr } 519605f8e8dSAndreas Gohr 520605f8e8dSAndreas Gohr // PSR-4 fallback dirs 521605f8e8dSAndreas Gohr foreach ($this->fallbackDirsPsr4 as $dir) { 5227a33d2f8SNiklas Keller if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 523605f8e8dSAndreas Gohr return $file; 524605f8e8dSAndreas Gohr } 525605f8e8dSAndreas Gohr } 526605f8e8dSAndreas Gohr 527605f8e8dSAndreas Gohr // PSR-0 lookup 528605f8e8dSAndreas Gohr if (false !== $pos = strrpos($class, '\\')) { 529605f8e8dSAndreas Gohr // namespaced class name 530605f8e8dSAndreas Gohr $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 531605f8e8dSAndreas Gohr . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 532605f8e8dSAndreas Gohr } else { 533605f8e8dSAndreas Gohr // PEAR-like class name 534605f8e8dSAndreas Gohr $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 535605f8e8dSAndreas Gohr } 536605f8e8dSAndreas Gohr 537605f8e8dSAndreas Gohr if (isset($this->prefixesPsr0[$first])) { 538605f8e8dSAndreas Gohr foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 539605f8e8dSAndreas Gohr if (0 === strpos($class, $prefix)) { 540605f8e8dSAndreas Gohr foreach ($dirs as $dir) { 5417a33d2f8SNiklas Keller if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 542605f8e8dSAndreas Gohr return $file; 543605f8e8dSAndreas Gohr } 544605f8e8dSAndreas Gohr } 545605f8e8dSAndreas Gohr } 546605f8e8dSAndreas Gohr } 547605f8e8dSAndreas Gohr } 548605f8e8dSAndreas Gohr 549605f8e8dSAndreas Gohr // PSR-0 fallback dirs 550605f8e8dSAndreas Gohr foreach ($this->fallbackDirsPsr0 as $dir) { 5517a33d2f8SNiklas Keller if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 552605f8e8dSAndreas Gohr return $file; 553605f8e8dSAndreas Gohr } 554605f8e8dSAndreas Gohr } 555605f8e8dSAndreas Gohr 556605f8e8dSAndreas Gohr // PSR-0 include paths. 557605f8e8dSAndreas Gohr if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 558605f8e8dSAndreas Gohr return $file; 559605f8e8dSAndreas Gohr } 5607a33d2f8SNiklas Keller 5617a33d2f8SNiklas Keller return false; 562605f8e8dSAndreas Gohr } 563*28e9760aSAndreas Gohr 564*28e9760aSAndreas Gohr /** 565*28e9760aSAndreas Gohr * @return void 566*28e9760aSAndreas Gohr */ 567*28e9760aSAndreas Gohr private static function initializeIncludeClosure() 568*28e9760aSAndreas Gohr { 569*28e9760aSAndreas Gohr if (self::$includeFile !== null) { 570*28e9760aSAndreas Gohr return; 571605f8e8dSAndreas Gohr } 572605f8e8dSAndreas Gohr 573605f8e8dSAndreas Gohr /** 574605f8e8dSAndreas Gohr * Scope isolated include. 575605f8e8dSAndreas Gohr * 576605f8e8dSAndreas Gohr * Prevents access to $this/self from included files. 577d3233986SAndreas Gohr * 578d3233986SAndreas Gohr * @param string $file 579d3233986SAndreas Gohr * @return void 580605f8e8dSAndreas Gohr */ 581*28e9760aSAndreas Gohr self::$includeFile = \Closure::bind(static function($file) { 582605f8e8dSAndreas Gohr include $file; 583*28e9760aSAndreas Gohr }, null, null); 584*28e9760aSAndreas Gohr } 585605f8e8dSAndreas Gohr} 586