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