xref: /dokuwiki/vendor/composer/ClassLoader.php (revision 28e9760a91ee147e8ce86c6d9f964e4d04b0acaf)
1605f8e8dSAndreas Gohr<?php
2605f8e8dSAndreas Gohr
3605f8e8dSAndreas Gohr/*
4605f8e8dSAndreas Gohr * This file is part of Composer.
5605f8e8dSAndreas Gohr *
6605f8e8dSAndreas Gohr * (c) Nils Adermann <naderman@naderman.de>
7605f8e8dSAndreas Gohr *     Jordi Boggiano <j.boggiano@seld.be>
8605f8e8dSAndreas Gohr *
9605f8e8dSAndreas Gohr * For the full copyright and license information, please view the LICENSE
10605f8e8dSAndreas Gohr * file that was distributed with this source code.
11605f8e8dSAndreas Gohr */
12605f8e8dSAndreas Gohr
13605f8e8dSAndreas Gohrnamespace Composer\Autoload;
14605f8e8dSAndreas Gohr
15605f8e8dSAndreas Gohr/**
167a33d2f8SNiklas Keller * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17605f8e8dSAndreas Gohr *
18605f8e8dSAndreas Gohr *     $loader = new \Composer\Autoload\ClassLoader();
19605f8e8dSAndreas Gohr *
20605f8e8dSAndreas Gohr *     // register classes with namespaces
21605f8e8dSAndreas Gohr *     $loader->add('Symfony\Component', __DIR__.'/component');
22605f8e8dSAndreas Gohr *     $loader->add('Symfony',           __DIR__.'/framework');
23605f8e8dSAndreas Gohr *
24605f8e8dSAndreas Gohr *     // activate the autoloader
25605f8e8dSAndreas Gohr *     $loader->register();
26605f8e8dSAndreas Gohr *
27605f8e8dSAndreas Gohr *     // to enable searching the include path (eg. for PEAR packages)
28605f8e8dSAndreas Gohr *     $loader->setUseIncludePath(true);
29605f8e8dSAndreas Gohr *
30605f8e8dSAndreas Gohr * In this example, if you try to use a class in the Symfony\Component
31605f8e8dSAndreas Gohr * namespace or one of its children (Symfony\Component\Console for instance),
32605f8e8dSAndreas Gohr * the autoloader will first look for the class under the component/
33605f8e8dSAndreas Gohr * directory, and it will then fallback to the framework/ directory if not
34605f8e8dSAndreas Gohr * found before giving up.
35605f8e8dSAndreas Gohr *
36605f8e8dSAndreas Gohr * This class is loosely based on the Symfony UniversalClassLoader.
37605f8e8dSAndreas Gohr *
38605f8e8dSAndreas Gohr * @author Fabien Potencier <fabien@symfony.com>
39605f8e8dSAndreas Gohr * @author Jordi Boggiano <j.boggiano@seld.be>
406cb05674SAndreas Gohr * @see    https://www.php-fig.org/psr/psr-0/
416cb05674SAndreas Gohr * @see    https://www.php-fig.org/psr/psr-4/
42605f8e8dSAndreas Gohr */
43605f8e8dSAndreas Gohrclass ClassLoader
44605f8e8dSAndreas Gohr{
45*28e9760aSAndreas Gohr    /** @var \Closure(string):void */
46*28e9760aSAndreas Gohr    private static $includeFile;
47*28e9760aSAndreas Gohr
48d3233986SAndreas Gohr    /** @var ?string */
496cb05674SAndreas Gohr    private $vendorDir;
506cb05674SAndreas Gohr
51605f8e8dSAndreas Gohr    // PSR-4
52d3233986SAndreas Gohr    /**
53d3233986SAndreas Gohr     * @var array[]
54d3233986SAndreas Gohr     * @psalm-var array<string, array<string, int>>
55d3233986SAndreas Gohr     */
56605f8e8dSAndreas Gohr    private $prefixLengthsPsr4 = array();
57d3233986SAndreas Gohr    /**
58d3233986SAndreas Gohr     * @var array[]
59d3233986SAndreas Gohr     * @psalm-var array<string, array<int, string>>
60d3233986SAndreas Gohr     */
61605f8e8dSAndreas Gohr    private $prefixDirsPsr4 = array();
62d3233986SAndreas Gohr    /**
63d3233986SAndreas Gohr     * @var array[]
64d3233986SAndreas Gohr     * @psalm-var array<string, string>
65d3233986SAndreas Gohr     */
66605f8e8dSAndreas Gohr    private $fallbackDirsPsr4 = array();
67605f8e8dSAndreas Gohr
68605f8e8dSAndreas Gohr    // PSR-0
69d3233986SAndreas Gohr    /**
70d3233986SAndreas Gohr     * @var array[]
71d3233986SAndreas Gohr     * @psalm-var array<string, array<string, string[]>>
72d3233986SAndreas Gohr     */
73605f8e8dSAndreas Gohr    private $prefixesPsr0 = array();
74d3233986SAndreas Gohr    /**
75d3233986SAndreas Gohr     * @var array[]
76d3233986SAndreas Gohr     * @psalm-var array<string, string>
77d3233986SAndreas Gohr     */
78605f8e8dSAndreas Gohr    private $fallbackDirsPsr0 = array();
79605f8e8dSAndreas Gohr
80d3233986SAndreas Gohr    /** @var bool */
81605f8e8dSAndreas Gohr    private $useIncludePath = false;
82d3233986SAndreas Gohr
83d3233986SAndreas Gohr    /**
84d3233986SAndreas Gohr     * @var string[]
85d3233986SAndreas Gohr     * @psalm-var array<string, string>
86d3233986SAndreas Gohr     */
87605f8e8dSAndreas Gohr    private $classMap = array();
88d3233986SAndreas Gohr
89d3233986SAndreas Gohr    /** @var bool */
90605f8e8dSAndreas Gohr    private $classMapAuthoritative = false;
91d3233986SAndreas Gohr
92d3233986SAndreas Gohr    /**
93d3233986SAndreas Gohr     * @var bool[]
94d3233986SAndreas Gohr     * @psalm-var array<string, bool>
95d3233986SAndreas Gohr     */
967a33d2f8SNiklas Keller    private $missingClasses = array();
97d3233986SAndreas Gohr
98d3233986SAndreas Gohr    /** @var ?string */
99e0dd796dSAndreas Gohr    private $apcuPrefix;
100605f8e8dSAndreas Gohr
101d3233986SAndreas Gohr    /**
102d3233986SAndreas Gohr     * @var self[]
103d3233986SAndreas Gohr     */
1046cb05674SAndreas Gohr    private static $registeredLoaders = array();
1056cb05674SAndreas Gohr
106d3233986SAndreas Gohr    /**
107d3233986SAndreas Gohr     * @param ?string $vendorDir
108d3233986SAndreas Gohr     */
1096cb05674SAndreas Gohr    public function __construct($vendorDir = null)
1106cb05674SAndreas Gohr    {
1116cb05674SAndreas Gohr        $this->vendorDir = $vendorDir;
112*28e9760aSAndreas Gohr        self::initializeIncludeClosure();
1136cb05674SAndreas Gohr    }
1146cb05674SAndreas Gohr
115d3233986SAndreas Gohr    /**
116d3233986SAndreas Gohr     * @return string[]
117d3233986SAndreas Gohr     */
118605f8e8dSAndreas Gohr    public function getPrefixes()
119605f8e8dSAndreas Gohr    {
120605f8e8dSAndreas Gohr        if (!empty($this->prefixesPsr0)) {
121a3bfbb3cSAndreas Gohr            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
122605f8e8dSAndreas Gohr        }
123605f8e8dSAndreas Gohr
124605f8e8dSAndreas Gohr        return array();
125605f8e8dSAndreas Gohr    }
126605f8e8dSAndreas Gohr
127d3233986SAndreas Gohr    /**
128d3233986SAndreas Gohr     * @return array[]
129d3233986SAndreas Gohr     * @psalm-return array<string, array<int, string>>
130d3233986SAndreas Gohr     */
131605f8e8dSAndreas Gohr    public function getPrefixesPsr4()
132605f8e8dSAndreas Gohr    {
133605f8e8dSAndreas Gohr        return $this->prefixDirsPsr4;
134605f8e8dSAndreas Gohr    }
135605f8e8dSAndreas Gohr
136d3233986SAndreas Gohr    /**
137d3233986SAndreas Gohr     * @return array[]
138d3233986SAndreas Gohr     * @psalm-return array<string, string>
139d3233986SAndreas Gohr     */
140605f8e8dSAndreas Gohr    public function getFallbackDirs()
141605f8e8dSAndreas Gohr    {
142605f8e8dSAndreas Gohr        return $this->fallbackDirsPsr0;
143605f8e8dSAndreas Gohr    }
144605f8e8dSAndreas Gohr
145d3233986SAndreas Gohr    /**
146d3233986SAndreas Gohr     * @return array[]
147d3233986SAndreas Gohr     * @psalm-return array<string, string>
148d3233986SAndreas Gohr     */
149605f8e8dSAndreas Gohr    public function getFallbackDirsPsr4()
150605f8e8dSAndreas Gohr    {
151605f8e8dSAndreas Gohr        return $this->fallbackDirsPsr4;
152605f8e8dSAndreas Gohr    }
153605f8e8dSAndreas Gohr
154d3233986SAndreas Gohr    /**
155d3233986SAndreas Gohr     * @return string[] Array of classname => path
156d3233986SAndreas Gohr     * @psalm-return array<string, string>
157d3233986SAndreas Gohr     */
158605f8e8dSAndreas Gohr    public function getClassMap()
159605f8e8dSAndreas Gohr    {
160605f8e8dSAndreas Gohr        return $this->classMap;
161605f8e8dSAndreas Gohr    }
162605f8e8dSAndreas Gohr
163605f8e8dSAndreas Gohr    /**
164d3233986SAndreas Gohr     * @param string[] $classMap Class to filename map
165d3233986SAndreas Gohr     * @psalm-param array<string, string> $classMap
166d3233986SAndreas Gohr     *
167d3233986SAndreas Gohr     * @return void
168605f8e8dSAndreas Gohr     */
169605f8e8dSAndreas Gohr    public function addClassMap(array $classMap)
170605f8e8dSAndreas Gohr    {
171605f8e8dSAndreas Gohr        if ($this->classMap) {
172605f8e8dSAndreas Gohr            $this->classMap = array_merge($this->classMap, $classMap);
173605f8e8dSAndreas Gohr        } else {
174605f8e8dSAndreas Gohr            $this->classMap = $classMap;
175605f8e8dSAndreas Gohr        }
176605f8e8dSAndreas Gohr    }
177605f8e8dSAndreas Gohr
178605f8e8dSAndreas Gohr    /**
179605f8e8dSAndreas Gohr     * Registers a set of PSR-0 directories for a given prefix, either
180605f8e8dSAndreas Gohr     * appending or prepending to the ones previously set for this prefix.
181605f8e8dSAndreas Gohr     *
182605f8e8dSAndreas Gohr     * @param string          $prefix  The prefix
183d3233986SAndreas Gohr     * @param string[]|string $paths   The PSR-0 root directories
184605f8e8dSAndreas Gohr     * @param bool            $prepend Whether to prepend the directories
185d3233986SAndreas Gohr     *
186d3233986SAndreas Gohr     * @return void
187605f8e8dSAndreas Gohr     */
188605f8e8dSAndreas Gohr    public function add($prefix, $paths, $prepend = false)
189605f8e8dSAndreas Gohr    {
190605f8e8dSAndreas Gohr        if (!$prefix) {
191605f8e8dSAndreas Gohr            if ($prepend) {
192605f8e8dSAndreas Gohr                $this->fallbackDirsPsr0 = array_merge(
193605f8e8dSAndreas Gohr                    (array) $paths,
194605f8e8dSAndreas Gohr                    $this->fallbackDirsPsr0
195605f8e8dSAndreas Gohr                );
196605f8e8dSAndreas Gohr            } else {
197605f8e8dSAndreas Gohr                $this->fallbackDirsPsr0 = array_merge(
198605f8e8dSAndreas Gohr                    $this->fallbackDirsPsr0,
199605f8e8dSAndreas Gohr                    (array) $paths
200605f8e8dSAndreas Gohr                );
201605f8e8dSAndreas Gohr            }
202605f8e8dSAndreas Gohr
203605f8e8dSAndreas Gohr            return;
204605f8e8dSAndreas Gohr        }
205605f8e8dSAndreas Gohr
206605f8e8dSAndreas Gohr        $first = $prefix[0];
207605f8e8dSAndreas Gohr        if (!isset($this->prefixesPsr0[$first][$prefix])) {
208605f8e8dSAndreas Gohr            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
209605f8e8dSAndreas Gohr
210605f8e8dSAndreas Gohr            return;
211605f8e8dSAndreas Gohr        }
212605f8e8dSAndreas Gohr        if ($prepend) {
213605f8e8dSAndreas Gohr            $this->prefixesPsr0[$first][$prefix] = array_merge(
214605f8e8dSAndreas Gohr                (array) $paths,
215605f8e8dSAndreas Gohr                $this->prefixesPsr0[$first][$prefix]
216605f8e8dSAndreas Gohr            );
217605f8e8dSAndreas Gohr        } else {
218605f8e8dSAndreas Gohr            $this->prefixesPsr0[$first][$prefix] = array_merge(
219605f8e8dSAndreas Gohr                $this->prefixesPsr0[$first][$prefix],
220605f8e8dSAndreas Gohr                (array) $paths
221605f8e8dSAndreas Gohr            );
222605f8e8dSAndreas Gohr        }
223605f8e8dSAndreas Gohr    }
224605f8e8dSAndreas Gohr
225605f8e8dSAndreas Gohr    /**
226605f8e8dSAndreas Gohr     * Registers a set of PSR-4 directories for a given namespace, either
227605f8e8dSAndreas Gohr     * appending or prepending to the ones previously set for this namespace.
228605f8e8dSAndreas Gohr     *
229605f8e8dSAndreas Gohr     * @param string          $prefix  The prefix/namespace, with trailing '\\'
230d3233986SAndreas Gohr     * @param string[]|string $paths   The PSR-4 base directories
231605f8e8dSAndreas Gohr     * @param bool            $prepend Whether to prepend the directories
232605f8e8dSAndreas Gohr     *
233605f8e8dSAndreas Gohr     * @throws \InvalidArgumentException
234d3233986SAndreas Gohr     *
235d3233986SAndreas Gohr     * @return void
236605f8e8dSAndreas Gohr     */
237605f8e8dSAndreas Gohr    public function addPsr4($prefix, $paths, $prepend = false)
238605f8e8dSAndreas Gohr    {
239605f8e8dSAndreas Gohr        if (!$prefix) {
240605f8e8dSAndreas Gohr            // Register directories for the root namespace.
241605f8e8dSAndreas Gohr            if ($prepend) {
242605f8e8dSAndreas Gohr                $this->fallbackDirsPsr4 = array_merge(
243605f8e8dSAndreas Gohr                    (array) $paths,
244605f8e8dSAndreas Gohr                    $this->fallbackDirsPsr4
245605f8e8dSAndreas Gohr                );
246605f8e8dSAndreas Gohr            } else {
247605f8e8dSAndreas Gohr                $this->fallbackDirsPsr4 = array_merge(
248605f8e8dSAndreas Gohr                    $this->fallbackDirsPsr4,
249605f8e8dSAndreas Gohr                    (array) $paths
250605f8e8dSAndreas Gohr                );
251605f8e8dSAndreas Gohr            }
252605f8e8dSAndreas Gohr        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
253605f8e8dSAndreas Gohr            // Register directories for a new namespace.
254605f8e8dSAndreas Gohr            $length = strlen($prefix);
255605f8e8dSAndreas Gohr            if ('\\' !== $prefix[$length - 1]) {
256605f8e8dSAndreas Gohr                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
257605f8e8dSAndreas Gohr            }
258605f8e8dSAndreas Gohr            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
259605f8e8dSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = (array) $paths;
260605f8e8dSAndreas Gohr        } elseif ($prepend) {
261605f8e8dSAndreas Gohr            // Prepend directories for an already registered namespace.
262605f8e8dSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = array_merge(
263605f8e8dSAndreas Gohr                (array) $paths,
264605f8e8dSAndreas Gohr                $this->prefixDirsPsr4[$prefix]
265605f8e8dSAndreas Gohr            );
266605f8e8dSAndreas Gohr        } else {
267605f8e8dSAndreas Gohr            // Append directories for an already registered namespace.
268605f8e8dSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = array_merge(
269605f8e8dSAndreas Gohr                $this->prefixDirsPsr4[$prefix],
270605f8e8dSAndreas Gohr                (array) $paths
271605f8e8dSAndreas Gohr            );
272605f8e8dSAndreas Gohr        }
273605f8e8dSAndreas Gohr    }
274605f8e8dSAndreas Gohr
275605f8e8dSAndreas Gohr    /**
276605f8e8dSAndreas Gohr     * Registers a set of PSR-0 directories for a given prefix,
277605f8e8dSAndreas Gohr     * replacing any others previously set for this prefix.
278605f8e8dSAndreas Gohr     *
279605f8e8dSAndreas Gohr     * @param string          $prefix The prefix
280d3233986SAndreas Gohr     * @param string[]|string $paths  The PSR-0 base directories
281d3233986SAndreas Gohr     *
282d3233986SAndreas Gohr     * @return void
283605f8e8dSAndreas Gohr     */
284605f8e8dSAndreas Gohr    public function set($prefix, $paths)
285605f8e8dSAndreas Gohr    {
286605f8e8dSAndreas Gohr        if (!$prefix) {
287605f8e8dSAndreas Gohr            $this->fallbackDirsPsr0 = (array) $paths;
288605f8e8dSAndreas Gohr        } else {
289605f8e8dSAndreas Gohr            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
290605f8e8dSAndreas Gohr        }
291605f8e8dSAndreas Gohr    }
292605f8e8dSAndreas Gohr
293605f8e8dSAndreas Gohr    /**
294605f8e8dSAndreas Gohr     * Registers a set of PSR-4 directories for a given namespace,
295605f8e8dSAndreas Gohr     * replacing any others previously set for this namespace.
296605f8e8dSAndreas Gohr     *
297605f8e8dSAndreas Gohr     * @param string          $prefix The prefix/namespace, with trailing '\\'
298d3233986SAndreas Gohr     * @param string[]|string $paths  The PSR-4 base directories
299605f8e8dSAndreas Gohr     *
300605f8e8dSAndreas Gohr     * @throws \InvalidArgumentException
301d3233986SAndreas Gohr     *
302d3233986SAndreas Gohr     * @return void
303605f8e8dSAndreas Gohr     */
304605f8e8dSAndreas Gohr    public function setPsr4($prefix, $paths)
305605f8e8dSAndreas Gohr    {
306605f8e8dSAndreas Gohr        if (!$prefix) {
307605f8e8dSAndreas Gohr            $this->fallbackDirsPsr4 = (array) $paths;
308605f8e8dSAndreas Gohr        } else {
309605f8e8dSAndreas Gohr            $length = strlen($prefix);
310605f8e8dSAndreas Gohr            if ('\\' !== $prefix[$length - 1]) {
311605f8e8dSAndreas Gohr                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
312605f8e8dSAndreas Gohr            }
313605f8e8dSAndreas Gohr            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
314605f8e8dSAndreas Gohr            $this->prefixDirsPsr4[$prefix] = (array) $paths;
315605f8e8dSAndreas Gohr        }
316605f8e8dSAndreas Gohr    }
317605f8e8dSAndreas Gohr
318605f8e8dSAndreas Gohr    /**
319605f8e8dSAndreas Gohr     * Turns on searching the include path for class files.
320605f8e8dSAndreas Gohr     *
321605f8e8dSAndreas Gohr     * @param bool $useIncludePath
322d3233986SAndreas Gohr     *
323d3233986SAndreas Gohr     * @return void
324605f8e8dSAndreas Gohr     */
325605f8e8dSAndreas Gohr    public function setUseIncludePath($useIncludePath)
326605f8e8dSAndreas Gohr    {
327605f8e8dSAndreas Gohr        $this->useIncludePath = $useIncludePath;
328605f8e8dSAndreas Gohr    }
329605f8e8dSAndreas Gohr
330605f8e8dSAndreas Gohr    /**
331605f8e8dSAndreas Gohr     * Can be used to check if the autoloader uses the include path to check
332605f8e8dSAndreas Gohr     * for classes.
333605f8e8dSAndreas Gohr     *
334605f8e8dSAndreas Gohr     * @return bool
335605f8e8dSAndreas Gohr     */
336605f8e8dSAndreas Gohr    public function getUseIncludePath()
337605f8e8dSAndreas Gohr    {
338605f8e8dSAndreas Gohr        return $this->useIncludePath;
339605f8e8dSAndreas Gohr    }
340605f8e8dSAndreas Gohr
341605f8e8dSAndreas Gohr    /**
342605f8e8dSAndreas Gohr     * Turns off searching the prefix and fallback directories for classes
343605f8e8dSAndreas Gohr     * that have not been registered with the class map.
344605f8e8dSAndreas Gohr     *
345605f8e8dSAndreas Gohr     * @param bool $classMapAuthoritative
346d3233986SAndreas Gohr     *
347d3233986SAndreas Gohr     * @return void
348605f8e8dSAndreas Gohr     */
349605f8e8dSAndreas Gohr    public function setClassMapAuthoritative($classMapAuthoritative)
350605f8e8dSAndreas Gohr    {
351605f8e8dSAndreas Gohr        $this->classMapAuthoritative = $classMapAuthoritative;
352605f8e8dSAndreas Gohr    }
353605f8e8dSAndreas Gohr
354605f8e8dSAndreas Gohr    /**
355605f8e8dSAndreas Gohr     * Should class lookup fail if not found in the current class map?
356605f8e8dSAndreas Gohr     *
357605f8e8dSAndreas Gohr     * @return bool
358605f8e8dSAndreas Gohr     */
359605f8e8dSAndreas Gohr    public function isClassMapAuthoritative()
360605f8e8dSAndreas Gohr    {
361605f8e8dSAndreas Gohr        return $this->classMapAuthoritative;
362605f8e8dSAndreas Gohr    }
363605f8e8dSAndreas Gohr
364605f8e8dSAndreas Gohr    /**
365e0dd796dSAndreas Gohr     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
366e0dd796dSAndreas Gohr     *
367e0dd796dSAndreas Gohr     * @param string|null $apcuPrefix
368d3233986SAndreas Gohr     *
369d3233986SAndreas Gohr     * @return void
370e0dd796dSAndreas Gohr     */
371e0dd796dSAndreas Gohr    public function setApcuPrefix($apcuPrefix)
372e0dd796dSAndreas Gohr    {
373e43cd7e1SAndreas Gohr        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
374e0dd796dSAndreas Gohr    }
375e0dd796dSAndreas Gohr
376e0dd796dSAndreas Gohr    /**
377e0dd796dSAndreas Gohr     * The APCu prefix in use, or null if APCu caching is not enabled.
378e0dd796dSAndreas Gohr     *
379e0dd796dSAndreas Gohr     * @return string|null
380e0dd796dSAndreas Gohr     */
381e0dd796dSAndreas Gohr    public function getApcuPrefix()
382e0dd796dSAndreas Gohr    {
383e0dd796dSAndreas Gohr        return $this->apcuPrefix;
384e0dd796dSAndreas Gohr    }
385e0dd796dSAndreas Gohr
386e0dd796dSAndreas Gohr    /**
387605f8e8dSAndreas Gohr     * Registers this instance as an autoloader.
388605f8e8dSAndreas Gohr     *
389605f8e8dSAndreas Gohr     * @param bool $prepend Whether to prepend the autoloader or not
390d3233986SAndreas Gohr     *
391d3233986SAndreas Gohr     * @return void
392605f8e8dSAndreas Gohr     */
393605f8e8dSAndreas Gohr    public function register($prepend = false)
394605f8e8dSAndreas Gohr    {
395605f8e8dSAndreas Gohr        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
3966cb05674SAndreas Gohr
3976cb05674SAndreas Gohr        if (null === $this->vendorDir) {
3986cb05674SAndreas Gohr            return;
3996cb05674SAndreas Gohr        }
4006cb05674SAndreas Gohr
4016cb05674SAndreas Gohr        if ($prepend) {
4026cb05674SAndreas Gohr            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
4036cb05674SAndreas Gohr        } else {
4046cb05674SAndreas Gohr            unset(self::$registeredLoaders[$this->vendorDir]);
4056cb05674SAndreas Gohr            self::$registeredLoaders[$this->vendorDir] = $this;
4066cb05674SAndreas Gohr        }
407605f8e8dSAndreas Gohr    }
408605f8e8dSAndreas Gohr
409605f8e8dSAndreas Gohr    /**
410605f8e8dSAndreas Gohr     * Unregisters this instance as an autoloader.
411d3233986SAndreas Gohr     *
412d3233986SAndreas Gohr     * @return void
413605f8e8dSAndreas Gohr     */
414605f8e8dSAndreas Gohr    public function unregister()
415605f8e8dSAndreas Gohr    {
416605f8e8dSAndreas Gohr        spl_autoload_unregister(array($this, 'loadClass'));
4176cb05674SAndreas Gohr
4186cb05674SAndreas Gohr        if (null !== $this->vendorDir) {
4196cb05674SAndreas Gohr            unset(self::$registeredLoaders[$this->vendorDir]);
4206cb05674SAndreas Gohr        }
421605f8e8dSAndreas Gohr    }
422605f8e8dSAndreas Gohr
423605f8e8dSAndreas Gohr    /**
424605f8e8dSAndreas Gohr     * Loads the given class or interface.
425605f8e8dSAndreas Gohr     *
426605f8e8dSAndreas Gohr     * @param  string    $class The name of the class
427d3233986SAndreas Gohr     * @return true|null True if loaded, null otherwise
428605f8e8dSAndreas Gohr     */
429605f8e8dSAndreas Gohr    public function loadClass($class)
430605f8e8dSAndreas Gohr    {
431605f8e8dSAndreas Gohr        if ($file = $this->findFile($class)) {
432*28e9760aSAndreas Gohr            $includeFile = self::$includeFile;
433*28e9760aSAndreas Gohr            $includeFile($file);
434605f8e8dSAndreas Gohr
435605f8e8dSAndreas Gohr            return true;
436605f8e8dSAndreas Gohr        }
437d3233986SAndreas Gohr
438d3233986SAndreas Gohr        return null;
439605f8e8dSAndreas Gohr    }
440605f8e8dSAndreas Gohr
441605f8e8dSAndreas Gohr    /**
442605f8e8dSAndreas Gohr     * Finds the path to the file where the class is defined.
443605f8e8dSAndreas Gohr     *
444605f8e8dSAndreas Gohr     * @param string $class The name of the class
445605f8e8dSAndreas Gohr     *
446605f8e8dSAndreas Gohr     * @return string|false The path if found, false otherwise
447605f8e8dSAndreas Gohr     */
448605f8e8dSAndreas Gohr    public function findFile($class)
449605f8e8dSAndreas Gohr    {
450605f8e8dSAndreas Gohr        // class map lookup
451605f8e8dSAndreas Gohr        if (isset($this->classMap[$class])) {
452605f8e8dSAndreas Gohr            return $this->classMap[$class];
453605f8e8dSAndreas Gohr        }
4547a33d2f8SNiklas Keller        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
455605f8e8dSAndreas Gohr            return false;
456605f8e8dSAndreas Gohr        }
457e0dd796dSAndreas Gohr        if (null !== $this->apcuPrefix) {
458e0dd796dSAndreas Gohr            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
459e0dd796dSAndreas Gohr            if ($hit) {
460e0dd796dSAndreas Gohr                return $file;
461e0dd796dSAndreas Gohr            }
462e0dd796dSAndreas Gohr        }
463605f8e8dSAndreas Gohr
464605f8e8dSAndreas Gohr        $file = $this->findFileWithExtension($class, '.php');
465605f8e8dSAndreas Gohr
466605f8e8dSAndreas Gohr        // Search for Hack files if we are running on HHVM
4677a33d2f8SNiklas Keller        if (false === $file && defined('HHVM_VERSION')) {
468605f8e8dSAndreas Gohr            $file = $this->findFileWithExtension($class, '.hh');
469605f8e8dSAndreas Gohr        }
470605f8e8dSAndreas Gohr
471e0dd796dSAndreas Gohr        if (null !== $this->apcuPrefix) {
472e0dd796dSAndreas Gohr            apcu_add($this->apcuPrefix.$class, $file);
473e0dd796dSAndreas Gohr        }
474e0dd796dSAndreas Gohr
4757a33d2f8SNiklas Keller        if (false === $file) {
476605f8e8dSAndreas Gohr            // Remember that this class does not exist.
4777a33d2f8SNiklas Keller            $this->missingClasses[$class] = true;
478605f8e8dSAndreas Gohr        }
479605f8e8dSAndreas Gohr
480605f8e8dSAndreas Gohr        return $file;
481605f8e8dSAndreas Gohr    }
482605f8e8dSAndreas Gohr
4836cb05674SAndreas Gohr    /**
4846cb05674SAndreas Gohr     * Returns the currently registered loaders indexed by their corresponding vendor directories.
4856cb05674SAndreas Gohr     *
4866cb05674SAndreas Gohr     * @return self[]
4876cb05674SAndreas Gohr     */
4886cb05674SAndreas Gohr    public static function getRegisteredLoaders()
4896cb05674SAndreas Gohr    {
4906cb05674SAndreas Gohr        return self::$registeredLoaders;
4916cb05674SAndreas Gohr    }
4926cb05674SAndreas Gohr
493d3233986SAndreas Gohr    /**
494d3233986SAndreas Gohr     * @param  string       $class
495d3233986SAndreas Gohr     * @param  string       $ext
496d3233986SAndreas Gohr     * @return string|false
497d3233986SAndreas Gohr     */
498605f8e8dSAndreas Gohr    private function findFileWithExtension($class, $ext)
499605f8e8dSAndreas Gohr    {
500605f8e8dSAndreas Gohr        // PSR-4 lookup
501605f8e8dSAndreas Gohr        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
502605f8e8dSAndreas Gohr
503605f8e8dSAndreas Gohr        $first = $class[0];
504605f8e8dSAndreas Gohr        if (isset($this->prefixLengthsPsr4[$first])) {
505e0dd796dSAndreas Gohr            $subPath = $class;
506e0dd796dSAndreas Gohr            while (false !== $lastPos = strrpos($subPath, '\\')) {
507e0dd796dSAndreas Gohr                $subPath = substr($subPath, 0, $lastPos);
508e0dd796dSAndreas Gohr                $search = $subPath . '\\';
509e0dd796dSAndreas Gohr                if (isset($this->prefixDirsPsr4[$search])) {
510e43cd7e1SAndreas Gohr                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
511e0dd796dSAndreas Gohr                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
512e43cd7e1SAndreas Gohr                        if (file_exists($file = $dir . $pathEnd)) {
513605f8e8dSAndreas Gohr                            return $file;
514605f8e8dSAndreas Gohr                        }
515605f8e8dSAndreas Gohr                    }
516605f8e8dSAndreas Gohr                }
517605f8e8dSAndreas Gohr            }
518605f8e8dSAndreas Gohr        }
519605f8e8dSAndreas Gohr
520605f8e8dSAndreas Gohr        // PSR-4 fallback dirs
521605f8e8dSAndreas Gohr        foreach ($this->fallbackDirsPsr4 as $dir) {
5227a33d2f8SNiklas Keller            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
523605f8e8dSAndreas Gohr                return $file;
524605f8e8dSAndreas Gohr            }
525605f8e8dSAndreas Gohr        }
526605f8e8dSAndreas Gohr
527605f8e8dSAndreas Gohr        // PSR-0 lookup
528605f8e8dSAndreas Gohr        if (false !== $pos = strrpos($class, '\\')) {
529605f8e8dSAndreas Gohr            // namespaced class name
530605f8e8dSAndreas Gohr            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
531605f8e8dSAndreas Gohr                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
532605f8e8dSAndreas Gohr        } else {
533605f8e8dSAndreas Gohr            // PEAR-like class name
534605f8e8dSAndreas Gohr            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
535605f8e8dSAndreas Gohr        }
536605f8e8dSAndreas Gohr
537605f8e8dSAndreas Gohr        if (isset($this->prefixesPsr0[$first])) {
538605f8e8dSAndreas Gohr            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
539605f8e8dSAndreas Gohr                if (0 === strpos($class, $prefix)) {
540605f8e8dSAndreas Gohr                    foreach ($dirs as $dir) {
5417a33d2f8SNiklas Keller                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
542605f8e8dSAndreas Gohr                            return $file;
543605f8e8dSAndreas Gohr                        }
544605f8e8dSAndreas Gohr                    }
545605f8e8dSAndreas Gohr                }
546605f8e8dSAndreas Gohr            }
547605f8e8dSAndreas Gohr        }
548605f8e8dSAndreas Gohr
549605f8e8dSAndreas Gohr        // PSR-0 fallback dirs
550605f8e8dSAndreas Gohr        foreach ($this->fallbackDirsPsr0 as $dir) {
5517a33d2f8SNiklas Keller            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
552605f8e8dSAndreas Gohr                return $file;
553605f8e8dSAndreas Gohr            }
554605f8e8dSAndreas Gohr        }
555605f8e8dSAndreas Gohr
556605f8e8dSAndreas Gohr        // PSR-0 include paths.
557605f8e8dSAndreas Gohr        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
558605f8e8dSAndreas Gohr            return $file;
559605f8e8dSAndreas Gohr        }
5607a33d2f8SNiklas Keller
5617a33d2f8SNiklas Keller        return false;
562605f8e8dSAndreas Gohr    }
563*28e9760aSAndreas Gohr
564*28e9760aSAndreas Gohr    /**
565*28e9760aSAndreas Gohr     * @return void
566*28e9760aSAndreas Gohr     */
567*28e9760aSAndreas Gohr    private static function initializeIncludeClosure()
568*28e9760aSAndreas Gohr    {
569*28e9760aSAndreas Gohr        if (self::$includeFile !== null) {
570*28e9760aSAndreas Gohr            return;
571605f8e8dSAndreas Gohr        }
572605f8e8dSAndreas Gohr
573605f8e8dSAndreas Gohr        /**
574605f8e8dSAndreas Gohr         * Scope isolated include.
575605f8e8dSAndreas Gohr         *
576605f8e8dSAndreas Gohr         * Prevents access to $this/self from included files.
577d3233986SAndreas Gohr         *
578d3233986SAndreas Gohr         * @param  string $file
579d3233986SAndreas Gohr         * @return void
580605f8e8dSAndreas Gohr         */
581*28e9760aSAndreas Gohr        self::$includeFile = \Closure::bind(static function($file) {
582605f8e8dSAndreas Gohr            include $file;
583*28e9760aSAndreas Gohr        }, null, null);
584*28e9760aSAndreas Gohr    }
585605f8e8dSAndreas Gohr}
586