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