xref: /plugin/pureldap/vendor/composer/ClassLoader.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
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