| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 | <?php/* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> *     Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Composer\Autoload;/** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * *     $loader = new \Composer\Autoload\ClassLoader(); * *     // register classes with namespaces *     $loader->add('Symfony\Component', __DIR__.'/component'); *     $loader->add('Symfony',           __DIR__.'/framework'); * *     // activate the autoloader *     $loader->register(); * *     // to enable searching the include path (eg. for PEAR packages) *     $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier <fabien@symfony.com> * @author Jordi Boggiano <j.boggiano@seld.be> * @see    https://www.php-fig.org/psr/psr-0/ * @see    https://www.php-fig.org/psr/psr-4/ */class ClassLoader{    /** @var \Closure(string):void */    private static $includeFile;    /** @var ?string */    private $vendorDir;    // PSR-4    /**     * @var array[]     * @psalm-var array<string, array<string, int>>     */    private $prefixLengthsPsr4 = array();    /**     * @var array[]     * @psalm-var array<string, array<int, string>>     */    private $prefixDirsPsr4 = array();    /**     * @var array[]     * @psalm-var array<string, string>     */    private $fallbackDirsPsr4 = array();    // PSR-0    /**     * @var array[]     * @psalm-var array<string, array<string, string[]>>     */    private $prefixesPsr0 = array();    /**     * @var array[]     * @psalm-var array<string, string>     */    private $fallbackDirsPsr0 = array();    /** @var bool */    private $useIncludePath = false;    /**     * @var string[]     * @psalm-var array<string, string>     */    private $classMap = array();    /** @var bool */    private $classMapAuthoritative = false;    /**     * @var bool[]     * @psalm-var array<string, bool>     */    private $missingClasses = array();    /** @var ?string */    private $apcuPrefix;    /**     * @var self[]     */    private static $registeredLoaders = array();    /**     * @param ?string $vendorDir     */    public function __construct($vendorDir = null)    {        $this->vendorDir = $vendorDir;        self::initializeIncludeClosure();    }    /**     * @return string[]     */    public function getPrefixes()    {        if (!empty($this->prefixesPsr0)) {            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));        }        return array();    }    /**     * @return array[]     * @psalm-return array<string, array<int, string>>     */    public function getPrefixesPsr4()    {        return $this->prefixDirsPsr4;    }    /**     * @return array[]     * @psalm-return array<string, string>     */    public function getFallbackDirs()    {        return $this->fallbackDirsPsr0;    }    /**     * @return array[]     * @psalm-return array<string, string>     */    public function getFallbackDirsPsr4()    {        return $this->fallbackDirsPsr4;    }    /**     * @return string[] Array of classname => path     * @psalm-return array<string, string>     */    public function getClassMap()    {        return $this->classMap;    }    /**     * @param string[] $classMap Class to filename map     * @psalm-param array<string, string> $classMap     *     * @return void     */    public function addClassMap(array $classMap)    {        if ($this->classMap) {            $this->classMap = array_merge($this->classMap, $classMap);        } else {            $this->classMap = $classMap;        }    }    /**     * Registers a set of PSR-0 directories for a given prefix, either     * appending or prepending to the ones previously set for this prefix.     *     * @param string          $prefix  The prefix     * @param string[]|string $paths   The PSR-0 root directories     * @param bool            $prepend Whether to prepend the directories     *     * @return void     */    public function add($prefix, $paths, $prepend = false)    {        if (!$prefix) {            if ($prepend) {                $this->fallbackDirsPsr0 = array_merge(                    (array) $paths,                    $this->fallbackDirsPsr0                );            } else {                $this->fallbackDirsPsr0 = array_merge(                    $this->fallbackDirsPsr0,                    (array) $paths                );            }            return;        }        $first = $prefix[0];        if (!isset($this->prefixesPsr0[$first][$prefix])) {            $this->prefixesPsr0[$first][$prefix] = (array) $paths;            return;        }        if ($prepend) {            $this->prefixesPsr0[$first][$prefix] = array_merge(                (array) $paths,                $this->prefixesPsr0[$first][$prefix]            );        } else {            $this->prefixesPsr0[$first][$prefix] = array_merge(                $this->prefixesPsr0[$first][$prefix],                (array) $paths            );        }    }    /**     * Registers a set of PSR-4 directories for a given namespace, either     * appending or prepending to the ones previously set for this namespace.     *     * @param string          $prefix  The prefix/namespace, with trailing '\\'     * @param string[]|string $paths   The PSR-4 base directories     * @param bool            $prepend Whether to prepend the directories     *     * @throws \InvalidArgumentException     *     * @return void     */    public function addPsr4($prefix, $paths, $prepend = false)    {        if (!$prefix) {            // Register directories for the root namespace.            if ($prepend) {                $this->fallbackDirsPsr4 = array_merge(                    (array) $paths,                    $this->fallbackDirsPsr4                );            } else {                $this->fallbackDirsPsr4 = array_merge(                    $this->fallbackDirsPsr4,                    (array) $paths                );            }        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {            // Register directories for a new namespace.            $length = strlen($prefix);            if ('\\' !== $prefix[$length - 1]) {                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");            }            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;            $this->prefixDirsPsr4[$prefix] = (array) $paths;        } elseif ($prepend) {            // Prepend directories for an already registered namespace.            $this->prefixDirsPsr4[$prefix] = array_merge(                (array) $paths,                $this->prefixDirsPsr4[$prefix]            );        } else {            // Append directories for an already registered namespace.            $this->prefixDirsPsr4[$prefix] = array_merge(                $this->prefixDirsPsr4[$prefix],                (array) $paths            );        }    }    /**     * Registers a set of PSR-0 directories for a given prefix,     * replacing any others previously set for this prefix.     *     * @param string          $prefix The prefix     * @param string[]|string $paths  The PSR-0 base directories     *     * @return void     */    public function set($prefix, $paths)    {        if (!$prefix) {            $this->fallbackDirsPsr0 = (array) $paths;        } else {            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;        }    }    /**     * Registers a set of PSR-4 directories for a given namespace,     * replacing any others previously set for this namespace.     *     * @param string          $prefix The prefix/namespace, with trailing '\\'     * @param string[]|string $paths  The PSR-4 base directories     *     * @throws \InvalidArgumentException     *     * @return void     */    public function setPsr4($prefix, $paths)    {        if (!$prefix) {            $this->fallbackDirsPsr4 = (array) $paths;        } else {            $length = strlen($prefix);            if ('\\' !== $prefix[$length - 1]) {                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");            }            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;            $this->prefixDirsPsr4[$prefix] = (array) $paths;        }    }    /**     * Turns on searching the include path for class files.     *     * @param bool $useIncludePath     *     * @return void     */    public function setUseIncludePath($useIncludePath)    {        $this->useIncludePath = $useIncludePath;    }    /**     * Can be used to check if the autoloader uses the include path to check     * for classes.     *     * @return bool     */    public function getUseIncludePath()    {        return $this->useIncludePath;    }    /**     * Turns off searching the prefix and fallback directories for classes     * that have not been registered with the class map.     *     * @param bool $classMapAuthoritative     *     * @return void     */    public function setClassMapAuthoritative($classMapAuthoritative)    {        $this->classMapAuthoritative = $classMapAuthoritative;    }    /**     * Should class lookup fail if not found in the current class map?     *     * @return bool     */    public function isClassMapAuthoritative()    {        return $this->classMapAuthoritative;    }    /**     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.     *     * @param string|null $apcuPrefix     *     * @return void     */    public function setApcuPrefix($apcuPrefix)    {        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;    }    /**     * The APCu prefix in use, or null if APCu caching is not enabled.     *     * @return string|null     */    public function getApcuPrefix()    {        return $this->apcuPrefix;    }    /**     * Registers this instance as an autoloader.     *     * @param bool $prepend Whether to prepend the autoloader or not     *     * @return void     */    public function register($prepend = false)    {        spl_autoload_register(array($this, 'loadClass'), true, $prepend);        if (null === $this->vendorDir) {            return;        }        if ($prepend) {            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;        } else {            unset(self::$registeredLoaders[$this->vendorDir]);            self::$registeredLoaders[$this->vendorDir] = $this;        }    }    /**     * Unregisters this instance as an autoloader.     *     * @return void     */    public function unregister()    {        spl_autoload_unregister(array($this, 'loadClass'));        if (null !== $this->vendorDir) {            unset(self::$registeredLoaders[$this->vendorDir]);        }    }    /**     * Loads the given class or interface.     *     * @param  string    $class The name of the class     * @return true|null True if loaded, null otherwise     */    public function loadClass($class)    {        if ($file = $this->findFile($class)) {            (self::$includeFile)($file);            return true;        }        return null;    }    /**     * Finds the path to the file where the class is defined.     *     * @param string $class The name of the class     *     * @return string|false The path if found, false otherwise     */    public function findFile($class)    {        // class map lookup        if (isset($this->classMap[$class])) {            return $this->classMap[$class];        }        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {            return false;        }        if (null !== $this->apcuPrefix) {            $file = apcu_fetch($this->apcuPrefix.$class, $hit);            if ($hit) {                return $file;            }        }        $file = $this->findFileWithExtension($class, '.php');        // Search for Hack files if we are running on HHVM        if (false === $file && defined('HHVM_VERSION')) {            $file = $this->findFileWithExtension($class, '.hh');        }        if (null !== $this->apcuPrefix) {            apcu_add($this->apcuPrefix.$class, $file);        }        if (false === $file) {            // Remember that this class does not exist.            $this->missingClasses[$class] = true;        }        return $file;    }    /**     * Returns the currently registered loaders indexed by their corresponding vendor directories.     *     * @return self[]     */    public static function getRegisteredLoaders()    {        return self::$registeredLoaders;    }    /**     * @param  string       $class     * @param  string       $ext     * @return string|false     */    private function findFileWithExtension($class, $ext)    {        // PSR-4 lookup        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;        $first = $class[0];        if (isset($this->prefixLengthsPsr4[$first])) {            $subPath = $class;            while (false !== $lastPos = strrpos($subPath, '\\')) {                $subPath = substr($subPath, 0, $lastPos);                $search = $subPath . '\\';                if (isset($this->prefixDirsPsr4[$search])) {                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);                    foreach ($this->prefixDirsPsr4[$search] as $dir) {                        if (file_exists($file = $dir . $pathEnd)) {                            return $file;                        }                    }                }            }        }        // PSR-4 fallback dirs        foreach ($this->fallbackDirsPsr4 as $dir) {            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {                return $file;            }        }        // PSR-0 lookup        if (false !== $pos = strrpos($class, '\\')) {            // namespaced class name            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);        } else {            // PEAR-like class name            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;        }        if (isset($this->prefixesPsr0[$first])) {            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {                if (0 === strpos($class, $prefix)) {                    foreach ($dirs as $dir) {                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {                            return $file;                        }                    }                }            }        }        // PSR-0 fallback dirs        foreach ($this->fallbackDirsPsr0 as $dir) {            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {                return $file;            }        }        // PSR-0 include paths.        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {            return $file;        }        return false;    }    private static function initializeIncludeClosure(): void    {        if (self::$includeFile !== null) {            return;        }        /**         * Scope isolated include.         *         * Prevents access to $this/self from included files.         *         * @param  string $file         * @return void         */        self::$includeFile = static function($file) {            include $file;        };    }}
 |