xref: /plugin/aichat/vendor/composer/ClassLoader.php (revision 3379af09b7ec10f96a8d4f23b1563bd7f9ae79ac)
18817535bSAndreas Gohr<?php
28817535bSAndreas Gohr
38817535bSAndreas Gohr/*
48817535bSAndreas Gohr * This file is part of Composer.
58817535bSAndreas Gohr *
68817535bSAndreas Gohr * (c) Nils Adermann <naderman@naderman.de>
78817535bSAndreas Gohr *     Jordi Boggiano <j.boggiano@seld.be>
88817535bSAndreas Gohr *
98817535bSAndreas Gohr * For the full copyright and license information, please view the LICENSE
108817535bSAndreas Gohr * file that was distributed with this source code.
118817535bSAndreas Gohr */
128817535bSAndreas Gohr
138817535bSAndreas Gohrnamespace Composer\Autoload;
148817535bSAndreas Gohr
158817535bSAndreas Gohr/**
168817535bSAndreas Gohr * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
178817535bSAndreas Gohr *
188817535bSAndreas Gohr *     $loader = new \Composer\Autoload\ClassLoader();
198817535bSAndreas Gohr *
208817535bSAndreas Gohr *     // register classes with namespaces
218817535bSAndreas Gohr *     $loader->add('Symfony\Component', __DIR__.'/component');
228817535bSAndreas Gohr *     $loader->add('Symfony',           __DIR__.'/framework');
238817535bSAndreas Gohr *
248817535bSAndreas Gohr *     // activate the autoloader
258817535bSAndreas Gohr *     $loader->register();
268817535bSAndreas Gohr *
278817535bSAndreas Gohr *     // to enable searching the include path (eg. for PEAR packages)
288817535bSAndreas Gohr *     $loader->setUseIncludePath(true);
298817535bSAndreas Gohr *
308817535bSAndreas Gohr * In this example, if you try to use a class in the Symfony\Component
318817535bSAndreas Gohr * namespace or one of its children (Symfony\Component\Console for instance),
328817535bSAndreas Gohr * the autoloader will first look for the class under the component/
338817535bSAndreas Gohr * directory, and it will then fallback to the framework/ directory if not
348817535bSAndreas Gohr * found before giving up.
358817535bSAndreas Gohr *
368817535bSAndreas Gohr * This class is loosely based on the Symfony UniversalClassLoader.
378817535bSAndreas Gohr *
388817535bSAndreas Gohr * @author Fabien Potencier <fabien@symfony.com>
398817535bSAndreas Gohr * @author Jordi Boggiano <j.boggiano@seld.be>
408817535bSAndreas Gohr * @see    https://www.php-fig.org/psr/psr-0/
418817535bSAndreas Gohr * @see    https://www.php-fig.org/psr/psr-4/
428817535bSAndreas Gohr */
438817535bSAndreas Gohrclass ClassLoader
448817535bSAndreas Gohr{
458817535bSAndreas Gohr    /** @var \Closure(string):void */
468817535bSAndreas Gohr    private static $includeFile;
478817535bSAndreas Gohr
48*3379af09SAndreas Gohr    /** @var string|null */
498817535bSAndreas Gohr    private $vendorDir;
508817535bSAndreas Gohr
518817535bSAndreas Gohr    // PSR-4
528817535bSAndreas Gohr    /**
53*3379af09SAndreas Gohr     * @var array<string, array<string, int>>
548817535bSAndreas Gohr     */
558817535bSAndreas Gohr    private $prefixLengthsPsr4 = array();
568817535bSAndreas Gohr    /**
57*3379af09SAndreas Gohr     * @var array<string, list<string>>
588817535bSAndreas Gohr     */
598817535bSAndreas Gohr    private $prefixDirsPsr4 = array();
608817535bSAndreas Gohr    /**
61*3379af09SAndreas Gohr     * @var list<string>
628817535bSAndreas Gohr     */
638817535bSAndreas Gohr    private $fallbackDirsPsr4 = array();
648817535bSAndreas Gohr
658817535bSAndreas Gohr    // PSR-0
668817535bSAndreas Gohr    /**
67*3379af09SAndreas Gohr     * List of PSR-0 prefixes
68*3379af09SAndreas Gohr     *
69*3379af09SAndreas Gohr     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
70*3379af09SAndreas Gohr     *
71*3379af09SAndreas Gohr     * @var array<string, array<string, list<string>>>
728817535bSAndreas Gohr     */
738817535bSAndreas Gohr    private $prefixesPsr0 = array();
748817535bSAndreas Gohr    /**
75*3379af09SAndreas Gohr     * @var list<string>
768817535bSAndreas Gohr     */
778817535bSAndreas Gohr    private $fallbackDirsPsr0 = array();
788817535bSAndreas Gohr
798817535bSAndreas Gohr    /** @var bool */
808817535bSAndreas Gohr    private $useIncludePath = false;
818817535bSAndreas Gohr
828817535bSAndreas Gohr    /**
83*3379af09SAndreas Gohr     * @var array<string, string>
848817535bSAndreas Gohr     */
858817535bSAndreas Gohr    private $classMap = array();
868817535bSAndreas Gohr
878817535bSAndreas Gohr    /** @var bool */
888817535bSAndreas Gohr    private $classMapAuthoritative = false;
898817535bSAndreas Gohr
908817535bSAndreas Gohr    /**
91*3379af09SAndreas Gohr     * @var array<string, bool>
928817535bSAndreas Gohr     */
938817535bSAndreas Gohr    private $missingClasses = array();
948817535bSAndreas Gohr
95*3379af09SAndreas Gohr    /** @var string|null */
968817535bSAndreas Gohr    private $apcuPrefix;
978817535bSAndreas Gohr
988817535bSAndreas Gohr    /**
99*3379af09SAndreas Gohr     * @var array<string, self>
1008817535bSAndreas Gohr     */
1018817535bSAndreas Gohr    private static $registeredLoaders = array();
1028817535bSAndreas Gohr
1038817535bSAndreas Gohr    /**
104*3379af09SAndreas Gohr     * @param string|null $vendorDir
1058817535bSAndreas Gohr     */
1068817535bSAndreas Gohr    public function __construct($vendorDir = null)
1078817535bSAndreas Gohr    {
1088817535bSAndreas Gohr        $this->vendorDir = $vendorDir;
1098817535bSAndreas Gohr        self::initializeIncludeClosure();
1108817535bSAndreas Gohr    }
1118817535bSAndreas Gohr
1128817535bSAndreas Gohr    /**
113*3379af09SAndreas Gohr     * @return array<string, list<string>>
1148817535bSAndreas Gohr     */
1158817535bSAndreas Gohr    public function getPrefixes()
1168817535bSAndreas Gohr    {
1178817535bSAndreas Gohr        if (!empty($this->prefixesPsr0)) {
1188817535bSAndreas Gohr            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
1198817535bSAndreas Gohr        }
1208817535bSAndreas Gohr
1218817535bSAndreas Gohr        return array();
1228817535bSAndreas Gohr    }
1238817535bSAndreas Gohr
1248817535bSAndreas Gohr    /**
125*3379af09SAndreas Gohr     * @return array<string, list<string>>
1268817535bSAndreas Gohr     */
1278817535bSAndreas Gohr    public function getPrefixesPsr4()
1288817535bSAndreas Gohr    {
1298817535bSAndreas Gohr        return $this->prefixDirsPsr4;
1308817535bSAndreas Gohr    }
1318817535bSAndreas Gohr
1328817535bSAndreas Gohr    /**
133*3379af09SAndreas Gohr     * @return list<string>
1348817535bSAndreas Gohr     */
1358817535bSAndreas Gohr    public function getFallbackDirs()
1368817535bSAndreas Gohr    {
1378817535bSAndreas Gohr        return $this->fallbackDirsPsr0;
1388817535bSAndreas Gohr    }
1398817535bSAndreas Gohr
1408817535bSAndreas Gohr    /**
141*3379af09SAndreas Gohr     * @return list<string>
1428817535bSAndreas Gohr     */
1438817535bSAndreas Gohr    public function getFallbackDirsPsr4()
1448817535bSAndreas Gohr    {
1458817535bSAndreas Gohr        return $this->fallbackDirsPsr4;
1468817535bSAndreas Gohr    }
1478817535bSAndreas Gohr
1488817535bSAndreas Gohr    /**
149*3379af09SAndreas Gohr     * @return array<string, string> Array of classname => path
1508817535bSAndreas Gohr     */
1518817535bSAndreas Gohr    public function getClassMap()
1528817535bSAndreas Gohr    {
1538817535bSAndreas Gohr        return $this->classMap;
1548817535bSAndreas Gohr    }
1558817535bSAndreas Gohr
1568817535bSAndreas Gohr    /**
157*3379af09SAndreas Gohr     * @param array<string, string> $classMap Class to filename map
1588817535bSAndreas Gohr     *
1598817535bSAndreas Gohr     * @return void
1608817535bSAndreas Gohr     */
1618817535bSAndreas Gohr    public function addClassMap(array $classMap)
1628817535bSAndreas Gohr    {
1638817535bSAndreas Gohr        if ($this->classMap) {
1648817535bSAndreas Gohr            $this->classMap = array_merge($this->classMap, $classMap);
1658817535bSAndreas Gohr        } else {
1668817535bSAndreas Gohr            $this->classMap = $classMap;
1678817535bSAndreas Gohr        }
1688817535bSAndreas Gohr    }
1698817535bSAndreas Gohr
1708817535bSAndreas Gohr    /**
1718817535bSAndreas Gohr     * Registers a set of PSR-0 directories for a given prefix, either
1728817535bSAndreas Gohr     * appending or prepending to the ones previously set for this prefix.
1738817535bSAndreas Gohr     *
1748817535bSAndreas Gohr     * @param string              $prefix  The prefix
175*3379af09SAndreas Gohr     * @param list<string>|string $paths   The PSR-0 root directories
1768817535bSAndreas Gohr     * @param bool                $prepend Whether to prepend the directories
1778817535bSAndreas Gohr     *
1788817535bSAndreas Gohr     * @return void
1798817535bSAndreas Gohr     */
1808817535bSAndreas Gohr    public function add($prefix, $paths, $prepend = false)
1818817535bSAndreas Gohr    {
182*3379af09SAndreas Gohr        $paths = (array) $paths;
1838817535bSAndreas Gohr        if (!$prefix) {
1848817535bSAndreas Gohr            if ($prepend) {
1858817535bSAndreas Gohr                $this->fallbackDirsPsr0 = array_merge(
186*3379af09SAndreas Gohr                    $paths,
1878817535bSAndreas Gohr                    $this->fallbackDirsPsr0
1888817535bSAndreas Gohr                );
1898817535bSAndreas Gohr            } else {
1908817535bSAndreas Gohr                $this->fallbackDirsPsr0 = array_merge(
1918817535bSAndreas Gohr                    $this->fallbackDirsPsr0,
192*3379af09SAndreas Gohr                    $paths
1938817535bSAndreas Gohr                );
1948817535bSAndreas Gohr            }
1958817535bSAndreas Gohr
1968817535bSAndreas Gohr            return;
1978817535bSAndreas Gohr        }
1988817535bSAndreas Gohr
1998817535bSAndreas Gohr        $first = $prefix[0];
2008817535bSAndreas Gohr        if (!isset($this->prefixesPsr0[$first][$prefix])) {
201*3379af09SAndreas Gohr            $this->prefixesPsr0[$first][$prefix] = $paths;
2028817535bSAndreas Gohr
2038817535bSAndreas Gohr            return;
2048817535bSAndreas Gohr        }
2058817535bSAndreas Gohr        if ($prepend) {
2068817535bSAndreas Gohr            $this->prefixesPsr0[$first][$prefix] = array_merge(
207*3379af09SAndreas Gohr                $paths,
2088817535bSAndreas Gohr                $this->prefixesPsr0[$first][$prefix]
2098817535bSAndreas Gohr            );
2108817535bSAndreas Gohr        } else {
2118817535bSAndreas Gohr            $this->prefixesPsr0[$first][$prefix] = array_merge(
2128817535bSAndreas Gohr                $this->prefixesPsr0[$first][$prefix],
213*3379af09SAndreas Gohr                $paths
2148817535bSAndreas Gohr            );
2158817535bSAndreas Gohr        }
2168817535bSAndreas Gohr    }
2178817535bSAndreas Gohr
2188817535bSAndreas Gohr    /**
2198817535bSAndreas Gohr     * Registers a set of PSR-4 directories for a given namespace, either
2208817535bSAndreas Gohr     * appending or prepending to the ones previously set for this namespace.
2218817535bSAndreas Gohr     *
2228817535bSAndreas Gohr     * @param string              $prefix  The prefix/namespace, with trailing '\\'
223*3379af09SAndreas Gohr     * @param list<string>|string $paths   The PSR-4 base directories
2248817535bSAndreas Gohr     * @param bool                $prepend Whether to prepend the directories
2258817535bSAndreas Gohr     *
2268817535bSAndreas Gohr     * @throws \InvalidArgumentException
2278817535bSAndreas Gohr     *
2288817535bSAndreas Gohr     * @return void
2298817535bSAndreas Gohr     */
2308817535bSAndreas Gohr    public function addPsr4($prefix, $paths, $prepend = false)
2318817535bSAndreas Gohr    {
232*3379af09SAndreas Gohr        $paths = (array) $paths;
2338817535bSAndreas Gohr        if (!$prefix) {
2348817535bSAndreas Gohr            // Register directories for the root namespace.
2358817535bSAndreas Gohr            if ($prepend) {
2368817535bSAndreas Gohr                $this->fallbackDirsPsr4 = array_merge(
237*3379af09SAndreas Gohr                    $paths,
2388817535bSAndreas Gohr                    $this->fallbackDirsPsr4
2398817535bSAndreas Gohr                );
2408817535bSAndreas Gohr            } else {
2418817535bSAndreas Gohr                $this->fallbackDirsPsr4 = array_merge(
2428817535bSAndreas Gohr                    $this->fallbackDirsPsr4,
243*3379af09SAndreas Gohr                    $paths
2448817535bSAndreas Gohr                );
2458817535bSAndreas Gohr            }
2468817535bSAndreas Gohr        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
2478817535bSAndreas Gohr            // Register directories for a new namespace.
2488817535bSAndreas Gohr            $length = strlen($prefix);
2498817535bSAndreas Gohr            if ('\\' !== $prefix[$length - 1]) {
2508817535bSAndreas Gohr                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
2518817535bSAndreas Gohr            }
2528817535bSAndreas Gohr            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
253*3379af09SAndreas Gohr            $this->prefixDirsPsr4[$prefix] = $paths;
2548817535bSAndreas Gohr        } elseif ($prepend) {
2558817535bSAndreas Gohr            // Prepend directories for an already registered namespace.
2568817535bSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = array_merge(
257*3379af09SAndreas Gohr                $paths,
2588817535bSAndreas Gohr                $this->prefixDirsPsr4[$prefix]
2598817535bSAndreas Gohr            );
2608817535bSAndreas Gohr        } else {
2618817535bSAndreas Gohr            // Append directories for an already registered namespace.
2628817535bSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = array_merge(
2638817535bSAndreas Gohr                $this->prefixDirsPsr4[$prefix],
264*3379af09SAndreas Gohr                $paths
2658817535bSAndreas Gohr            );
2668817535bSAndreas Gohr        }
2678817535bSAndreas Gohr    }
2688817535bSAndreas Gohr
2698817535bSAndreas Gohr    /**
2708817535bSAndreas Gohr     * Registers a set of PSR-0 directories for a given prefix,
2718817535bSAndreas Gohr     * replacing any others previously set for this prefix.
2728817535bSAndreas Gohr     *
2738817535bSAndreas Gohr     * @param string              $prefix The prefix
274*3379af09SAndreas Gohr     * @param list<string>|string $paths  The PSR-0 base directories
2758817535bSAndreas Gohr     *
2768817535bSAndreas Gohr     * @return void
2778817535bSAndreas Gohr     */
2788817535bSAndreas Gohr    public function set($prefix, $paths)
2798817535bSAndreas Gohr    {
2808817535bSAndreas Gohr        if (!$prefix) {
2818817535bSAndreas Gohr            $this->fallbackDirsPsr0 = (array) $paths;
2828817535bSAndreas Gohr        } else {
2838817535bSAndreas Gohr            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
2848817535bSAndreas Gohr        }
2858817535bSAndreas Gohr    }
2868817535bSAndreas Gohr
2878817535bSAndreas Gohr    /**
2888817535bSAndreas Gohr     * Registers a set of PSR-4 directories for a given namespace,
2898817535bSAndreas Gohr     * replacing any others previously set for this namespace.
2908817535bSAndreas Gohr     *
2918817535bSAndreas Gohr     * @param string              $prefix The prefix/namespace, with trailing '\\'
292*3379af09SAndreas Gohr     * @param list<string>|string $paths  The PSR-4 base directories
2938817535bSAndreas Gohr     *
2948817535bSAndreas Gohr     * @throws \InvalidArgumentException
2958817535bSAndreas Gohr     *
2968817535bSAndreas Gohr     * @return void
2978817535bSAndreas Gohr     */
2988817535bSAndreas Gohr    public function setPsr4($prefix, $paths)
2998817535bSAndreas Gohr    {
3008817535bSAndreas Gohr        if (!$prefix) {
3018817535bSAndreas Gohr            $this->fallbackDirsPsr4 = (array) $paths;
3028817535bSAndreas Gohr        } else {
3038817535bSAndreas Gohr            $length = strlen($prefix);
3048817535bSAndreas Gohr            if ('\\' !== $prefix[$length - 1]) {
3058817535bSAndreas Gohr                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
3068817535bSAndreas Gohr            }
3078817535bSAndreas Gohr            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
3088817535bSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = (array) $paths;
3098817535bSAndreas Gohr        }
3108817535bSAndreas Gohr    }
3118817535bSAndreas Gohr
3128817535bSAndreas Gohr    /**
3138817535bSAndreas Gohr     * Turns on searching the include path for class files.
3148817535bSAndreas Gohr     *
3158817535bSAndreas Gohr     * @param bool $useIncludePath
3168817535bSAndreas Gohr     *
3178817535bSAndreas Gohr     * @return void
3188817535bSAndreas Gohr     */
3198817535bSAndreas Gohr    public function setUseIncludePath($useIncludePath)
3208817535bSAndreas Gohr    {
3218817535bSAndreas Gohr        $this->useIncludePath = $useIncludePath;
3228817535bSAndreas Gohr    }
3238817535bSAndreas Gohr
3248817535bSAndreas Gohr    /**
3258817535bSAndreas Gohr     * Can be used to check if the autoloader uses the include path to check
3268817535bSAndreas Gohr     * for classes.
3278817535bSAndreas Gohr     *
3288817535bSAndreas Gohr     * @return bool
3298817535bSAndreas Gohr     */
3308817535bSAndreas Gohr    public function getUseIncludePath()
3318817535bSAndreas Gohr    {
3328817535bSAndreas Gohr        return $this->useIncludePath;
3338817535bSAndreas Gohr    }
3348817535bSAndreas Gohr
3358817535bSAndreas Gohr    /**
3368817535bSAndreas Gohr     * Turns off searching the prefix and fallback directories for classes
3378817535bSAndreas Gohr     * that have not been registered with the class map.
3388817535bSAndreas Gohr     *
3398817535bSAndreas Gohr     * @param bool $classMapAuthoritative
3408817535bSAndreas Gohr     *
3418817535bSAndreas Gohr     * @return void
3428817535bSAndreas Gohr     */
3438817535bSAndreas Gohr    public function setClassMapAuthoritative($classMapAuthoritative)
3448817535bSAndreas Gohr    {
3458817535bSAndreas Gohr        $this->classMapAuthoritative = $classMapAuthoritative;
3468817535bSAndreas Gohr    }
3478817535bSAndreas Gohr
3488817535bSAndreas Gohr    /**
3498817535bSAndreas Gohr     * Should class lookup fail if not found in the current class map?
3508817535bSAndreas Gohr     *
3518817535bSAndreas Gohr     * @return bool
3528817535bSAndreas Gohr     */
3538817535bSAndreas Gohr    public function isClassMapAuthoritative()
3548817535bSAndreas Gohr    {
3558817535bSAndreas Gohr        return $this->classMapAuthoritative;
3568817535bSAndreas Gohr    }
3578817535bSAndreas Gohr
3588817535bSAndreas Gohr    /**
3598817535bSAndreas Gohr     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
3608817535bSAndreas Gohr     *
3618817535bSAndreas Gohr     * @param string|null $apcuPrefix
3628817535bSAndreas Gohr     *
3638817535bSAndreas Gohr     * @return void
3648817535bSAndreas Gohr     */
3658817535bSAndreas Gohr    public function setApcuPrefix($apcuPrefix)
3668817535bSAndreas Gohr    {
3678817535bSAndreas Gohr        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
3688817535bSAndreas Gohr    }
3698817535bSAndreas Gohr
3708817535bSAndreas Gohr    /**
3718817535bSAndreas Gohr     * The APCu prefix in use, or null if APCu caching is not enabled.
3728817535bSAndreas Gohr     *
3738817535bSAndreas Gohr     * @return string|null
3748817535bSAndreas Gohr     */
3758817535bSAndreas Gohr    public function getApcuPrefix()
3768817535bSAndreas Gohr    {
3778817535bSAndreas Gohr        return $this->apcuPrefix;
3788817535bSAndreas Gohr    }
3798817535bSAndreas Gohr
3808817535bSAndreas Gohr    /**
3818817535bSAndreas Gohr     * Registers this instance as an autoloader.
3828817535bSAndreas Gohr     *
3838817535bSAndreas Gohr     * @param bool $prepend Whether to prepend the autoloader or not
3848817535bSAndreas Gohr     *
3858817535bSAndreas Gohr     * @return void
3868817535bSAndreas Gohr     */
3878817535bSAndreas Gohr    public function register($prepend = false)
3888817535bSAndreas Gohr    {
3898817535bSAndreas Gohr        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
3908817535bSAndreas Gohr
3918817535bSAndreas Gohr        if (null === $this->vendorDir) {
3928817535bSAndreas Gohr            return;
3938817535bSAndreas Gohr        }
3948817535bSAndreas Gohr
3958817535bSAndreas Gohr        if ($prepend) {
3968817535bSAndreas Gohr            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
3978817535bSAndreas Gohr        } else {
3988817535bSAndreas Gohr            unset(self::$registeredLoaders[$this->vendorDir]);
3998817535bSAndreas Gohr            self::$registeredLoaders[$this->vendorDir] = $this;
4008817535bSAndreas Gohr        }
4018817535bSAndreas Gohr    }
4028817535bSAndreas Gohr
4038817535bSAndreas Gohr    /**
4048817535bSAndreas Gohr     * Unregisters this instance as an autoloader.
4058817535bSAndreas Gohr     *
4068817535bSAndreas Gohr     * @return void
4078817535bSAndreas Gohr     */
4088817535bSAndreas Gohr    public function unregister()
4098817535bSAndreas Gohr    {
4108817535bSAndreas Gohr        spl_autoload_unregister(array($this, 'loadClass'));
4118817535bSAndreas Gohr
4128817535bSAndreas Gohr        if (null !== $this->vendorDir) {
4138817535bSAndreas Gohr            unset(self::$registeredLoaders[$this->vendorDir]);
4148817535bSAndreas Gohr        }
4158817535bSAndreas Gohr    }
4168817535bSAndreas Gohr
4178817535bSAndreas Gohr    /**
4188817535bSAndreas Gohr     * Loads the given class or interface.
4198817535bSAndreas Gohr     *
4208817535bSAndreas Gohr     * @param  string    $class The name of the class
4218817535bSAndreas Gohr     * @return true|null True if loaded, null otherwise
4228817535bSAndreas Gohr     */
4238817535bSAndreas Gohr    public function loadClass($class)
4248817535bSAndreas Gohr    {
4258817535bSAndreas Gohr        if ($file = $this->findFile($class)) {
4268817535bSAndreas Gohr            $includeFile = self::$includeFile;
4278817535bSAndreas Gohr            $includeFile($file);
4288817535bSAndreas Gohr
4298817535bSAndreas Gohr            return true;
4308817535bSAndreas Gohr        }
4318817535bSAndreas Gohr
4328817535bSAndreas Gohr        return null;
4338817535bSAndreas Gohr    }
4348817535bSAndreas Gohr
4358817535bSAndreas Gohr    /**
4368817535bSAndreas Gohr     * Finds the path to the file where the class is defined.
4378817535bSAndreas Gohr     *
4388817535bSAndreas Gohr     * @param string $class The name of the class
4398817535bSAndreas Gohr     *
4408817535bSAndreas Gohr     * @return string|false The path if found, false otherwise
4418817535bSAndreas Gohr     */
4428817535bSAndreas Gohr    public function findFile($class)
4438817535bSAndreas Gohr    {
4448817535bSAndreas Gohr        // class map lookup
4458817535bSAndreas Gohr        if (isset($this->classMap[$class])) {
4468817535bSAndreas Gohr            return $this->classMap[$class];
4478817535bSAndreas Gohr        }
4488817535bSAndreas Gohr        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
4498817535bSAndreas Gohr            return false;
4508817535bSAndreas Gohr        }
4518817535bSAndreas Gohr        if (null !== $this->apcuPrefix) {
4528817535bSAndreas Gohr            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
4538817535bSAndreas Gohr            if ($hit) {
4548817535bSAndreas Gohr                return $file;
4558817535bSAndreas Gohr            }
4568817535bSAndreas Gohr        }
4578817535bSAndreas Gohr
4588817535bSAndreas Gohr        $file = $this->findFileWithExtension($class, '.php');
4598817535bSAndreas Gohr
4608817535bSAndreas Gohr        // Search for Hack files if we are running on HHVM
4618817535bSAndreas Gohr        if (false === $file && defined('HHVM_VERSION')) {
4628817535bSAndreas Gohr            $file = $this->findFileWithExtension($class, '.hh');
4638817535bSAndreas Gohr        }
4648817535bSAndreas Gohr
4658817535bSAndreas Gohr        if (null !== $this->apcuPrefix) {
4668817535bSAndreas Gohr            apcu_add($this->apcuPrefix.$class, $file);
4678817535bSAndreas Gohr        }
4688817535bSAndreas Gohr
4698817535bSAndreas Gohr        if (false === $file) {
4708817535bSAndreas Gohr            // Remember that this class does not exist.
4718817535bSAndreas Gohr            $this->missingClasses[$class] = true;
4728817535bSAndreas Gohr        }
4738817535bSAndreas Gohr
4748817535bSAndreas Gohr        return $file;
4758817535bSAndreas Gohr    }
4768817535bSAndreas Gohr
4778817535bSAndreas Gohr    /**
478*3379af09SAndreas Gohr     * Returns the currently registered loaders keyed by their corresponding vendor directories.
4798817535bSAndreas Gohr     *
480*3379af09SAndreas Gohr     * @return array<string, self>
4818817535bSAndreas Gohr     */
4828817535bSAndreas Gohr    public static function getRegisteredLoaders()
4838817535bSAndreas Gohr    {
4848817535bSAndreas Gohr        return self::$registeredLoaders;
4858817535bSAndreas Gohr    }
4868817535bSAndreas Gohr
4878817535bSAndreas Gohr    /**
4888817535bSAndreas Gohr     * @param  string       $class
4898817535bSAndreas Gohr     * @param  string       $ext
4908817535bSAndreas Gohr     * @return string|false
4918817535bSAndreas Gohr     */
4928817535bSAndreas Gohr    private function findFileWithExtension($class, $ext)
4938817535bSAndreas Gohr    {
4948817535bSAndreas Gohr        // PSR-4 lookup
4958817535bSAndreas Gohr        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
4968817535bSAndreas Gohr
4978817535bSAndreas Gohr        $first = $class[0];
4988817535bSAndreas Gohr        if (isset($this->prefixLengthsPsr4[$first])) {
4998817535bSAndreas Gohr            $subPath = $class;
5008817535bSAndreas Gohr            while (false !== $lastPos = strrpos($subPath, '\\')) {
5018817535bSAndreas Gohr                $subPath = substr($subPath, 0, $lastPos);
5028817535bSAndreas Gohr                $search = $subPath . '\\';
5038817535bSAndreas Gohr                if (isset($this->prefixDirsPsr4[$search])) {
5048817535bSAndreas Gohr                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
5058817535bSAndreas Gohr                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
5068817535bSAndreas Gohr                        if (file_exists($file = $dir . $pathEnd)) {
5078817535bSAndreas Gohr                            return $file;
5088817535bSAndreas Gohr                        }
5098817535bSAndreas Gohr                    }
5108817535bSAndreas Gohr                }
5118817535bSAndreas Gohr            }
5128817535bSAndreas Gohr        }
5138817535bSAndreas Gohr
5148817535bSAndreas Gohr        // PSR-4 fallback dirs
5158817535bSAndreas Gohr        foreach ($this->fallbackDirsPsr4 as $dir) {
5168817535bSAndreas Gohr            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
5178817535bSAndreas Gohr                return $file;
5188817535bSAndreas Gohr            }
5198817535bSAndreas Gohr        }
5208817535bSAndreas Gohr
5218817535bSAndreas Gohr        // PSR-0 lookup
5228817535bSAndreas Gohr        if (false !== $pos = strrpos($class, '\\')) {
5238817535bSAndreas Gohr            // namespaced class name
5248817535bSAndreas Gohr            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
5258817535bSAndreas Gohr                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
5268817535bSAndreas Gohr        } else {
5278817535bSAndreas Gohr            // PEAR-like class name
5288817535bSAndreas Gohr            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
5298817535bSAndreas Gohr        }
5308817535bSAndreas Gohr
5318817535bSAndreas Gohr        if (isset($this->prefixesPsr0[$first])) {
5328817535bSAndreas Gohr            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
5338817535bSAndreas Gohr                if (0 === strpos($class, $prefix)) {
5348817535bSAndreas Gohr                    foreach ($dirs as $dir) {
5358817535bSAndreas Gohr                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
5368817535bSAndreas Gohr                            return $file;
5378817535bSAndreas Gohr                        }
5388817535bSAndreas Gohr                    }
5398817535bSAndreas Gohr                }
5408817535bSAndreas Gohr            }
5418817535bSAndreas Gohr        }
5428817535bSAndreas Gohr
5438817535bSAndreas Gohr        // PSR-0 fallback dirs
5448817535bSAndreas Gohr        foreach ($this->fallbackDirsPsr0 as $dir) {
5458817535bSAndreas Gohr            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
5468817535bSAndreas Gohr                return $file;
5478817535bSAndreas Gohr            }
5488817535bSAndreas Gohr        }
5498817535bSAndreas Gohr
5508817535bSAndreas Gohr        // PSR-0 include paths.
5518817535bSAndreas Gohr        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
5528817535bSAndreas Gohr            return $file;
5538817535bSAndreas Gohr        }
5548817535bSAndreas Gohr
5558817535bSAndreas Gohr        return false;
5568817535bSAndreas Gohr    }
5578817535bSAndreas Gohr
5588817535bSAndreas Gohr    /**
5598817535bSAndreas Gohr     * @return void
5608817535bSAndreas Gohr     */
5618817535bSAndreas Gohr    private static function initializeIncludeClosure()
5628817535bSAndreas Gohr    {
5638817535bSAndreas Gohr        if (self::$includeFile !== null) {
5648817535bSAndreas Gohr            return;
5658817535bSAndreas Gohr        }
5668817535bSAndreas Gohr
5678817535bSAndreas Gohr        /**
5688817535bSAndreas Gohr         * Scope isolated include.
5698817535bSAndreas Gohr         *
5708817535bSAndreas Gohr         * Prevents access to $this/self from included files.
5718817535bSAndreas Gohr         *
5728817535bSAndreas Gohr         * @param  string $file
5738817535bSAndreas Gohr         * @return void
5748817535bSAndreas Gohr         */
5758817535bSAndreas Gohr        self::$includeFile = \Closure::bind(static function($file) {
5768817535bSAndreas Gohr            include $file;
5778817535bSAndreas Gohr        }, null, null);
5788817535bSAndreas Gohr    }
5798817535bSAndreas Gohr}
580