198a36116SAndreas Gohr<?php 298a36116SAndreas Gohr 398a36116SAndreas Gohr/* 498a36116SAndreas Gohr * This file is part of Composer. 598a36116SAndreas Gohr * 698a36116SAndreas Gohr * (c) Nils Adermann <naderman@naderman.de> 798a36116SAndreas Gohr * Jordi Boggiano <j.boggiano@seld.be> 898a36116SAndreas Gohr * 998a36116SAndreas Gohr * For the full copyright and license information, please view the LICENSE 1098a36116SAndreas Gohr * file that was distributed with this source code. 1198a36116SAndreas Gohr */ 1298a36116SAndreas Gohr 1398a36116SAndreas Gohrnamespace Composer\Autoload; 1498a36116SAndreas Gohr 1598a36116SAndreas Gohr/** 1698a36116SAndreas Gohr * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 1798a36116SAndreas Gohr * 1898a36116SAndreas Gohr * $loader = new \Composer\Autoload\ClassLoader(); 1998a36116SAndreas Gohr * 2098a36116SAndreas Gohr * // register classes with namespaces 2198a36116SAndreas Gohr * $loader->add('Symfony\Component', __DIR__.'/component'); 2298a36116SAndreas Gohr * $loader->add('Symfony', __DIR__.'/framework'); 2398a36116SAndreas Gohr * 2498a36116SAndreas Gohr * // activate the autoloader 2598a36116SAndreas Gohr * $loader->register(); 2698a36116SAndreas Gohr * 2798a36116SAndreas Gohr * // to enable searching the include path (eg. for PEAR packages) 2898a36116SAndreas Gohr * $loader->setUseIncludePath(true); 2998a36116SAndreas Gohr * 3098a36116SAndreas Gohr * In this example, if you try to use a class in the Symfony\Component 3198a36116SAndreas Gohr * namespace or one of its children (Symfony\Component\Console for instance), 3298a36116SAndreas Gohr * the autoloader will first look for the class under the component/ 3398a36116SAndreas Gohr * directory, and it will then fallback to the framework/ directory if not 3498a36116SAndreas Gohr * found before giving up. 3598a36116SAndreas Gohr * 3698a36116SAndreas Gohr * This class is loosely based on the Symfony UniversalClassLoader. 3798a36116SAndreas Gohr * 3898a36116SAndreas Gohr * @author Fabien Potencier <fabien@symfony.com> 3998a36116SAndreas Gohr * @author Jordi Boggiano <j.boggiano@seld.be> 40*aec1a091SAndreas Gohr * @see https://www.php-fig.org/psr/psr-0/ 41*aec1a091SAndreas Gohr * @see https://www.php-fig.org/psr/psr-4/ 4298a36116SAndreas Gohr */ 4398a36116SAndreas Gohrclass ClassLoader 4498a36116SAndreas Gohr{ 45*aec1a091SAndreas Gohr /** @var ?string */ 46*aec1a091SAndreas Gohr private $vendorDir; 47*aec1a091SAndreas Gohr 4898a36116SAndreas Gohr // PSR-4 49*aec1a091SAndreas Gohr /** 50*aec1a091SAndreas Gohr * @var array[] 51*aec1a091SAndreas Gohr * @psalm-var array<string, array<string, int>> 52*aec1a091SAndreas Gohr */ 5398a36116SAndreas Gohr private $prefixLengthsPsr4 = array(); 54*aec1a091SAndreas Gohr /** 55*aec1a091SAndreas Gohr * @var array[] 56*aec1a091SAndreas Gohr * @psalm-var array<string, array<int, string>> 57*aec1a091SAndreas Gohr */ 5898a36116SAndreas Gohr private $prefixDirsPsr4 = array(); 59*aec1a091SAndreas Gohr /** 60*aec1a091SAndreas Gohr * @var array[] 61*aec1a091SAndreas Gohr * @psalm-var array<string, string> 62*aec1a091SAndreas Gohr */ 6398a36116SAndreas Gohr private $fallbackDirsPsr4 = array(); 6498a36116SAndreas Gohr 6598a36116SAndreas Gohr // PSR-0 66*aec1a091SAndreas Gohr /** 67*aec1a091SAndreas Gohr * @var array[] 68*aec1a091SAndreas Gohr * @psalm-var array<string, array<string, string[]>> 69*aec1a091SAndreas Gohr */ 7098a36116SAndreas Gohr private $prefixesPsr0 = array(); 71*aec1a091SAndreas Gohr /** 72*aec1a091SAndreas Gohr * @var array[] 73*aec1a091SAndreas Gohr * @psalm-var array<string, string> 74*aec1a091SAndreas Gohr */ 7598a36116SAndreas Gohr private $fallbackDirsPsr0 = array(); 7698a36116SAndreas Gohr 77*aec1a091SAndreas Gohr /** @var bool */ 7898a36116SAndreas Gohr private $useIncludePath = false; 79*aec1a091SAndreas Gohr 80*aec1a091SAndreas Gohr /** 81*aec1a091SAndreas Gohr * @var string[] 82*aec1a091SAndreas Gohr * @psalm-var array<string, string> 83*aec1a091SAndreas Gohr */ 8498a36116SAndreas Gohr private $classMap = array(); 85*aec1a091SAndreas Gohr 86*aec1a091SAndreas Gohr /** @var bool */ 8798a36116SAndreas Gohr private $classMapAuthoritative = false; 88*aec1a091SAndreas Gohr 89*aec1a091SAndreas Gohr /** 90*aec1a091SAndreas Gohr * @var bool[] 91*aec1a091SAndreas Gohr * @psalm-var array<string, bool> 92*aec1a091SAndreas Gohr */ 9398a36116SAndreas Gohr private $missingClasses = array(); 94*aec1a091SAndreas Gohr 95*aec1a091SAndreas Gohr /** @var ?string */ 9698a36116SAndreas Gohr private $apcuPrefix; 9798a36116SAndreas Gohr 98*aec1a091SAndreas Gohr /** 99*aec1a091SAndreas Gohr * @var self[] 100*aec1a091SAndreas Gohr */ 101*aec1a091SAndreas Gohr private static $registeredLoaders = array(); 102*aec1a091SAndreas Gohr 103*aec1a091SAndreas Gohr /** 104*aec1a091SAndreas Gohr * @param ?string $vendorDir 105*aec1a091SAndreas Gohr */ 106*aec1a091SAndreas Gohr public function __construct($vendorDir = null) 107*aec1a091SAndreas Gohr { 108*aec1a091SAndreas Gohr $this->vendorDir = $vendorDir; 109*aec1a091SAndreas Gohr } 110*aec1a091SAndreas Gohr 111*aec1a091SAndreas Gohr /** 112*aec1a091SAndreas Gohr * @return string[] 113*aec1a091SAndreas Gohr */ 11498a36116SAndreas Gohr public function getPrefixes() 11598a36116SAndreas Gohr { 11698a36116SAndreas Gohr if (!empty($this->prefixesPsr0)) { 117*aec1a091SAndreas Gohr return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 11898a36116SAndreas Gohr } 11998a36116SAndreas Gohr 12098a36116SAndreas Gohr return array(); 12198a36116SAndreas Gohr } 12298a36116SAndreas Gohr 123*aec1a091SAndreas Gohr /** 124*aec1a091SAndreas Gohr * @return array[] 125*aec1a091SAndreas Gohr * @psalm-return array<string, array<int, string>> 126*aec1a091SAndreas Gohr */ 12798a36116SAndreas Gohr public function getPrefixesPsr4() 12898a36116SAndreas Gohr { 12998a36116SAndreas Gohr return $this->prefixDirsPsr4; 13098a36116SAndreas Gohr } 13198a36116SAndreas Gohr 132*aec1a091SAndreas Gohr /** 133*aec1a091SAndreas Gohr * @return array[] 134*aec1a091SAndreas Gohr * @psalm-return array<string, string> 135*aec1a091SAndreas Gohr */ 13698a36116SAndreas Gohr public function getFallbackDirs() 13798a36116SAndreas Gohr { 13898a36116SAndreas Gohr return $this->fallbackDirsPsr0; 13998a36116SAndreas Gohr } 14098a36116SAndreas Gohr 141*aec1a091SAndreas Gohr /** 142*aec1a091SAndreas Gohr * @return array[] 143*aec1a091SAndreas Gohr * @psalm-return array<string, string> 144*aec1a091SAndreas Gohr */ 14598a36116SAndreas Gohr public function getFallbackDirsPsr4() 14698a36116SAndreas Gohr { 14798a36116SAndreas Gohr return $this->fallbackDirsPsr4; 14898a36116SAndreas Gohr } 14998a36116SAndreas Gohr 150*aec1a091SAndreas Gohr /** 151*aec1a091SAndreas Gohr * @return string[] Array of classname => path 152*aec1a091SAndreas Gohr * @psalm-var array<string, string> 153*aec1a091SAndreas Gohr */ 15498a36116SAndreas Gohr public function getClassMap() 15598a36116SAndreas Gohr { 15698a36116SAndreas Gohr return $this->classMap; 15798a36116SAndreas Gohr } 15898a36116SAndreas Gohr 15998a36116SAndreas Gohr /** 160*aec1a091SAndreas Gohr * @param string[] $classMap Class to filename map 161*aec1a091SAndreas Gohr * @psalm-param array<string, string> $classMap 162*aec1a091SAndreas Gohr * 163*aec1a091SAndreas Gohr * @return void 16498a36116SAndreas Gohr */ 16598a36116SAndreas Gohr public function addClassMap(array $classMap) 16698a36116SAndreas Gohr { 16798a36116SAndreas Gohr if ($this->classMap) { 16898a36116SAndreas Gohr $this->classMap = array_merge($this->classMap, $classMap); 16998a36116SAndreas Gohr } else { 17098a36116SAndreas Gohr $this->classMap = $classMap; 17198a36116SAndreas Gohr } 17298a36116SAndreas Gohr } 17398a36116SAndreas Gohr 17498a36116SAndreas Gohr /** 17598a36116SAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, either 17698a36116SAndreas Gohr * appending or prepending to the ones previously set for this prefix. 17798a36116SAndreas Gohr * 17898a36116SAndreas Gohr * @param string $prefix The prefix 179*aec1a091SAndreas Gohr * @param string[]|string $paths The PSR-0 root directories 18098a36116SAndreas Gohr * @param bool $prepend Whether to prepend the directories 181*aec1a091SAndreas Gohr * 182*aec1a091SAndreas Gohr * @return void 18398a36116SAndreas Gohr */ 18498a36116SAndreas Gohr public function add($prefix, $paths, $prepend = false) 18598a36116SAndreas Gohr { 18698a36116SAndreas Gohr if (!$prefix) { 18798a36116SAndreas Gohr if ($prepend) { 18898a36116SAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 18998a36116SAndreas Gohr (array) $paths, 19098a36116SAndreas Gohr $this->fallbackDirsPsr0 19198a36116SAndreas Gohr ); 19298a36116SAndreas Gohr } else { 19398a36116SAndreas Gohr $this->fallbackDirsPsr0 = array_merge( 19498a36116SAndreas Gohr $this->fallbackDirsPsr0, 19598a36116SAndreas Gohr (array) $paths 19698a36116SAndreas Gohr ); 19798a36116SAndreas Gohr } 19898a36116SAndreas Gohr 19998a36116SAndreas Gohr return; 20098a36116SAndreas Gohr } 20198a36116SAndreas Gohr 20298a36116SAndreas Gohr $first = $prefix[0]; 20398a36116SAndreas Gohr if (!isset($this->prefixesPsr0[$first][$prefix])) { 20498a36116SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = (array) $paths; 20598a36116SAndreas Gohr 20698a36116SAndreas Gohr return; 20798a36116SAndreas Gohr } 20898a36116SAndreas Gohr if ($prepend) { 20998a36116SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 21098a36116SAndreas Gohr (array) $paths, 21198a36116SAndreas Gohr $this->prefixesPsr0[$first][$prefix] 21298a36116SAndreas Gohr ); 21398a36116SAndreas Gohr } else { 21498a36116SAndreas Gohr $this->prefixesPsr0[$first][$prefix] = array_merge( 21598a36116SAndreas Gohr $this->prefixesPsr0[$first][$prefix], 21698a36116SAndreas Gohr (array) $paths 21798a36116SAndreas Gohr ); 21898a36116SAndreas Gohr } 21998a36116SAndreas Gohr } 22098a36116SAndreas Gohr 22198a36116SAndreas Gohr /** 22298a36116SAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, either 22398a36116SAndreas Gohr * appending or prepending to the ones previously set for this namespace. 22498a36116SAndreas Gohr * 22598a36116SAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 226*aec1a091SAndreas Gohr * @param string[]|string $paths The PSR-4 base directories 22798a36116SAndreas Gohr * @param bool $prepend Whether to prepend the directories 22898a36116SAndreas Gohr * 22998a36116SAndreas Gohr * @throws \InvalidArgumentException 230*aec1a091SAndreas Gohr * 231*aec1a091SAndreas Gohr * @return void 23298a36116SAndreas Gohr */ 23398a36116SAndreas Gohr public function addPsr4($prefix, $paths, $prepend = false) 23498a36116SAndreas Gohr { 23598a36116SAndreas Gohr if (!$prefix) { 23698a36116SAndreas Gohr // Register directories for the root namespace. 23798a36116SAndreas Gohr if ($prepend) { 23898a36116SAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 23998a36116SAndreas Gohr (array) $paths, 24098a36116SAndreas Gohr $this->fallbackDirsPsr4 24198a36116SAndreas Gohr ); 24298a36116SAndreas Gohr } else { 24398a36116SAndreas Gohr $this->fallbackDirsPsr4 = array_merge( 24498a36116SAndreas Gohr $this->fallbackDirsPsr4, 24598a36116SAndreas Gohr (array) $paths 24698a36116SAndreas Gohr ); 24798a36116SAndreas Gohr } 24898a36116SAndreas Gohr } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 24998a36116SAndreas Gohr // Register directories for a new namespace. 25098a36116SAndreas Gohr $length = strlen($prefix); 25198a36116SAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 25298a36116SAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 25398a36116SAndreas Gohr } 25498a36116SAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 25598a36116SAndreas Gohr $this->prefixDirsPsr4[$prefix] = (array) $paths; 25698a36116SAndreas Gohr } elseif ($prepend) { 25798a36116SAndreas Gohr // Prepend directories for an already registered namespace. 25898a36116SAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 25998a36116SAndreas Gohr (array) $paths, 26098a36116SAndreas Gohr $this->prefixDirsPsr4[$prefix] 26198a36116SAndreas Gohr ); 26298a36116SAndreas Gohr } else { 26398a36116SAndreas Gohr // Append directories for an already registered namespace. 26498a36116SAndreas Gohr $this->prefixDirsPsr4[$prefix] = array_merge( 26598a36116SAndreas Gohr $this->prefixDirsPsr4[$prefix], 26698a36116SAndreas Gohr (array) $paths 26798a36116SAndreas Gohr ); 26898a36116SAndreas Gohr } 26998a36116SAndreas Gohr } 27098a36116SAndreas Gohr 27198a36116SAndreas Gohr /** 27298a36116SAndreas Gohr * Registers a set of PSR-0 directories for a given prefix, 27398a36116SAndreas Gohr * replacing any others previously set for this prefix. 27498a36116SAndreas Gohr * 27598a36116SAndreas Gohr * @param string $prefix The prefix 276*aec1a091SAndreas Gohr * @param string[]|string $paths The PSR-0 base directories 277*aec1a091SAndreas Gohr * 278*aec1a091SAndreas Gohr * @return void 27998a36116SAndreas Gohr */ 28098a36116SAndreas Gohr public function set($prefix, $paths) 28198a36116SAndreas Gohr { 28298a36116SAndreas Gohr if (!$prefix) { 28398a36116SAndreas Gohr $this->fallbackDirsPsr0 = (array) $paths; 28498a36116SAndreas Gohr } else { 28598a36116SAndreas Gohr $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 28698a36116SAndreas Gohr } 28798a36116SAndreas Gohr } 28898a36116SAndreas Gohr 28998a36116SAndreas Gohr /** 29098a36116SAndreas Gohr * Registers a set of PSR-4 directories for a given namespace, 29198a36116SAndreas Gohr * replacing any others previously set for this namespace. 29298a36116SAndreas Gohr * 29398a36116SAndreas Gohr * @param string $prefix The prefix/namespace, with trailing '\\' 294*aec1a091SAndreas Gohr * @param string[]|string $paths The PSR-4 base directories 29598a36116SAndreas Gohr * 29698a36116SAndreas Gohr * @throws \InvalidArgumentException 297*aec1a091SAndreas Gohr * 298*aec1a091SAndreas Gohr * @return void 29998a36116SAndreas Gohr */ 30098a36116SAndreas Gohr public function setPsr4($prefix, $paths) 30198a36116SAndreas Gohr { 30298a36116SAndreas Gohr if (!$prefix) { 30398a36116SAndreas Gohr $this->fallbackDirsPsr4 = (array) $paths; 30498a36116SAndreas Gohr } else { 30598a36116SAndreas Gohr $length = strlen($prefix); 30698a36116SAndreas Gohr if ('\\' !== $prefix[$length - 1]) { 30798a36116SAndreas Gohr throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 30898a36116SAndreas Gohr } 30998a36116SAndreas Gohr $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 31098a36116SAndreas Gohr $this->prefixDirsPsr4[$prefix] = (array) $paths; 31198a36116SAndreas Gohr } 31298a36116SAndreas Gohr } 31398a36116SAndreas Gohr 31498a36116SAndreas Gohr /** 31598a36116SAndreas Gohr * Turns on searching the include path for class files. 31698a36116SAndreas Gohr * 31798a36116SAndreas Gohr * @param bool $useIncludePath 318*aec1a091SAndreas Gohr * 319*aec1a091SAndreas Gohr * @return void 32098a36116SAndreas Gohr */ 32198a36116SAndreas Gohr public function setUseIncludePath($useIncludePath) 32298a36116SAndreas Gohr { 32398a36116SAndreas Gohr $this->useIncludePath = $useIncludePath; 32498a36116SAndreas Gohr } 32598a36116SAndreas Gohr 32698a36116SAndreas Gohr /** 32798a36116SAndreas Gohr * Can be used to check if the autoloader uses the include path to check 32898a36116SAndreas Gohr * for classes. 32998a36116SAndreas Gohr * 33098a36116SAndreas Gohr * @return bool 33198a36116SAndreas Gohr */ 33298a36116SAndreas Gohr public function getUseIncludePath() 33398a36116SAndreas Gohr { 33498a36116SAndreas Gohr return $this->useIncludePath; 33598a36116SAndreas Gohr } 33698a36116SAndreas Gohr 33798a36116SAndreas Gohr /** 33898a36116SAndreas Gohr * Turns off searching the prefix and fallback directories for classes 33998a36116SAndreas Gohr * that have not been registered with the class map. 34098a36116SAndreas Gohr * 34198a36116SAndreas Gohr * @param bool $classMapAuthoritative 342*aec1a091SAndreas Gohr * 343*aec1a091SAndreas Gohr * @return void 34498a36116SAndreas Gohr */ 34598a36116SAndreas Gohr public function setClassMapAuthoritative($classMapAuthoritative) 34698a36116SAndreas Gohr { 34798a36116SAndreas Gohr $this->classMapAuthoritative = $classMapAuthoritative; 34898a36116SAndreas Gohr } 34998a36116SAndreas Gohr 35098a36116SAndreas Gohr /** 35198a36116SAndreas Gohr * Should class lookup fail if not found in the current class map? 35298a36116SAndreas Gohr * 35398a36116SAndreas Gohr * @return bool 35498a36116SAndreas Gohr */ 35598a36116SAndreas Gohr public function isClassMapAuthoritative() 35698a36116SAndreas Gohr { 35798a36116SAndreas Gohr return $this->classMapAuthoritative; 35898a36116SAndreas Gohr } 35998a36116SAndreas Gohr 36098a36116SAndreas Gohr /** 36198a36116SAndreas Gohr * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 36298a36116SAndreas Gohr * 36398a36116SAndreas Gohr * @param string|null $apcuPrefix 364*aec1a091SAndreas Gohr * 365*aec1a091SAndreas Gohr * @return void 36698a36116SAndreas Gohr */ 36798a36116SAndreas Gohr public function setApcuPrefix($apcuPrefix) 36898a36116SAndreas Gohr { 36998a36116SAndreas Gohr $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 37098a36116SAndreas Gohr } 37198a36116SAndreas Gohr 37298a36116SAndreas Gohr /** 37398a36116SAndreas Gohr * The APCu prefix in use, or null if APCu caching is not enabled. 37498a36116SAndreas Gohr * 37598a36116SAndreas Gohr * @return string|null 37698a36116SAndreas Gohr */ 37798a36116SAndreas Gohr public function getApcuPrefix() 37898a36116SAndreas Gohr { 37998a36116SAndreas Gohr return $this->apcuPrefix; 38098a36116SAndreas Gohr } 38198a36116SAndreas Gohr 38298a36116SAndreas Gohr /** 38398a36116SAndreas Gohr * Registers this instance as an autoloader. 38498a36116SAndreas Gohr * 38598a36116SAndreas Gohr * @param bool $prepend Whether to prepend the autoloader or not 386*aec1a091SAndreas Gohr * 387*aec1a091SAndreas Gohr * @return void 38898a36116SAndreas Gohr */ 38998a36116SAndreas Gohr public function register($prepend = false) 39098a36116SAndreas Gohr { 39198a36116SAndreas Gohr spl_autoload_register(array($this, 'loadClass'), true, $prepend); 392*aec1a091SAndreas Gohr 393*aec1a091SAndreas Gohr if (null === $this->vendorDir) { 394*aec1a091SAndreas Gohr return; 395*aec1a091SAndreas Gohr } 396*aec1a091SAndreas Gohr 397*aec1a091SAndreas Gohr if ($prepend) { 398*aec1a091SAndreas Gohr self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 399*aec1a091SAndreas Gohr } else { 400*aec1a091SAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 401*aec1a091SAndreas Gohr self::$registeredLoaders[$this->vendorDir] = $this; 402*aec1a091SAndreas Gohr } 40398a36116SAndreas Gohr } 40498a36116SAndreas Gohr 40598a36116SAndreas Gohr /** 40698a36116SAndreas Gohr * Unregisters this instance as an autoloader. 407*aec1a091SAndreas Gohr * 408*aec1a091SAndreas Gohr * @return void 40998a36116SAndreas Gohr */ 41098a36116SAndreas Gohr public function unregister() 41198a36116SAndreas Gohr { 41298a36116SAndreas Gohr spl_autoload_unregister(array($this, 'loadClass')); 413*aec1a091SAndreas Gohr 414*aec1a091SAndreas Gohr if (null !== $this->vendorDir) { 415*aec1a091SAndreas Gohr unset(self::$registeredLoaders[$this->vendorDir]); 416*aec1a091SAndreas Gohr } 41798a36116SAndreas Gohr } 41898a36116SAndreas Gohr 41998a36116SAndreas Gohr /** 42098a36116SAndreas Gohr * Loads the given class or interface. 42198a36116SAndreas Gohr * 42298a36116SAndreas Gohr * @param string $class The name of the class 423*aec1a091SAndreas Gohr * @return true|null True if loaded, null otherwise 42498a36116SAndreas Gohr */ 42598a36116SAndreas Gohr public function loadClass($class) 42698a36116SAndreas Gohr { 42798a36116SAndreas Gohr if ($file = $this->findFile($class)) { 42898a36116SAndreas Gohr includeFile($file); 42998a36116SAndreas Gohr 43098a36116SAndreas Gohr return true; 43198a36116SAndreas Gohr } 432*aec1a091SAndreas Gohr 433*aec1a091SAndreas Gohr return null; 43498a36116SAndreas Gohr } 43598a36116SAndreas Gohr 43698a36116SAndreas Gohr /** 43798a36116SAndreas Gohr * Finds the path to the file where the class is defined. 43898a36116SAndreas Gohr * 43998a36116SAndreas Gohr * @param string $class The name of the class 44098a36116SAndreas Gohr * 44198a36116SAndreas Gohr * @return string|false The path if found, false otherwise 44298a36116SAndreas Gohr */ 44398a36116SAndreas Gohr public function findFile($class) 44498a36116SAndreas Gohr { 44598a36116SAndreas Gohr // class map lookup 44698a36116SAndreas Gohr if (isset($this->classMap[$class])) { 44798a36116SAndreas Gohr return $this->classMap[$class]; 44898a36116SAndreas Gohr } 44998a36116SAndreas Gohr if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 45098a36116SAndreas Gohr return false; 45198a36116SAndreas Gohr } 45298a36116SAndreas Gohr if (null !== $this->apcuPrefix) { 45398a36116SAndreas Gohr $file = apcu_fetch($this->apcuPrefix.$class, $hit); 45498a36116SAndreas Gohr if ($hit) { 45598a36116SAndreas Gohr return $file; 45698a36116SAndreas Gohr } 45798a36116SAndreas Gohr } 45898a36116SAndreas Gohr 45998a36116SAndreas Gohr $file = $this->findFileWithExtension($class, '.php'); 46098a36116SAndreas Gohr 46198a36116SAndreas Gohr // Search for Hack files if we are running on HHVM 46298a36116SAndreas Gohr if (false === $file && defined('HHVM_VERSION')) { 46398a36116SAndreas Gohr $file = $this->findFileWithExtension($class, '.hh'); 46498a36116SAndreas Gohr } 46598a36116SAndreas Gohr 46698a36116SAndreas Gohr if (null !== $this->apcuPrefix) { 46798a36116SAndreas Gohr apcu_add($this->apcuPrefix.$class, $file); 46898a36116SAndreas Gohr } 46998a36116SAndreas Gohr 47098a36116SAndreas Gohr if (false === $file) { 47198a36116SAndreas Gohr // Remember that this class does not exist. 47298a36116SAndreas Gohr $this->missingClasses[$class] = true; 47398a36116SAndreas Gohr } 47498a36116SAndreas Gohr 47598a36116SAndreas Gohr return $file; 47698a36116SAndreas Gohr } 47798a36116SAndreas Gohr 478*aec1a091SAndreas Gohr /** 479*aec1a091SAndreas Gohr * Returns the currently registered loaders indexed by their corresponding vendor directories. 480*aec1a091SAndreas Gohr * 481*aec1a091SAndreas Gohr * @return self[] 482*aec1a091SAndreas Gohr */ 483*aec1a091SAndreas Gohr public static function getRegisteredLoaders() 484*aec1a091SAndreas Gohr { 485*aec1a091SAndreas Gohr return self::$registeredLoaders; 486*aec1a091SAndreas Gohr } 487*aec1a091SAndreas Gohr 488*aec1a091SAndreas Gohr /** 489*aec1a091SAndreas Gohr * @param string $class 490*aec1a091SAndreas Gohr * @param string $ext 491*aec1a091SAndreas Gohr * @return string|false 492*aec1a091SAndreas Gohr */ 49398a36116SAndreas Gohr private function findFileWithExtension($class, $ext) 49498a36116SAndreas Gohr { 49598a36116SAndreas Gohr // PSR-4 lookup 49698a36116SAndreas Gohr $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 49798a36116SAndreas Gohr 49898a36116SAndreas Gohr $first = $class[0]; 49998a36116SAndreas Gohr if (isset($this->prefixLengthsPsr4[$first])) { 50098a36116SAndreas Gohr $subPath = $class; 50198a36116SAndreas Gohr while (false !== $lastPos = strrpos($subPath, '\\')) { 50298a36116SAndreas Gohr $subPath = substr($subPath, 0, $lastPos); 50398a36116SAndreas Gohr $search = $subPath . '\\'; 50498a36116SAndreas Gohr if (isset($this->prefixDirsPsr4[$search])) { 50598a36116SAndreas Gohr $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 50698a36116SAndreas Gohr foreach ($this->prefixDirsPsr4[$search] as $dir) { 50798a36116SAndreas Gohr if (file_exists($file = $dir . $pathEnd)) { 50898a36116SAndreas Gohr return $file; 50998a36116SAndreas Gohr } 51098a36116SAndreas Gohr } 51198a36116SAndreas Gohr } 51298a36116SAndreas Gohr } 51398a36116SAndreas Gohr } 51498a36116SAndreas Gohr 51598a36116SAndreas Gohr // PSR-4 fallback dirs 51698a36116SAndreas Gohr foreach ($this->fallbackDirsPsr4 as $dir) { 51798a36116SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 51898a36116SAndreas Gohr return $file; 51998a36116SAndreas Gohr } 52098a36116SAndreas Gohr } 52198a36116SAndreas Gohr 52298a36116SAndreas Gohr // PSR-0 lookup 52398a36116SAndreas Gohr if (false !== $pos = strrpos($class, '\\')) { 52498a36116SAndreas Gohr // namespaced class name 52598a36116SAndreas Gohr $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 52698a36116SAndreas Gohr . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 52798a36116SAndreas Gohr } else { 52898a36116SAndreas Gohr // PEAR-like class name 52998a36116SAndreas Gohr $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 53098a36116SAndreas Gohr } 53198a36116SAndreas Gohr 53298a36116SAndreas Gohr if (isset($this->prefixesPsr0[$first])) { 53398a36116SAndreas Gohr foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 53498a36116SAndreas Gohr if (0 === strpos($class, $prefix)) { 53598a36116SAndreas Gohr foreach ($dirs as $dir) { 53698a36116SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 53798a36116SAndreas Gohr return $file; 53898a36116SAndreas Gohr } 53998a36116SAndreas Gohr } 54098a36116SAndreas Gohr } 54198a36116SAndreas Gohr } 54298a36116SAndreas Gohr } 54398a36116SAndreas Gohr 54498a36116SAndreas Gohr // PSR-0 fallback dirs 54598a36116SAndreas Gohr foreach ($this->fallbackDirsPsr0 as $dir) { 54698a36116SAndreas Gohr if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 54798a36116SAndreas Gohr return $file; 54898a36116SAndreas Gohr } 54998a36116SAndreas Gohr } 55098a36116SAndreas Gohr 55198a36116SAndreas Gohr // PSR-0 include paths. 55298a36116SAndreas Gohr if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 55398a36116SAndreas Gohr return $file; 55498a36116SAndreas Gohr } 55598a36116SAndreas Gohr 55698a36116SAndreas Gohr return false; 55798a36116SAndreas Gohr } 55898a36116SAndreas Gohr} 55998a36116SAndreas Gohr 56098a36116SAndreas Gohr/** 56198a36116SAndreas Gohr * Scope isolated include. 56298a36116SAndreas Gohr * 56398a36116SAndreas Gohr * Prevents access to $this/self from included files. 564*aec1a091SAndreas Gohr * 565*aec1a091SAndreas Gohr * @param string $file 566*aec1a091SAndreas Gohr * @return void 567*aec1a091SAndreas Gohr * @private 56898a36116SAndreas Gohr */ 56998a36116SAndreas Gohrfunction includeFile($file) 57098a36116SAndreas Gohr{ 57198a36116SAndreas Gohr include $file; 57298a36116SAndreas Gohr} 573