@ -42,30 +42,76 @@ namespace Composer\Autoload;
*/
*/
class ClassLoader
class ClassLoader
{
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
private $vendorDir;
// PSR-4
// PSR-4
/**
* @var array< string , array < string , int > >
*/
private $prefixLengthsPsr4 = array();
private $prefixLengthsPsr4 = array();
/**
* @var array< string , list < string > >
*/
private $prefixDirsPsr4 = array();
private $prefixDirsPsr4 = array();
/**
* @var list< string >
*/
private $fallbackDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array< string , array < string , list < string > >>
*/
private $prefixesPsr0 = array();
private $prefixesPsr0 = array();
/**
* @var list< string >
*/
private $fallbackDirsPsr0 = array();
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
private $useIncludePath = false;
/**
* @var array< string , string >
*/
private $classMap = array();
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
private $classMapAuthoritative = false;
/**
* @var array< string , bool >
*/
private $missingClasses = array();
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
private $apcuPrefix;
/**
* @var array< string , self >
*/
private static $registeredLoaders = array();
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
public function __construct($vendorDir = null)
{
{
$this->vendorDir = $vendorDir;
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
}
/**
* @return array< string , list < string > >
*/
public function getPrefixes()
public function getPrefixes()
{
{
if (!empty($this->prefixesPsr0)) {
if (!empty($this->prefixesPsr0)) {
@ -75,28 +121,42 @@ class ClassLoader
return array();
return array();
}
}
/**
* @return array< string , list < string > >
*/
public function getPrefixesPsr4()
public function getPrefixesPsr4()
{
{
return $this->prefixDirsPsr4;
return $this->prefixDirsPsr4;
}
}
/**
* @return list< string >
*/
public function getFallbackDirs()
public function getFallbackDirs()
{
{
return $this->fallbackDirsPsr0;
return $this->fallbackDirsPsr0;
}
}
/**
* @return list< string >
*/
public function getFallbackDirsPsr4()
public function getFallbackDirsPsr4()
{
{
return $this->fallbackDirsPsr4;
return $this->fallbackDirsPsr4;
}
}
/**
* @return array< string , string > Array of classname => path
*/
public function getClassMap()
public function getClassMap()
{
{
return $this->classMap;
return $this->classMap;
}
}
/**
/**
* @param array $classMap Class to filename map
* @param array< string , string > $classMap Class to filename map
*
* @return void
*/
*/
public function addClassMap(array $classMap)
public function addClassMap(array $classMap)
{
{
@ -111,22 +171,25 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix, either
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
* appending or prepending to the ones previously set for this prefix.
*
*
* @param string $prefix The prefix
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param list< string > |string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
*/
public function add($prefix, $paths, $prepend = false)
public function add($prefix, $paths, $prepend = false)
{
{
$paths = (array) $paths;
if (!$prefix) {
if (!$prefix) {
if ($prepend) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$paths,
$this->fallbackDirsPsr0
$this->fallbackDirsPsr0
);
);
} else {
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$this->fallbackDirsPsr0,
(array) $paths
$paths
);
);
}
}
@ -135,19 +198,19 @@ class ClassLoader
$first = $prefix[0];
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
return;
}
}
if ($prepend) {
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$paths,
$this->prefixesPsr0[$first][$prefix]
$this->prefixesPsr0[$first][$prefix]
);
);
} else {
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$this->prefixesPsr0[$first][$prefix],
(array) $paths
$paths
);
);
}
}
}
}
@ -156,25 +219,28 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace, either
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
* appending or prepending to the ones previously set for this namespace.
*
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array |string $paths The PSR-4 base directories
* @param list< string > |string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
* @param bool $prepend Whether to prepend the directories
*
*
* @throws \InvalidArgumentException
* @throws \InvalidArgumentException
*
* @return void
*/
*/
public function addPsr4($prefix, $paths, $prepend = false)
public function addPsr4($prefix, $paths, $prepend = false)
{
{
$paths = (array) $paths;
if (!$prefix) {
if (!$prefix) {
// Register directories for the root namespace.
// Register directories for the root namespace.
if ($prepend) {
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$paths,
$this->fallbackDirsPsr4
$this->fallbackDirsPsr4
);
);
} else {
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$this->fallbackDirsPsr4,
(array) $paths
$paths
);
);
}
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
@ -184,18 +250,18 @@ class ClassLoader
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$paths,
$this->prefixDirsPsr4[$prefix]
$this->prefixDirsPsr4[$prefix]
);
);
} else {
} else {
// Append directories for an already registered namespace.
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$this->prefixDirsPsr4[$prefix],
(array) $paths
$paths
);
);
}
}
}
}
@ -204,8 +270,10 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix,
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
* replacing any others previously set for this prefix.
*
*
* @param string $prefix The prefix
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
* @param list< string > |string $paths The PSR-0 base directories
*
* @return void
*/
*/
public function set($prefix, $paths)
public function set($prefix, $paths)
{
{
@ -220,10 +288,12 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace,
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
* replacing any others previously set for this namespace.
*
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array |string $paths The PSR-4 base directories
* @param list< string > |string $paths The PSR-4 base directories
*
*
* @throws \InvalidArgumentException
* @throws \InvalidArgumentException
*
* @return void
*/
*/
public function setPsr4($prefix, $paths)
public function setPsr4($prefix, $paths)
{
{
@ -243,6 +313,8 @@ class ClassLoader
* Turns on searching the include path for class files.
* Turns on searching the include path for class files.
*
*
* @param bool $useIncludePath
* @param bool $useIncludePath
*
* @return void
*/
*/
public function setUseIncludePath($useIncludePath)
public function setUseIncludePath($useIncludePath)
{
{
@ -265,6 +337,8 @@ class ClassLoader
* that have not been registered with the class map.
* that have not been registered with the class map.
*
*
* @param bool $classMapAuthoritative
* @param bool $classMapAuthoritative
*
* @return void
*/
*/
public function setClassMapAuthoritative($classMapAuthoritative)
public function setClassMapAuthoritative($classMapAuthoritative)
{
{
@ -285,6 +359,8 @@ class ClassLoader
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
*
* @param string|null $apcuPrefix
* @param string|null $apcuPrefix
*
* @return void
*/
*/
public function setApcuPrefix($apcuPrefix)
public function setApcuPrefix($apcuPrefix)
{
{
@ -305,6 +381,8 @@ class ClassLoader
* Registers this instance as an autoloader.
* Registers this instance as an autoloader.
*
*
* @param bool $prepend Whether to prepend the autoloader or not
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
*/
public function register($prepend = false)
public function register($prepend = false)
{
{
@ -324,6 +402,8 @@ class ClassLoader
/**
/**
* Unregisters this instance as an autoloader.
* Unregisters this instance as an autoloader.
*
* @return void
*/
*/
public function unregister()
public function unregister()
{
{
@ -343,7 +423,8 @@ class ClassLoader
public function loadClass($class)
public function loadClass($class)
{
{
if ($file = $this->findFile($class)) {
if ($file = $this->findFile($class)) {
includeFile($file);
$includeFile = self::$includeFile;
$includeFile($file);
return true;
return true;
}
}
@ -394,15 +475,20 @@ class ClassLoader
}
}
/**
/**
* Returns the currently registered loaders index ed by their corresponding vendor directories.
* Returns the currently registered loaders key ed by their corresponding vendor directories.
*
*
* @return self[]
* @return array< string , self >
*/
*/
public static function getRegisteredLoaders()
public static function getRegisteredLoaders()
{
{
return self::$registeredLoaders;
return self::$registeredLoaders;
}
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
private function findFileWithExtension($class, $ext)
{
{
// PSR-4 lookup
// PSR-4 lookup
@ -468,14 +554,26 @@ class ClassLoader
return false;
return false;
}
}
}
/**
/**
* Scope isolated include.
* @return void
*
*/
* Prevents access to $this/self from included files.
private static function initializeIncludeClosure()
*/
{
function includeFile($file)
if (self::$includeFile !== null) {
{
return;
include $file;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}
}