1*ee5c0205SAndreas Gohr<?php 2*ee5c0205SAndreas Gohr 3*ee5c0205SAndreas Gohr/* 4*ee5c0205SAndreas Gohr * This file is part of Composer. 5*ee5c0205SAndreas Gohr * 6*ee5c0205SAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 7*ee5c0205SAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 8*ee5c0205SAndreas Gohr * 9*ee5c0205SAndreas Gohr * For the full copyright and license information, please view the LICENSE 10*ee5c0205SAndreas Gohr * file that was distributed with this source code. 11*ee5c0205SAndreas Gohr */ 12*ee5c0205SAndreas Gohr 13*ee5c0205SAndreas Gohrnamespace Composer\Autoload; 14*ee5c0205SAndreas Gohr 15*ee5c0205SAndreas Gohr/** 16*ee5c0205SAndreas Gohr * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17*ee5c0205SAndreas Gohr * 18*ee5c0205SAndreas Gohr * $loader = new \Composer\Autoload\ClassLoader(); 19*ee5c0205SAndreas Gohr * 20*ee5c0205SAndreas Gohr * // register classes with namespaces 21*ee5c0205SAndreas Gohr * $loader->add('Symfony\Component', __DIR__.'/component'); 22*ee5c0205SAndreas Gohr * $loader->add('Symfony', __DIR__.'/framework'); 23*ee5c0205SAndreas Gohr * 24*ee5c0205SAndreas Gohr * // activate the autoloader 25*ee5c0205SAndreas Gohr * $loader->register(); 26*ee5c0205SAndreas Gohr * 27*ee5c0205SAndreas Gohr * // to enable searching the include path (eg. for PEAR packages) 28*ee5c0205SAndreas Gohr * $loader->setUseIncludePath(true); 29*ee5c0205SAndreas Gohr * 30*ee5c0205SAndreas Gohr * In this example, if you try to use a class in the Symfony\Component 31*ee5c0205SAndreas Gohr * namespace or one of its children (Symfony\Component\Console for instance), 32*ee5c0205SAndreas Gohr * the autoloader will first look for the class under the component/ 33*ee5c0205SAndreas Gohr * directory, and it will then fallback to the framework/ directory if not 34*ee5c0205SAndreas Gohr * found before giving up. 35*ee5c0205SAndreas Gohr * 36*ee5c0205SAndreas Gohr * This class is loosely based on the Symfony UniversalClassLoader. 37*ee5c0205SAndreas Gohr * 38*ee5c0205SAndreas Gohr * @author Fabien Potencier <fabien@symfony.com> 39*ee5c0205SAndreas Gohr * @author Jordi Boggiano <j.boggiano@seld.be> 40*ee5c0205SAndreas Gohr * @see https://www.php-fig.org/psr/psr-0/ 41*ee5c0205SAndreas Gohr * @see https://www.php-fig.org/psr/psr-4/ 42*ee5c0205SAndreas Gohr */ 43*ee5c0205SAndreas Gohrclass ClassLoader 44*ee5c0205SAndreas Gohr{ 45*ee5c0205SAndreas Gohr /** @var \Closure(string):void */ 46*ee5c0205SAndreas Gohr private static $includeFile; 47*ee5c0205SAndreas Gohr 48*ee5c0205SAndreas Gohr /** @var string|null */ 49*ee5c0205SAndreas Gohr private $vendorDir; 50*ee5c0205SAndreas Gohr 51*ee5c0205SAndreas Gohr // PSR-4 52*ee5c0205SAndreas Gohr /** 53*ee5c0205SAndreas Gohr * @var array<string, array<string, int>> 54*ee5c0205SAndreas Gohr */ 55*ee5c0205SAndreas Gohr private $prefixLengthsPsr4 = array(); 56*ee5c0205SAndreas Gohr /** 57*ee5c0205SAndreas Gohr * @var array<string, list<string>> 58*ee5c0205SAndreas Gohr */ 59*ee5c0205SAndreas Gohr private $prefixDirsPsr4 = array(); 60*ee5c0205SAndreas Gohr /** 61*ee5c0205SAndreas Gohr * @var list<string> 62*ee5c0205SAndreas Gohr */ 63*ee5c0205SAndreas Gohr private $fallbackDirsPsr4 = array(); 64*ee5c0205SAndreas Gohr 65*ee5c0205SAndreas Gohr // PSR-0 66*ee5c0205SAndreas Gohr /** 67*ee5c0205SAndreas Gohr * List of PSR-0 prefixes 68*ee5c0205SAndreas Gohr * 69*ee5c0205SAndreas Gohr * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) 70*ee5c0205SAndreas Gohr * 71*ee5c0205SAndreas Gohr * @var array<string, array<string, list<string>>> 72*ee5c0205SAndreas Gohr */ 73*ee5c0205SAndreas Gohr private $prefixesPsr0 = array(); 74*ee5c0205SAndreas Gohr /** 75*ee5c0205SAndreas Gohr * @var list<string> 76*ee5c0205SAndreas Gohr */ 77*ee5c0205SAndreas Gohr private $fallbackDirsPsr0 = array(); 78*ee5c0205SAndreas Gohr 79*ee5c0205SAndreas Gohr /** @var bool */ 80*ee5c0205SAndreas Gohr private $useIncludePath = false; 81*ee5c0205SAndreas Gohr 82*ee5c0205SAndreas Gohr /** 83*ee5c0205SAndreas Gohr * @var array<string, string> 84*ee5c0205SAndreas Gohr */ 85*ee5c0205SAndreas Gohr private $classMap = array(); 86*ee5c0205SAndreas Gohr 87*ee5c0205SAndreas Gohr /** @var bool */ 88*ee5c0205SAndreas Gohr private $classMapAuthoritative = false; 89*ee5c0205SAndreas Gohr 90*ee5c0205SAndreas Gohr /** 91*ee5c0205SAndreas Gohr * @var array<string, bool> 92*ee5c0205SAndreas Gohr */ 93*ee5c0205SAndreas Gohr private $missingClasses = array(); 94*ee5c0205SAndreas Gohr 95*ee5c0205SAndreas Gohr /** @var string|null */ 96*ee5c0205SAndreas Gohr private $apcuPrefix; 97*ee5c0205SAndreas Gohr 98*ee5c0205SAndreas Gohr /** 99*ee5c0205SAndreas Gohr * @var array<string, self> 100*ee5c0205SAndreas Gohr */ 101*ee5c0205SAndreas Gohr private static $registeredLoaders = array(); 102*ee5c0205SAndreas Gohr 103*ee5c0205SAndreas Gohr /** 104*ee5c0205SAndreas Gohr * @param string|null $vendorDir 105*ee5c0205SAndreas Gohr */ 106*ee5c0205SAndreas Gohr public function __construct($vendorDir = null) 107*ee5c0205SAndreas Gohr { 108*ee5c0205SAndreas Gohr $this->vendorDir = $vendorDir; 109*ee5c0205SAndreas Gohr self::initializeIncludeClosure(); 110*ee5c0205SAndreas Gohr } 111*ee5c0205SAndreas Gohr 112*ee5c0205SAndreas Gohr /** 113*ee5c0205SAndreas Gohr * @return array<string, list<string>> 114*ee5c0205SAndreas Gohr */ 115*ee5c0205SAndreas Gohr public function getPrefixes() 116*ee5c0205SAndreas Gohr { 117*ee5c0205SAndreas Gohr if (!empty($this->prefixesPsr0)) { 118*ee5c0205SAndreas Gohr return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 119*ee5c0205SAndreas Gohr } 120*ee5c0205SAndreas Gohr 121*ee5c0205SAndreas Gohr return array(); 122*ee5c0205SAndreas Gohr } 123*ee5c0205SAndreas Gohr 124*ee5c0205SAndreas Gohr /** 125*ee5c0205SAndreas Gohr * @return array<string, list<string>> 126*ee5c0205SAndreas Gohr */ 127*ee5c0205SAndreas Gohr public function getPrefixesPsr4() 128*ee5c0205SAndreas Gohr { 129*ee5c0205SAndreas Gohr return $this->prefixDirsPsr4; 130*ee5c0205SAndreas Gohr } 131*ee5c0205SAndreas Gohr 132*ee5c0205SAndreas Gohr /** 133*ee5c0205SAndreas Gohr * @return list<string> 134*ee5c0205SAndreas Gohr */ 135*ee5c0205SAndreas Gohr public function getFallbackDirs() 136*ee5c0205SAndreas Gohr { 137*ee5c0205SAndreas Gohr return $this->fallbackDirsPsr0; 138*ee5c0205SAndreas Gohr } 139*ee5c0205SAndreas Gohr 140*ee5c0205SAndreas Gohr /** 141*ee5c0205SAndreas Gohr * @return list<string> 142*ee5c0205SAndreas Gohr */ 143*ee5c0205SAndreas Gohr public function getFallbackDirsPsr4() 144*ee5c0205SAndreas Gohr { 145*ee5c0205SAndreas Gohr return $this->fallbackDirsPsr4; 146*ee5c0205SAndreas Gohr } 147*ee5c0205SAndreas Gohr 148*ee5c0205SAndreas Gohr /** 149*ee5c0205SAndreas Gohr * @return array<string, string> Array of classname => path 150*ee5c0205SAndreas Gohr */ 151*ee5c0205SAndreas Gohr public function getClassMap() 152*ee5c0205SAndreas Gohr { 153*ee5c0205SAndreas Gohr return $this->classMap; 154*ee5c0205SAndreas Gohr } 155*ee5c0205SAndreas Gohr 156*ee5c0205SAndreas Gohr /** 157*ee5c0205SAndreas Gohr * @param array<string, string> $classMap Class to filename map 158*ee5c0205SAndreas Gohr * 159*ee5c0205SAndreas Gohr * @return void 160*ee5c0205SAndreas Gohr */ 161*ee5c0205SAndreas Gohr public function addClassMap(array $classMap) 162*ee5c0205SAndreas Gohr { 163*ee5c0205SAndreas Gohr if ($this->classMap) { 164*ee5c0205SAndreas Gohr $this->classMap = array_merge($this->classMap, $classMap); 165*ee5c0205SAndreas Gohr } else { 166*ee5c0205SAndreas Gohr $this->classMap = $classMap; 167*ee5c0205SAndreas Gohr } 168*ee5c0205SAndreas Gohr } 169*ee5c0205SAndreas Gohr 170*ee5c0205SAndreas Gohr /** 171*ee5c0205SAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, either 172*ee5c0205SAndreas Gohr * appending or prepending to the ones previously set for this prefix. 173*ee5c0205SAndreas Gohr * 174*ee5c0205SAndreas Gohr * @param string $prefix The prefix 175*ee5c0205SAndreas Gohr * @param list<string>|string $paths The PSR-0 root directories 176*ee5c0205SAndreas Gohr * @param bool $prepend Whether to prepend the directories 177*ee5c0205SAndreas Gohr * 178*ee5c0205SAndreas Gohr * @return void 179*ee5c0205SAndreas Gohr */ 180*ee5c0205SAndreas Gohr public function add($prefix, $paths, $prepend = false) 181*ee5c0205SAndreas Gohr { 182*ee5c0205SAndreas Gohr $paths = (array) $paths; 183*ee5c0205SAndreas Gohr if (!$prefix) { 184*ee5c0205SAndreas Gohr if ($prepend) { 185*ee5c0205SAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 186*ee5c0205SAndreas Gohr $paths, 187*ee5c0205SAndreas Gohr $this->fallbackDirsPsr0 188*ee5c0205SAndreas Gohr ); 189*ee5c0205SAndreas Gohr } else { 190*ee5c0205SAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 191*ee5c0205SAndreas Gohr $this->fallbackDirsPsr0, 192*ee5c0205SAndreas Gohr $paths 193*ee5c0205SAndreas Gohr ); 194*ee5c0205SAndreas Gohr } 195*ee5c0205SAndreas Gohr 196*ee5c0205SAndreas Gohr return; 197*ee5c0205SAndreas Gohr } 198*ee5c0205SAndreas Gohr 199*ee5c0205SAndreas Gohr $first = $prefix[0]; 200*ee5c0205SAndreas Gohr if (!isset($this->prefixesPsr0[$first][$prefix])) { 201*ee5c0205SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = $paths; 202*ee5c0205SAndreas Gohr 203*ee5c0205SAndreas Gohr return; 204*ee5c0205SAndreas Gohr } 205*ee5c0205SAndreas Gohr if ($prepend) { 206*ee5c0205SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 207*ee5c0205SAndreas Gohr $paths, 208*ee5c0205SAndreas Gohr $this->prefixesPsr0[$first][$prefix] 209*ee5c0205SAndreas Gohr ); 210*ee5c0205SAndreas Gohr } else { 211*ee5c0205SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 212*ee5c0205SAndreas Gohr $this->prefixesPsr0[$first][$prefix], 213*ee5c0205SAndreas Gohr $paths 214*ee5c0205SAndreas Gohr ); 215*ee5c0205SAndreas Gohr } 216*ee5c0205SAndreas Gohr } 217*ee5c0205SAndreas Gohr 218*ee5c0205SAndreas Gohr /** 219*ee5c0205SAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, either 220*ee5c0205SAndreas Gohr * appending or prepending to the ones previously set for this namespace. 221*ee5c0205SAndreas Gohr * 222*ee5c0205SAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 223*ee5c0205SAndreas Gohr * @param list<string>|string $paths The PSR-4 base directories 224*ee5c0205SAndreas Gohr * @param bool $prepend Whether to prepend the directories 225*ee5c0205SAndreas Gohr * 226*ee5c0205SAndreas Gohr * @throws \InvalidArgumentException 227*ee5c0205SAndreas Gohr * 228*ee5c0205SAndreas Gohr * @return void 229*ee5c0205SAndreas Gohr */ 230*ee5c0205SAndreas Gohr public function addPsr4($prefix, $paths, $prepend = false) 231*ee5c0205SAndreas Gohr { 232*ee5c0205SAndreas Gohr $paths = (array) $paths; 233*ee5c0205SAndreas Gohr if (!$prefix) { 234*ee5c0205SAndreas Gohr // Register directories for the root namespace. 235*ee5c0205SAndreas Gohr if ($prepend) { 236*ee5c0205SAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 237*ee5c0205SAndreas Gohr $paths, 238*ee5c0205SAndreas Gohr $this->fallbackDirsPsr4 239*ee5c0205SAndreas Gohr ); 240*ee5c0205SAndreas Gohr } else { 241*ee5c0205SAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 242*ee5c0205SAndreas Gohr $this->fallbackDirsPsr4, 243*ee5c0205SAndreas Gohr $paths 244*ee5c0205SAndreas Gohr ); 245*ee5c0205SAndreas Gohr } 246*ee5c0205SAndreas Gohr } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 247*ee5c0205SAndreas Gohr // Register directories for a new namespace. 248*ee5c0205SAndreas Gohr $length = strlen($prefix); 249*ee5c0205SAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 250*ee5c0205SAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 251*ee5c0205SAndreas Gohr } 252*ee5c0205SAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 253*ee5c0205SAndreas Gohr $this->prefixDirsPsr4[$prefix] = $paths; 254*ee5c0205SAndreas Gohr } elseif ($prepend) { 255*ee5c0205SAndreas Gohr // Prepend directories for an already registered namespace. 256*ee5c0205SAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 257*ee5c0205SAndreas Gohr $paths, 258*ee5c0205SAndreas Gohr $this->prefixDirsPsr4[$prefix] 259*ee5c0205SAndreas Gohr ); 260*ee5c0205SAndreas Gohr } else { 261*ee5c0205SAndreas Gohr // Append directories for an already registered namespace. 262*ee5c0205SAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 263*ee5c0205SAndreas Gohr $this->prefixDirsPsr4[$prefix], 264*ee5c0205SAndreas Gohr $paths 265*ee5c0205SAndreas Gohr ); 266*ee5c0205SAndreas Gohr } 267*ee5c0205SAndreas Gohr } 268*ee5c0205SAndreas Gohr 269*ee5c0205SAndreas Gohr /** 270*ee5c0205SAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, 271*ee5c0205SAndreas Gohr * replacing any others previously set for this prefix. 272*ee5c0205SAndreas Gohr * 273*ee5c0205SAndreas Gohr * @param string $prefix The prefix 274*ee5c0205SAndreas Gohr * @param list<string>|string $paths The PSR-0 base directories 275*ee5c0205SAndreas Gohr * 276*ee5c0205SAndreas Gohr * @return void 277*ee5c0205SAndreas Gohr */ 278*ee5c0205SAndreas Gohr public function set($prefix, $paths) 279*ee5c0205SAndreas Gohr { 280*ee5c0205SAndreas Gohr if (!$prefix) { 281*ee5c0205SAndreas Gohr $this->fallbackDirsPsr0 = (array) $paths; 282*ee5c0205SAndreas Gohr } else { 283*ee5c0205SAndreas Gohr $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 284*ee5c0205SAndreas Gohr } 285*ee5c0205SAndreas Gohr } 286*ee5c0205SAndreas Gohr 287*ee5c0205SAndreas Gohr /** 288*ee5c0205SAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, 289*ee5c0205SAndreas Gohr * replacing any others previously set for this namespace. 290*ee5c0205SAndreas Gohr * 291*ee5c0205SAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 292*ee5c0205SAndreas Gohr * @param list<string>|string $paths The PSR-4 base directories 293*ee5c0205SAndreas Gohr * 294*ee5c0205SAndreas Gohr * @throws \InvalidArgumentException 295*ee5c0205SAndreas Gohr * 296*ee5c0205SAndreas Gohr * @return void 297*ee5c0205SAndreas Gohr */ 298*ee5c0205SAndreas Gohr public function setPsr4($prefix, $paths) 299*ee5c0205SAndreas Gohr { 300*ee5c0205SAndreas Gohr if (!$prefix) { 301*ee5c0205SAndreas Gohr $this->fallbackDirsPsr4 = (array) $paths; 302*ee5c0205SAndreas Gohr } else { 303*ee5c0205SAndreas Gohr $length = strlen($prefix); 304*ee5c0205SAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 305*ee5c0205SAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 306*ee5c0205SAndreas Gohr } 307*ee5c0205SAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 308*ee5c0205SAndreas Gohr $this->prefixDirsPsr4[$prefix] = (array) $paths; 309*ee5c0205SAndreas Gohr } 310*ee5c0205SAndreas Gohr } 311*ee5c0205SAndreas Gohr 312*ee5c0205SAndreas Gohr /** 313*ee5c0205SAndreas Gohr * Turns on searching the include path for class files. 314*ee5c0205SAndreas Gohr * 315*ee5c0205SAndreas Gohr * @param bool $useIncludePath 316*ee5c0205SAndreas Gohr * 317*ee5c0205SAndreas Gohr * @return void 318*ee5c0205SAndreas Gohr */ 319*ee5c0205SAndreas Gohr public function setUseIncludePath($useIncludePath) 320*ee5c0205SAndreas Gohr { 321*ee5c0205SAndreas Gohr $this->useIncludePath = $useIncludePath; 322*ee5c0205SAndreas Gohr } 323*ee5c0205SAndreas Gohr 324*ee5c0205SAndreas Gohr /** 325*ee5c0205SAndreas Gohr * Can be used to check if the autoloader uses the include path to check 326*ee5c0205SAndreas Gohr * for classes. 327*ee5c0205SAndreas Gohr * 328*ee5c0205SAndreas Gohr * @return bool 329*ee5c0205SAndreas Gohr */ 330*ee5c0205SAndreas Gohr public function getUseIncludePath() 331*ee5c0205SAndreas Gohr { 332*ee5c0205SAndreas Gohr return $this->useIncludePath; 333*ee5c0205SAndreas Gohr } 334*ee5c0205SAndreas Gohr 335*ee5c0205SAndreas Gohr /** 336*ee5c0205SAndreas Gohr * Turns off searching the prefix and fallback directories for classes 337*ee5c0205SAndreas Gohr * that have not been registered with the class map. 338*ee5c0205SAndreas Gohr * 339*ee5c0205SAndreas Gohr * @param bool $classMapAuthoritative 340*ee5c0205SAndreas Gohr * 341*ee5c0205SAndreas Gohr * @return void 342*ee5c0205SAndreas Gohr */ 343*ee5c0205SAndreas Gohr public function setClassMapAuthoritative($classMapAuthoritative) 344*ee5c0205SAndreas Gohr { 345*ee5c0205SAndreas Gohr $this->classMapAuthoritative = $classMapAuthoritative; 346*ee5c0205SAndreas Gohr } 347*ee5c0205SAndreas Gohr 348*ee5c0205SAndreas Gohr /** 349*ee5c0205SAndreas Gohr * Should class lookup fail if not found in the current class map? 350*ee5c0205SAndreas Gohr * 351*ee5c0205SAndreas Gohr * @return bool 352*ee5c0205SAndreas Gohr */ 353*ee5c0205SAndreas Gohr public function isClassMapAuthoritative() 354*ee5c0205SAndreas Gohr { 355*ee5c0205SAndreas Gohr return $this->classMapAuthoritative; 356*ee5c0205SAndreas Gohr } 357*ee5c0205SAndreas Gohr 358*ee5c0205SAndreas Gohr /** 359*ee5c0205SAndreas Gohr * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 360*ee5c0205SAndreas Gohr * 361*ee5c0205SAndreas Gohr * @param string|null $apcuPrefix 362*ee5c0205SAndreas Gohr * 363*ee5c0205SAndreas Gohr * @return void 364*ee5c0205SAndreas Gohr */ 365*ee5c0205SAndreas Gohr public function setApcuPrefix($apcuPrefix) 366*ee5c0205SAndreas Gohr { 367*ee5c0205SAndreas Gohr $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 368*ee5c0205SAndreas Gohr } 369*ee5c0205SAndreas Gohr 370*ee5c0205SAndreas Gohr /** 371*ee5c0205SAndreas Gohr * The APCu prefix in use, or null if APCu caching is not enabled. 372*ee5c0205SAndreas Gohr * 373*ee5c0205SAndreas Gohr * @return string|null 374*ee5c0205SAndreas Gohr */ 375*ee5c0205SAndreas Gohr public function getApcuPrefix() 376*ee5c0205SAndreas Gohr { 377*ee5c0205SAndreas Gohr return $this->apcuPrefix; 378*ee5c0205SAndreas Gohr } 379*ee5c0205SAndreas Gohr 380*ee5c0205SAndreas Gohr /** 381*ee5c0205SAndreas Gohr * Registers this instance as an autoloader. 382*ee5c0205SAndreas Gohr * 383*ee5c0205SAndreas Gohr * @param bool $prepend Whether to prepend the autoloader or not 384*ee5c0205SAndreas Gohr * 385*ee5c0205SAndreas Gohr * @return void 386*ee5c0205SAndreas Gohr */ 387*ee5c0205SAndreas Gohr public function register($prepend = false) 388*ee5c0205SAndreas Gohr { 389*ee5c0205SAndreas Gohr spl_autoload_register(array($this, 'loadClass'), true, $prepend); 390*ee5c0205SAndreas Gohr 391*ee5c0205SAndreas Gohr if (null === $this->vendorDir) { 392*ee5c0205SAndreas Gohr return; 393*ee5c0205SAndreas Gohr } 394*ee5c0205SAndreas Gohr 395*ee5c0205SAndreas Gohr if ($prepend) { 396*ee5c0205SAndreas Gohr self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 397*ee5c0205SAndreas Gohr } else { 398*ee5c0205SAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 399*ee5c0205SAndreas Gohr self::$registeredLoaders[$this->vendorDir] = $this; 400*ee5c0205SAndreas Gohr } 401*ee5c0205SAndreas Gohr } 402*ee5c0205SAndreas Gohr 403*ee5c0205SAndreas Gohr /** 404*ee5c0205SAndreas Gohr * Unregisters this instance as an autoloader. 405*ee5c0205SAndreas Gohr * 406*ee5c0205SAndreas Gohr * @return void 407*ee5c0205SAndreas Gohr */ 408*ee5c0205SAndreas Gohr public function unregister() 409*ee5c0205SAndreas Gohr { 410*ee5c0205SAndreas Gohr spl_autoload_unregister(array($this, 'loadClass')); 411*ee5c0205SAndreas Gohr 412*ee5c0205SAndreas Gohr if (null !== $this->vendorDir) { 413*ee5c0205SAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 414*ee5c0205SAndreas Gohr } 415*ee5c0205SAndreas Gohr } 416*ee5c0205SAndreas Gohr 417*ee5c0205SAndreas Gohr /** 418*ee5c0205SAndreas Gohr * Loads the given class or interface. 419*ee5c0205SAndreas Gohr * 420*ee5c0205SAndreas Gohr * @param string $class The name of the class 421*ee5c0205SAndreas Gohr * @return true|null True if loaded, null otherwise 422*ee5c0205SAndreas Gohr */ 423*ee5c0205SAndreas Gohr public function loadClass($class) 424*ee5c0205SAndreas Gohr { 425*ee5c0205SAndreas Gohr if ($file = $this->findFile($class)) { 426*ee5c0205SAndreas Gohr $includeFile = self::$includeFile; 427*ee5c0205SAndreas Gohr $includeFile($file); 428*ee5c0205SAndreas Gohr 429*ee5c0205SAndreas Gohr return true; 430*ee5c0205SAndreas Gohr } 431*ee5c0205SAndreas Gohr 432*ee5c0205SAndreas Gohr return null; 433*ee5c0205SAndreas Gohr } 434*ee5c0205SAndreas Gohr 435*ee5c0205SAndreas Gohr /** 436*ee5c0205SAndreas Gohr * Finds the path to the file where the class is defined. 437*ee5c0205SAndreas Gohr * 438*ee5c0205SAndreas Gohr * @param string $class The name of the class 439*ee5c0205SAndreas Gohr * 440*ee5c0205SAndreas Gohr * @return string|false The path if found, false otherwise 441*ee5c0205SAndreas Gohr */ 442*ee5c0205SAndreas Gohr public function findFile($class) 443*ee5c0205SAndreas Gohr { 444*ee5c0205SAndreas Gohr // class map lookup 445*ee5c0205SAndreas Gohr if (isset($this->classMap[$class])) { 446*ee5c0205SAndreas Gohr return $this->classMap[$class]; 447*ee5c0205SAndreas Gohr } 448*ee5c0205SAndreas Gohr if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 449*ee5c0205SAndreas Gohr return false; 450*ee5c0205SAndreas Gohr } 451*ee5c0205SAndreas Gohr if (null !== $this->apcuPrefix) { 452*ee5c0205SAndreas Gohr $file = apcu_fetch($this->apcuPrefix.$class, $hit); 453*ee5c0205SAndreas Gohr if ($hit) { 454*ee5c0205SAndreas Gohr return $file; 455*ee5c0205SAndreas Gohr } 456*ee5c0205SAndreas Gohr } 457*ee5c0205SAndreas Gohr 458*ee5c0205SAndreas Gohr $file = $this->findFileWithExtension($class, '.php'); 459*ee5c0205SAndreas Gohr 460*ee5c0205SAndreas Gohr // Search for Hack files if we are running on HHVM 461*ee5c0205SAndreas Gohr if (false === $file && defined('HHVM_VERSION')) { 462*ee5c0205SAndreas Gohr $file = $this->findFileWithExtension($class, '.hh'); 463*ee5c0205SAndreas Gohr } 464*ee5c0205SAndreas Gohr 465*ee5c0205SAndreas Gohr if (null !== $this->apcuPrefix) { 466*ee5c0205SAndreas Gohr apcu_add($this->apcuPrefix.$class, $file); 467*ee5c0205SAndreas Gohr } 468*ee5c0205SAndreas Gohr 469*ee5c0205SAndreas Gohr if (false === $file) { 470*ee5c0205SAndreas Gohr // Remember that this class does not exist. 471*ee5c0205SAndreas Gohr $this->missingClasses[$class] = true; 472*ee5c0205SAndreas Gohr } 473*ee5c0205SAndreas Gohr 474*ee5c0205SAndreas Gohr return $file; 475*ee5c0205SAndreas Gohr } 476*ee5c0205SAndreas Gohr 477*ee5c0205SAndreas Gohr /** 478*ee5c0205SAndreas Gohr * Returns the currently registered loaders keyed by their corresponding vendor directories. 479*ee5c0205SAndreas Gohr * 480*ee5c0205SAndreas Gohr * @return array<string, self> 481*ee5c0205SAndreas Gohr */ 482*ee5c0205SAndreas Gohr public static function getRegisteredLoaders() 483*ee5c0205SAndreas Gohr { 484*ee5c0205SAndreas Gohr return self::$registeredLoaders; 485*ee5c0205SAndreas Gohr } 486*ee5c0205SAndreas Gohr 487*ee5c0205SAndreas Gohr /** 488*ee5c0205SAndreas Gohr * @param string $class 489*ee5c0205SAndreas Gohr * @param string $ext 490*ee5c0205SAndreas Gohr * @return string|false 491*ee5c0205SAndreas Gohr */ 492*ee5c0205SAndreas Gohr private function findFileWithExtension($class, $ext) 493*ee5c0205SAndreas Gohr { 494*ee5c0205SAndreas Gohr // PSR-4 lookup 495*ee5c0205SAndreas Gohr $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 496*ee5c0205SAndreas Gohr 497*ee5c0205SAndreas Gohr $first = $class[0]; 498*ee5c0205SAndreas Gohr if (isset($this->prefixLengthsPsr4[$first])) { 499*ee5c0205SAndreas Gohr $subPath = $class; 500*ee5c0205SAndreas Gohr while (false !== $lastPos = strrpos($subPath, '\\')) { 501*ee5c0205SAndreas Gohr $subPath = substr($subPath, 0, $lastPos); 502*ee5c0205SAndreas Gohr $search = $subPath . '\\'; 503*ee5c0205SAndreas Gohr if (isset($this->prefixDirsPsr4[$search])) { 504*ee5c0205SAndreas Gohr $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 505*ee5c0205SAndreas Gohr foreach ($this->prefixDirsPsr4[$search] as $dir) { 506*ee5c0205SAndreas Gohr if (file_exists($file = $dir . $pathEnd)) { 507*ee5c0205SAndreas Gohr return $file; 508*ee5c0205SAndreas Gohr } 509*ee5c0205SAndreas Gohr } 510*ee5c0205SAndreas Gohr } 511*ee5c0205SAndreas Gohr } 512*ee5c0205SAndreas Gohr } 513*ee5c0205SAndreas Gohr 514*ee5c0205SAndreas Gohr // PSR-4 fallback dirs 515*ee5c0205SAndreas Gohr foreach ($this->fallbackDirsPsr4 as $dir) { 516*ee5c0205SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 517*ee5c0205SAndreas Gohr return $file; 518*ee5c0205SAndreas Gohr } 519*ee5c0205SAndreas Gohr } 520*ee5c0205SAndreas Gohr 521*ee5c0205SAndreas Gohr // PSR-0 lookup 522*ee5c0205SAndreas Gohr if (false !== $pos = strrpos($class, '\\')) { 523*ee5c0205SAndreas Gohr // namespaced class name 524*ee5c0205SAndreas Gohr $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 525*ee5c0205SAndreas Gohr . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 526*ee5c0205SAndreas Gohr } else { 527*ee5c0205SAndreas Gohr // PEAR-like class name 528*ee5c0205SAndreas Gohr $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 529*ee5c0205SAndreas Gohr } 530*ee5c0205SAndreas Gohr 531*ee5c0205SAndreas Gohr if (isset($this->prefixesPsr0[$first])) { 532*ee5c0205SAndreas Gohr foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 533*ee5c0205SAndreas Gohr if (0 === strpos($class, $prefix)) { 534*ee5c0205SAndreas Gohr foreach ($dirs as $dir) { 535*ee5c0205SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 536*ee5c0205SAndreas Gohr return $file; 537*ee5c0205SAndreas Gohr } 538*ee5c0205SAndreas Gohr } 539*ee5c0205SAndreas Gohr } 540*ee5c0205SAndreas Gohr } 541*ee5c0205SAndreas Gohr } 542*ee5c0205SAndreas Gohr 543*ee5c0205SAndreas Gohr // PSR-0 fallback dirs 544*ee5c0205SAndreas Gohr foreach ($this->fallbackDirsPsr0 as $dir) { 545*ee5c0205SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 546*ee5c0205SAndreas Gohr return $file; 547*ee5c0205SAndreas Gohr } 548*ee5c0205SAndreas Gohr } 549*ee5c0205SAndreas Gohr 550*ee5c0205SAndreas Gohr // PSR-0 include paths. 551*ee5c0205SAndreas Gohr if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 552*ee5c0205SAndreas Gohr return $file; 553*ee5c0205SAndreas Gohr } 554*ee5c0205SAndreas Gohr 555*ee5c0205SAndreas Gohr return false; 556*ee5c0205SAndreas Gohr } 557*ee5c0205SAndreas Gohr 558*ee5c0205SAndreas Gohr /** 559*ee5c0205SAndreas Gohr * @return void 560*ee5c0205SAndreas Gohr */ 561*ee5c0205SAndreas Gohr private static function initializeIncludeClosure() 562*ee5c0205SAndreas Gohr { 563*ee5c0205SAndreas Gohr if (self::$includeFile !== null) { 564*ee5c0205SAndreas Gohr return; 565*ee5c0205SAndreas Gohr } 566*ee5c0205SAndreas Gohr 567*ee5c0205SAndreas Gohr /** 568*ee5c0205SAndreas Gohr * Scope isolated include. 569*ee5c0205SAndreas Gohr * 570*ee5c0205SAndreas Gohr * Prevents access to $this/self from included files. 571*ee5c0205SAndreas Gohr * 572*ee5c0205SAndreas Gohr * @param string $file 573*ee5c0205SAndreas Gohr * @return void 574*ee5c0205SAndreas Gohr */ 575*ee5c0205SAndreas Gohr self::$includeFile = \Closure::bind(static function($file) { 576*ee5c0205SAndreas Gohr include $file; 577*ee5c0205SAndreas Gohr }, null, null); 578*ee5c0205SAndreas Gohr } 579*ee5c0205SAndreas Gohr} 580