10b3fd2d3SAndreas Gohr<?php 20b3fd2d3SAndreas Gohr 30b3fd2d3SAndreas Gohr/* 40b3fd2d3SAndreas Gohr * This file is part of Composer. 50b3fd2d3SAndreas Gohr * 60b3fd2d3SAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 70b3fd2d3SAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 80b3fd2d3SAndreas Gohr * 90b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE 100b3fd2d3SAndreas Gohr * file that was distributed with this source code. 110b3fd2d3SAndreas Gohr */ 120b3fd2d3SAndreas Gohr 130b3fd2d3SAndreas Gohrnamespace Composer\Autoload; 140b3fd2d3SAndreas Gohr 150b3fd2d3SAndreas Gohr/** 160b3fd2d3SAndreas Gohr * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 170b3fd2d3SAndreas Gohr * 180b3fd2d3SAndreas Gohr * $loader = new \Composer\Autoload\ClassLoader(); 190b3fd2d3SAndreas Gohr * 200b3fd2d3SAndreas Gohr * // register classes with namespaces 210b3fd2d3SAndreas Gohr * $loader->add('Symfony\Component', __DIR__.'/component'); 220b3fd2d3SAndreas Gohr * $loader->add('Symfony', __DIR__.'/framework'); 230b3fd2d3SAndreas Gohr * 240b3fd2d3SAndreas Gohr * // activate the autoloader 250b3fd2d3SAndreas Gohr * $loader->register(); 260b3fd2d3SAndreas Gohr * 270b3fd2d3SAndreas Gohr * // to enable searching the include path (eg. for PEAR packages) 280b3fd2d3SAndreas Gohr * $loader->setUseIncludePath(true); 290b3fd2d3SAndreas Gohr * 300b3fd2d3SAndreas Gohr * In this example, if you try to use a class in the Symfony\Component 310b3fd2d3SAndreas Gohr * namespace or one of its children (Symfony\Component\Console for instance), 320b3fd2d3SAndreas Gohr * the autoloader will first look for the class under the component/ 330b3fd2d3SAndreas Gohr * directory, and it will then fallback to the framework/ directory if not 340b3fd2d3SAndreas Gohr * found before giving up. 350b3fd2d3SAndreas Gohr * 360b3fd2d3SAndreas Gohr * This class is loosely based on the Symfony UniversalClassLoader. 370b3fd2d3SAndreas Gohr * 380b3fd2d3SAndreas Gohr * @author Fabien Potencier <fabien@symfony.com> 390b3fd2d3SAndreas Gohr * @author Jordi Boggiano <j.boggiano@seld.be> 40fd0855ecSAndreas Gohr * @see https://www.php-fig.org/psr/psr-0/ 41fd0855ecSAndreas Gohr * @see https://www.php-fig.org/psr/psr-4/ 420b3fd2d3SAndreas Gohr */ 430b3fd2d3SAndreas Gohrclass ClassLoader 440b3fd2d3SAndreas Gohr{ 45*dad993c5SAndreas Gohr /** @var \Closure(string):void */ 46*dad993c5SAndreas Gohr private static $includeFile; 47*dad993c5SAndreas Gohr 48*dad993c5SAndreas Gohr /** @var string|null */ 49fd0855ecSAndreas Gohr private $vendorDir; 50fd0855ecSAndreas Gohr 510b3fd2d3SAndreas Gohr // PSR-4 52*dad993c5SAndreas Gohr /** 53*dad993c5SAndreas Gohr * @var array<string, array<string, int>> 54*dad993c5SAndreas Gohr */ 550b3fd2d3SAndreas Gohr private $prefixLengthsPsr4 = array(); 56*dad993c5SAndreas Gohr /** 57*dad993c5SAndreas Gohr * @var array<string, list<string>> 58*dad993c5SAndreas Gohr */ 590b3fd2d3SAndreas Gohr private $prefixDirsPsr4 = array(); 60*dad993c5SAndreas Gohr /** 61*dad993c5SAndreas Gohr * @var list<string> 62*dad993c5SAndreas Gohr */ 630b3fd2d3SAndreas Gohr private $fallbackDirsPsr4 = array(); 640b3fd2d3SAndreas Gohr 650b3fd2d3SAndreas Gohr // PSR-0 66*dad993c5SAndreas Gohr /** 67*dad993c5SAndreas Gohr * List of PSR-0 prefixes 68*dad993c5SAndreas Gohr * 69*dad993c5SAndreas Gohr * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) 70*dad993c5SAndreas Gohr * 71*dad993c5SAndreas Gohr * @var array<string, array<string, list<string>>> 72*dad993c5SAndreas Gohr */ 730b3fd2d3SAndreas Gohr private $prefixesPsr0 = array(); 74*dad993c5SAndreas Gohr /** 75*dad993c5SAndreas Gohr * @var list<string> 76*dad993c5SAndreas Gohr */ 770b3fd2d3SAndreas Gohr private $fallbackDirsPsr0 = array(); 780b3fd2d3SAndreas Gohr 79*dad993c5SAndreas Gohr /** @var bool */ 800b3fd2d3SAndreas Gohr private $useIncludePath = false; 81*dad993c5SAndreas Gohr 82*dad993c5SAndreas Gohr /** 83*dad993c5SAndreas Gohr * @var array<string, string> 84*dad993c5SAndreas Gohr */ 850b3fd2d3SAndreas Gohr private $classMap = array(); 86*dad993c5SAndreas Gohr 87*dad993c5SAndreas Gohr /** @var bool */ 880b3fd2d3SAndreas Gohr private $classMapAuthoritative = false; 89*dad993c5SAndreas Gohr 90*dad993c5SAndreas Gohr /** 91*dad993c5SAndreas Gohr * @var array<string, bool> 92*dad993c5SAndreas Gohr */ 930b3fd2d3SAndreas Gohr private $missingClasses = array(); 94*dad993c5SAndreas Gohr 95*dad993c5SAndreas Gohr /** @var string|null */ 960b3fd2d3SAndreas Gohr private $apcuPrefix; 970b3fd2d3SAndreas Gohr 98*dad993c5SAndreas Gohr /** 99*dad993c5SAndreas Gohr * @var array<string, self> 100*dad993c5SAndreas Gohr */ 101fd0855ecSAndreas Gohr private static $registeredLoaders = array(); 102fd0855ecSAndreas Gohr 103*dad993c5SAndreas Gohr /** 104*dad993c5SAndreas Gohr * @param string|null $vendorDir 105*dad993c5SAndreas Gohr */ 106fd0855ecSAndreas Gohr public function __construct($vendorDir = null) 107fd0855ecSAndreas Gohr { 108fd0855ecSAndreas Gohr $this->vendorDir = $vendorDir; 109*dad993c5SAndreas Gohr self::initializeIncludeClosure(); 110fd0855ecSAndreas Gohr } 111fd0855ecSAndreas Gohr 112*dad993c5SAndreas Gohr /** 113*dad993c5SAndreas Gohr * @return array<string, list<string>> 114*dad993c5SAndreas Gohr */ 1150b3fd2d3SAndreas Gohr public function getPrefixes() 1160b3fd2d3SAndreas Gohr { 1170b3fd2d3SAndreas Gohr if (!empty($this->prefixesPsr0)) { 118fd0855ecSAndreas Gohr return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 1190b3fd2d3SAndreas Gohr } 1200b3fd2d3SAndreas Gohr 1210b3fd2d3SAndreas Gohr return array(); 1220b3fd2d3SAndreas Gohr } 1230b3fd2d3SAndreas Gohr 124*dad993c5SAndreas Gohr /** 125*dad993c5SAndreas Gohr * @return array<string, list<string>> 126*dad993c5SAndreas Gohr */ 1270b3fd2d3SAndreas Gohr public function getPrefixesPsr4() 1280b3fd2d3SAndreas Gohr { 1290b3fd2d3SAndreas Gohr return $this->prefixDirsPsr4; 1300b3fd2d3SAndreas Gohr } 1310b3fd2d3SAndreas Gohr 132*dad993c5SAndreas Gohr /** 133*dad993c5SAndreas Gohr * @return list<string> 134*dad993c5SAndreas Gohr */ 1350b3fd2d3SAndreas Gohr public function getFallbackDirs() 1360b3fd2d3SAndreas Gohr { 1370b3fd2d3SAndreas Gohr return $this->fallbackDirsPsr0; 1380b3fd2d3SAndreas Gohr } 1390b3fd2d3SAndreas Gohr 140*dad993c5SAndreas Gohr /** 141*dad993c5SAndreas Gohr * @return list<string> 142*dad993c5SAndreas Gohr */ 1430b3fd2d3SAndreas Gohr public function getFallbackDirsPsr4() 1440b3fd2d3SAndreas Gohr { 1450b3fd2d3SAndreas Gohr return $this->fallbackDirsPsr4; 1460b3fd2d3SAndreas Gohr } 1470b3fd2d3SAndreas Gohr 148*dad993c5SAndreas Gohr /** 149*dad993c5SAndreas Gohr * @return array<string, string> Array of classname => path 150*dad993c5SAndreas Gohr */ 1510b3fd2d3SAndreas Gohr public function getClassMap() 1520b3fd2d3SAndreas Gohr { 1530b3fd2d3SAndreas Gohr return $this->classMap; 1540b3fd2d3SAndreas Gohr } 1550b3fd2d3SAndreas Gohr 1560b3fd2d3SAndreas Gohr /** 157*dad993c5SAndreas Gohr * @param array<string, string> $classMap Class to filename map 158*dad993c5SAndreas Gohr * 159*dad993c5SAndreas Gohr * @return void 1600b3fd2d3SAndreas Gohr */ 1610b3fd2d3SAndreas Gohr public function addClassMap(array $classMap) 1620b3fd2d3SAndreas Gohr { 1630b3fd2d3SAndreas Gohr if ($this->classMap) { 1640b3fd2d3SAndreas Gohr $this->classMap = array_merge($this->classMap, $classMap); 1650b3fd2d3SAndreas Gohr } else { 1660b3fd2d3SAndreas Gohr $this->classMap = $classMap; 1670b3fd2d3SAndreas Gohr } 1680b3fd2d3SAndreas Gohr } 1690b3fd2d3SAndreas Gohr 1700b3fd2d3SAndreas Gohr /** 1710b3fd2d3SAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, either 1720b3fd2d3SAndreas Gohr * appending or prepending to the ones previously set for this prefix. 1730b3fd2d3SAndreas Gohr * 1740b3fd2d3SAndreas Gohr * @param string $prefix The prefix 175*dad993c5SAndreas Gohr * @param list<string>|string $paths The PSR-0 root directories 1760b3fd2d3SAndreas Gohr * @param bool $prepend Whether to prepend the directories 177*dad993c5SAndreas Gohr * 178*dad993c5SAndreas Gohr * @return void 1790b3fd2d3SAndreas Gohr */ 1800b3fd2d3SAndreas Gohr public function add($prefix, $paths, $prepend = false) 1810b3fd2d3SAndreas Gohr { 182*dad993c5SAndreas Gohr $paths = (array) $paths; 1830b3fd2d3SAndreas Gohr if (!$prefix) { 1840b3fd2d3SAndreas Gohr if ($prepend) { 1850b3fd2d3SAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 186*dad993c5SAndreas Gohr $paths, 1870b3fd2d3SAndreas Gohr $this->fallbackDirsPsr0 1880b3fd2d3SAndreas Gohr ); 1890b3fd2d3SAndreas Gohr } else { 1900b3fd2d3SAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 1910b3fd2d3SAndreas Gohr $this->fallbackDirsPsr0, 192*dad993c5SAndreas Gohr $paths 1930b3fd2d3SAndreas Gohr ); 1940b3fd2d3SAndreas Gohr } 1950b3fd2d3SAndreas Gohr 1960b3fd2d3SAndreas Gohr return; 1970b3fd2d3SAndreas Gohr } 1980b3fd2d3SAndreas Gohr 1990b3fd2d3SAndreas Gohr $first = $prefix[0]; 2000b3fd2d3SAndreas Gohr if (!isset($this->prefixesPsr0[$first][$prefix])) { 201*dad993c5SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = $paths; 2020b3fd2d3SAndreas Gohr 2030b3fd2d3SAndreas Gohr return; 2040b3fd2d3SAndreas Gohr } 2050b3fd2d3SAndreas Gohr if ($prepend) { 2060b3fd2d3SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 207*dad993c5SAndreas Gohr $paths, 2080b3fd2d3SAndreas Gohr $this->prefixesPsr0[$first][$prefix] 2090b3fd2d3SAndreas Gohr ); 2100b3fd2d3SAndreas Gohr } else { 2110b3fd2d3SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 2120b3fd2d3SAndreas Gohr $this->prefixesPsr0[$first][$prefix], 213*dad993c5SAndreas Gohr $paths 2140b3fd2d3SAndreas Gohr ); 2150b3fd2d3SAndreas Gohr } 2160b3fd2d3SAndreas Gohr } 2170b3fd2d3SAndreas Gohr 2180b3fd2d3SAndreas Gohr /** 2190b3fd2d3SAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, either 2200b3fd2d3SAndreas Gohr * appending or prepending to the ones previously set for this namespace. 2210b3fd2d3SAndreas Gohr * 2220b3fd2d3SAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 223*dad993c5SAndreas Gohr * @param list<string>|string $paths The PSR-4 base directories 2240b3fd2d3SAndreas Gohr * @param bool $prepend Whether to prepend the directories 2250b3fd2d3SAndreas Gohr * 2260b3fd2d3SAndreas Gohr * @throws \InvalidArgumentException 227*dad993c5SAndreas Gohr * 228*dad993c5SAndreas Gohr * @return void 2290b3fd2d3SAndreas Gohr */ 2300b3fd2d3SAndreas Gohr public function addPsr4($prefix, $paths, $prepend = false) 2310b3fd2d3SAndreas Gohr { 232*dad993c5SAndreas Gohr $paths = (array) $paths; 2330b3fd2d3SAndreas Gohr if (!$prefix) { 2340b3fd2d3SAndreas Gohr // Register directories for the root namespace. 2350b3fd2d3SAndreas Gohr if ($prepend) { 2360b3fd2d3SAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 237*dad993c5SAndreas Gohr $paths, 2380b3fd2d3SAndreas Gohr $this->fallbackDirsPsr4 2390b3fd2d3SAndreas Gohr ); 2400b3fd2d3SAndreas Gohr } else { 2410b3fd2d3SAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 2420b3fd2d3SAndreas Gohr $this->fallbackDirsPsr4, 243*dad993c5SAndreas Gohr $paths 2440b3fd2d3SAndreas Gohr ); 2450b3fd2d3SAndreas Gohr } 2460b3fd2d3SAndreas Gohr } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 2470b3fd2d3SAndreas Gohr // Register directories for a new namespace. 2480b3fd2d3SAndreas Gohr $length = strlen($prefix); 2490b3fd2d3SAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 2500b3fd2d3SAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 2510b3fd2d3SAndreas Gohr } 2520b3fd2d3SAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 253*dad993c5SAndreas Gohr $this->prefixDirsPsr4[$prefix] = $paths; 2540b3fd2d3SAndreas Gohr } elseif ($prepend) { 2550b3fd2d3SAndreas Gohr // Prepend directories for an already registered namespace. 2560b3fd2d3SAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 257*dad993c5SAndreas Gohr $paths, 2580b3fd2d3SAndreas Gohr $this->prefixDirsPsr4[$prefix] 2590b3fd2d3SAndreas Gohr ); 2600b3fd2d3SAndreas Gohr } else { 2610b3fd2d3SAndreas Gohr // Append directories for an already registered namespace. 2620b3fd2d3SAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 2630b3fd2d3SAndreas Gohr $this->prefixDirsPsr4[$prefix], 264*dad993c5SAndreas Gohr $paths 2650b3fd2d3SAndreas Gohr ); 2660b3fd2d3SAndreas Gohr } 2670b3fd2d3SAndreas Gohr } 2680b3fd2d3SAndreas Gohr 2690b3fd2d3SAndreas Gohr /** 2700b3fd2d3SAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, 2710b3fd2d3SAndreas Gohr * replacing any others previously set for this prefix. 2720b3fd2d3SAndreas Gohr * 2730b3fd2d3SAndreas Gohr * @param string $prefix The prefix 274*dad993c5SAndreas Gohr * @param list<string>|string $paths The PSR-0 base directories 275*dad993c5SAndreas Gohr * 276*dad993c5SAndreas Gohr * @return void 2770b3fd2d3SAndreas Gohr */ 2780b3fd2d3SAndreas Gohr public function set($prefix, $paths) 2790b3fd2d3SAndreas Gohr { 2800b3fd2d3SAndreas Gohr if (!$prefix) { 2810b3fd2d3SAndreas Gohr $this->fallbackDirsPsr0 = (array) $paths; 2820b3fd2d3SAndreas Gohr } else { 2830b3fd2d3SAndreas Gohr $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 2840b3fd2d3SAndreas Gohr } 2850b3fd2d3SAndreas Gohr } 2860b3fd2d3SAndreas Gohr 2870b3fd2d3SAndreas Gohr /** 2880b3fd2d3SAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, 2890b3fd2d3SAndreas Gohr * replacing any others previously set for this namespace. 2900b3fd2d3SAndreas Gohr * 2910b3fd2d3SAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 292*dad993c5SAndreas Gohr * @param list<string>|string $paths The PSR-4 base directories 2930b3fd2d3SAndreas Gohr * 2940b3fd2d3SAndreas Gohr * @throws \InvalidArgumentException 295*dad993c5SAndreas Gohr * 296*dad993c5SAndreas Gohr * @return void 2970b3fd2d3SAndreas Gohr */ 2980b3fd2d3SAndreas Gohr public function setPsr4($prefix, $paths) 2990b3fd2d3SAndreas Gohr { 3000b3fd2d3SAndreas Gohr if (!$prefix) { 3010b3fd2d3SAndreas Gohr $this->fallbackDirsPsr4 = (array) $paths; 3020b3fd2d3SAndreas Gohr } else { 3030b3fd2d3SAndreas Gohr $length = strlen($prefix); 3040b3fd2d3SAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 3050b3fd2d3SAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 3060b3fd2d3SAndreas Gohr } 3070b3fd2d3SAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 3080b3fd2d3SAndreas Gohr $this->prefixDirsPsr4[$prefix] = (array) $paths; 3090b3fd2d3SAndreas Gohr } 3100b3fd2d3SAndreas Gohr } 3110b3fd2d3SAndreas Gohr 3120b3fd2d3SAndreas Gohr /** 3130b3fd2d3SAndreas Gohr * Turns on searching the include path for class files. 3140b3fd2d3SAndreas Gohr * 3150b3fd2d3SAndreas Gohr * @param bool $useIncludePath 316*dad993c5SAndreas Gohr * 317*dad993c5SAndreas Gohr * @return void 3180b3fd2d3SAndreas Gohr */ 3190b3fd2d3SAndreas Gohr public function setUseIncludePath($useIncludePath) 3200b3fd2d3SAndreas Gohr { 3210b3fd2d3SAndreas Gohr $this->useIncludePath = $useIncludePath; 3220b3fd2d3SAndreas Gohr } 3230b3fd2d3SAndreas Gohr 3240b3fd2d3SAndreas Gohr /** 3250b3fd2d3SAndreas Gohr * Can be used to check if the autoloader uses the include path to check 3260b3fd2d3SAndreas Gohr * for classes. 3270b3fd2d3SAndreas Gohr * 3280b3fd2d3SAndreas Gohr * @return bool 3290b3fd2d3SAndreas Gohr */ 3300b3fd2d3SAndreas Gohr public function getUseIncludePath() 3310b3fd2d3SAndreas Gohr { 3320b3fd2d3SAndreas Gohr return $this->useIncludePath; 3330b3fd2d3SAndreas Gohr } 3340b3fd2d3SAndreas Gohr 3350b3fd2d3SAndreas Gohr /** 3360b3fd2d3SAndreas Gohr * Turns off searching the prefix and fallback directories for classes 3370b3fd2d3SAndreas Gohr * that have not been registered with the class map. 3380b3fd2d3SAndreas Gohr * 3390b3fd2d3SAndreas Gohr * @param bool $classMapAuthoritative 340*dad993c5SAndreas Gohr * 341*dad993c5SAndreas Gohr * @return void 3420b3fd2d3SAndreas Gohr */ 3430b3fd2d3SAndreas Gohr public function setClassMapAuthoritative($classMapAuthoritative) 3440b3fd2d3SAndreas Gohr { 3450b3fd2d3SAndreas Gohr $this->classMapAuthoritative = $classMapAuthoritative; 3460b3fd2d3SAndreas Gohr } 3470b3fd2d3SAndreas Gohr 3480b3fd2d3SAndreas Gohr /** 3490b3fd2d3SAndreas Gohr * Should class lookup fail if not found in the current class map? 3500b3fd2d3SAndreas Gohr * 3510b3fd2d3SAndreas Gohr * @return bool 3520b3fd2d3SAndreas Gohr */ 3530b3fd2d3SAndreas Gohr public function isClassMapAuthoritative() 3540b3fd2d3SAndreas Gohr { 3550b3fd2d3SAndreas Gohr return $this->classMapAuthoritative; 3560b3fd2d3SAndreas Gohr } 3570b3fd2d3SAndreas Gohr 3580b3fd2d3SAndreas Gohr /** 3590b3fd2d3SAndreas Gohr * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 3600b3fd2d3SAndreas Gohr * 3610b3fd2d3SAndreas Gohr * @param string|null $apcuPrefix 362*dad993c5SAndreas Gohr * 363*dad993c5SAndreas Gohr * @return void 3640b3fd2d3SAndreas Gohr */ 3650b3fd2d3SAndreas Gohr public function setApcuPrefix($apcuPrefix) 3660b3fd2d3SAndreas Gohr { 3670b3fd2d3SAndreas Gohr $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 3680b3fd2d3SAndreas Gohr } 3690b3fd2d3SAndreas Gohr 3700b3fd2d3SAndreas Gohr /** 3710b3fd2d3SAndreas Gohr * The APCu prefix in use, or null if APCu caching is not enabled. 3720b3fd2d3SAndreas Gohr * 3730b3fd2d3SAndreas Gohr * @return string|null 3740b3fd2d3SAndreas Gohr */ 3750b3fd2d3SAndreas Gohr public function getApcuPrefix() 3760b3fd2d3SAndreas Gohr { 3770b3fd2d3SAndreas Gohr return $this->apcuPrefix; 3780b3fd2d3SAndreas Gohr } 3790b3fd2d3SAndreas Gohr 3800b3fd2d3SAndreas Gohr /** 3810b3fd2d3SAndreas Gohr * Registers this instance as an autoloader. 3820b3fd2d3SAndreas Gohr * 3830b3fd2d3SAndreas Gohr * @param bool $prepend Whether to prepend the autoloader or not 384*dad993c5SAndreas Gohr * 385*dad993c5SAndreas Gohr * @return void 3860b3fd2d3SAndreas Gohr */ 3870b3fd2d3SAndreas Gohr public function register($prepend = false) 3880b3fd2d3SAndreas Gohr { 3890b3fd2d3SAndreas Gohr spl_autoload_register(array($this, 'loadClass'), true, $prepend); 390fd0855ecSAndreas Gohr 391fd0855ecSAndreas Gohr if (null === $this->vendorDir) { 392fd0855ecSAndreas Gohr return; 393fd0855ecSAndreas Gohr } 394fd0855ecSAndreas Gohr 395fd0855ecSAndreas Gohr if ($prepend) { 396fd0855ecSAndreas Gohr self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 397fd0855ecSAndreas Gohr } else { 398fd0855ecSAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 399fd0855ecSAndreas Gohr self::$registeredLoaders[$this->vendorDir] = $this; 400fd0855ecSAndreas Gohr } 4010b3fd2d3SAndreas Gohr } 4020b3fd2d3SAndreas Gohr 4030b3fd2d3SAndreas Gohr /** 4040b3fd2d3SAndreas Gohr * Unregisters this instance as an autoloader. 405*dad993c5SAndreas Gohr * 406*dad993c5SAndreas Gohr * @return void 4070b3fd2d3SAndreas Gohr */ 4080b3fd2d3SAndreas Gohr public function unregister() 4090b3fd2d3SAndreas Gohr { 4100b3fd2d3SAndreas Gohr spl_autoload_unregister(array($this, 'loadClass')); 411fd0855ecSAndreas Gohr 412fd0855ecSAndreas Gohr if (null !== $this->vendorDir) { 413fd0855ecSAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 414fd0855ecSAndreas Gohr } 4150b3fd2d3SAndreas Gohr } 4160b3fd2d3SAndreas Gohr 4170b3fd2d3SAndreas Gohr /** 4180b3fd2d3SAndreas Gohr * Loads the given class or interface. 4190b3fd2d3SAndreas Gohr * 4200b3fd2d3SAndreas Gohr * @param string $class The name of the class 421fd0855ecSAndreas Gohr * @return true|null True if loaded, null otherwise 4220b3fd2d3SAndreas Gohr */ 4230b3fd2d3SAndreas Gohr public function loadClass($class) 4240b3fd2d3SAndreas Gohr { 4250b3fd2d3SAndreas Gohr if ($file = $this->findFile($class)) { 426*dad993c5SAndreas Gohr $includeFile = self::$includeFile; 427*dad993c5SAndreas Gohr $includeFile($file); 4280b3fd2d3SAndreas Gohr 4290b3fd2d3SAndreas Gohr return true; 4300b3fd2d3SAndreas Gohr } 431fd0855ecSAndreas Gohr 432fd0855ecSAndreas Gohr return null; 4330b3fd2d3SAndreas Gohr } 4340b3fd2d3SAndreas Gohr 4350b3fd2d3SAndreas Gohr /** 4360b3fd2d3SAndreas Gohr * Finds the path to the file where the class is defined. 4370b3fd2d3SAndreas Gohr * 4380b3fd2d3SAndreas Gohr * @param string $class The name of the class 4390b3fd2d3SAndreas Gohr * 4400b3fd2d3SAndreas Gohr * @return string|false The path if found, false otherwise 4410b3fd2d3SAndreas Gohr */ 4420b3fd2d3SAndreas Gohr public function findFile($class) 4430b3fd2d3SAndreas Gohr { 4440b3fd2d3SAndreas Gohr // class map lookup 4450b3fd2d3SAndreas Gohr if (isset($this->classMap[$class])) { 4460b3fd2d3SAndreas Gohr return $this->classMap[$class]; 4470b3fd2d3SAndreas Gohr } 4480b3fd2d3SAndreas Gohr if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 4490b3fd2d3SAndreas Gohr return false; 4500b3fd2d3SAndreas Gohr } 4510b3fd2d3SAndreas Gohr if (null !== $this->apcuPrefix) { 4520b3fd2d3SAndreas Gohr $file = apcu_fetch($this->apcuPrefix.$class, $hit); 4530b3fd2d3SAndreas Gohr if ($hit) { 4540b3fd2d3SAndreas Gohr return $file; 4550b3fd2d3SAndreas Gohr } 4560b3fd2d3SAndreas Gohr } 4570b3fd2d3SAndreas Gohr 4580b3fd2d3SAndreas Gohr $file = $this->findFileWithExtension($class, '.php'); 4590b3fd2d3SAndreas Gohr 4600b3fd2d3SAndreas Gohr // Search for Hack files if we are running on HHVM 4610b3fd2d3SAndreas Gohr if (false === $file && defined('HHVM_VERSION')) { 4620b3fd2d3SAndreas Gohr $file = $this->findFileWithExtension($class, '.hh'); 4630b3fd2d3SAndreas Gohr } 4640b3fd2d3SAndreas Gohr 4650b3fd2d3SAndreas Gohr if (null !== $this->apcuPrefix) { 4660b3fd2d3SAndreas Gohr apcu_add($this->apcuPrefix.$class, $file); 4670b3fd2d3SAndreas Gohr } 4680b3fd2d3SAndreas Gohr 4690b3fd2d3SAndreas Gohr if (false === $file) { 4700b3fd2d3SAndreas Gohr // Remember that this class does not exist. 4710b3fd2d3SAndreas Gohr $this->missingClasses[$class] = true; 4720b3fd2d3SAndreas Gohr } 4730b3fd2d3SAndreas Gohr 4740b3fd2d3SAndreas Gohr return $file; 4750b3fd2d3SAndreas Gohr } 4760b3fd2d3SAndreas Gohr 477fd0855ecSAndreas Gohr /** 478*dad993c5SAndreas Gohr * Returns the currently registered loaders keyed by their corresponding vendor directories. 479fd0855ecSAndreas Gohr * 480*dad993c5SAndreas Gohr * @return array<string, self> 481fd0855ecSAndreas Gohr */ 482fd0855ecSAndreas Gohr public static function getRegisteredLoaders() 483fd0855ecSAndreas Gohr { 484fd0855ecSAndreas Gohr return self::$registeredLoaders; 485fd0855ecSAndreas Gohr } 486fd0855ecSAndreas Gohr 487*dad993c5SAndreas Gohr /** 488*dad993c5SAndreas Gohr * @param string $class 489*dad993c5SAndreas Gohr * @param string $ext 490*dad993c5SAndreas Gohr * @return string|false 491*dad993c5SAndreas Gohr */ 4920b3fd2d3SAndreas Gohr private function findFileWithExtension($class, $ext) 4930b3fd2d3SAndreas Gohr { 4940b3fd2d3SAndreas Gohr // PSR-4 lookup 4950b3fd2d3SAndreas Gohr $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 4960b3fd2d3SAndreas Gohr 4970b3fd2d3SAndreas Gohr $first = $class[0]; 4980b3fd2d3SAndreas Gohr if (isset($this->prefixLengthsPsr4[$first])) { 4990b3fd2d3SAndreas Gohr $subPath = $class; 5000b3fd2d3SAndreas Gohr while (false !== $lastPos = strrpos($subPath, '\\')) { 5010b3fd2d3SAndreas Gohr $subPath = substr($subPath, 0, $lastPos); 5020b3fd2d3SAndreas Gohr $search = $subPath . '\\'; 5030b3fd2d3SAndreas Gohr if (isset($this->prefixDirsPsr4[$search])) { 5040b3fd2d3SAndreas Gohr $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 5050b3fd2d3SAndreas Gohr foreach ($this->prefixDirsPsr4[$search] as $dir) { 5060b3fd2d3SAndreas Gohr if (file_exists($file = $dir . $pathEnd)) { 5070b3fd2d3SAndreas Gohr return $file; 5080b3fd2d3SAndreas Gohr } 5090b3fd2d3SAndreas Gohr } 5100b3fd2d3SAndreas Gohr } 5110b3fd2d3SAndreas Gohr } 5120b3fd2d3SAndreas Gohr } 5130b3fd2d3SAndreas Gohr 5140b3fd2d3SAndreas Gohr // PSR-4 fallback dirs 5150b3fd2d3SAndreas Gohr foreach ($this->fallbackDirsPsr4 as $dir) { 5160b3fd2d3SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 5170b3fd2d3SAndreas Gohr return $file; 5180b3fd2d3SAndreas Gohr } 5190b3fd2d3SAndreas Gohr } 5200b3fd2d3SAndreas Gohr 5210b3fd2d3SAndreas Gohr // PSR-0 lookup 5220b3fd2d3SAndreas Gohr if (false !== $pos = strrpos($class, '\\')) { 5230b3fd2d3SAndreas Gohr // namespaced class name 5240b3fd2d3SAndreas Gohr $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 5250b3fd2d3SAndreas Gohr . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 5260b3fd2d3SAndreas Gohr } else { 5270b3fd2d3SAndreas Gohr // PEAR-like class name 5280b3fd2d3SAndreas Gohr $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 5290b3fd2d3SAndreas Gohr } 5300b3fd2d3SAndreas Gohr 5310b3fd2d3SAndreas Gohr if (isset($this->prefixesPsr0[$first])) { 5320b3fd2d3SAndreas Gohr foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 5330b3fd2d3SAndreas Gohr if (0 === strpos($class, $prefix)) { 5340b3fd2d3SAndreas Gohr foreach ($dirs as $dir) { 5350b3fd2d3SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 5360b3fd2d3SAndreas Gohr return $file; 5370b3fd2d3SAndreas Gohr } 5380b3fd2d3SAndreas Gohr } 5390b3fd2d3SAndreas Gohr } 5400b3fd2d3SAndreas Gohr } 5410b3fd2d3SAndreas Gohr } 5420b3fd2d3SAndreas Gohr 5430b3fd2d3SAndreas Gohr // PSR-0 fallback dirs 5440b3fd2d3SAndreas Gohr foreach ($this->fallbackDirsPsr0 as $dir) { 5450b3fd2d3SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 5460b3fd2d3SAndreas Gohr return $file; 5470b3fd2d3SAndreas Gohr } 5480b3fd2d3SAndreas Gohr } 5490b3fd2d3SAndreas Gohr 5500b3fd2d3SAndreas Gohr // PSR-0 include paths. 5510b3fd2d3SAndreas Gohr if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 5520b3fd2d3SAndreas Gohr return $file; 5530b3fd2d3SAndreas Gohr } 5540b3fd2d3SAndreas Gohr 5550b3fd2d3SAndreas Gohr return false; 5560b3fd2d3SAndreas Gohr } 557*dad993c5SAndreas Gohr 558*dad993c5SAndreas Gohr /** 559*dad993c5SAndreas Gohr * @return void 560*dad993c5SAndreas Gohr */ 561*dad993c5SAndreas Gohr private static function initializeIncludeClosure() 562*dad993c5SAndreas Gohr { 563*dad993c5SAndreas Gohr if (self::$includeFile !== null) { 564*dad993c5SAndreas Gohr return; 5650b3fd2d3SAndreas Gohr } 5660b3fd2d3SAndreas Gohr 5670b3fd2d3SAndreas Gohr /** 5680b3fd2d3SAndreas Gohr * Scope isolated include. 5690b3fd2d3SAndreas Gohr * 5700b3fd2d3SAndreas Gohr * Prevents access to $this/self from included files. 571*dad993c5SAndreas Gohr * 572*dad993c5SAndreas Gohr * @param string $file 573*dad993c5SAndreas Gohr * @return void 5740b3fd2d3SAndreas Gohr */ 575*dad993c5SAndreas Gohr self::$includeFile = \Closure::bind(static function($file) { 5760b3fd2d3SAndreas Gohr include $file; 577*dad993c5SAndreas Gohr }, null, null); 578*dad993c5SAndreas Gohr } 5790b3fd2d3SAndreas Gohr} 580