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