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