mantis-matrix-integration – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 <?php
2  
3 /*
4 * This file is part of Composer.
5 *
6 * (c) Nils Adermann <naderman@naderman.de>
7 * Jordi Boggiano <j.boggiano@seld.be>
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12  
13 namespace Composer\Autoload;
14  
15 /**
16 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17 *
18 * $loader = new \Composer\Autoload\ClassLoader();
19 *
20 * // register classes with namespaces
21 * $loader->add('Symfony\Component', __DIR__.'/component');
22 * $loader->add('Symfony', __DIR__.'/framework');
23 *
24 * // activate the autoloader
25 * $loader->register();
26 *
27 * // to enable searching the include path (eg. for PEAR packages)
28 * $loader->setUseIncludePath(true);
29 *
30 * In this example, if you try to use a class in the Symfony\Component
31 * namespace or one of its children (Symfony\Component\Console for instance),
32 * the autoloader will first look for the class under the component/
33 * directory, and it will then fallback to the framework/ directory if not
34 * found before giving up.
35 *
36 * This class is loosely based on the Symfony UniversalClassLoader.
37 *
38 * @author Fabien Potencier <fabien@symfony.com>
39 * @author Jordi Boggiano <j.boggiano@seld.be>
40 * @see https://www.php-fig.org/psr/psr-0/
41 * @see https://www.php-fig.org/psr/psr-4/
42 */
43 class ClassLoader
44 {
45 /** @var \Closure(string):void */
46 private static $includeFile;
47  
48 /** @var ?string */
49 private $vendorDir;
50  
51 // PSR-4
52 /**
53 * @var array[]
54 * @psalm-var array<string, array<string, int>>
55 */
56 private $prefixLengthsPsr4 = array();
57 /**
58 * @var array[]
59 * @psalm-var array<string, array<int, string>>
60 */
61 private $prefixDirsPsr4 = array();
62 /**
63 * @var array[]
64 * @psalm-var array<string, string>
65 */
66 private $fallbackDirsPsr4 = array();
67  
68 // PSR-0
69 /**
70 * @var array[]
71 * @psalm-var array<string, array<string, string[]>>
72 */
73 private $prefixesPsr0 = array();
74 /**
75 * @var array[]
76 * @psalm-var array<string, string>
77 */
78 private $fallbackDirsPsr0 = array();
79  
80 /** @var bool */
81 private $useIncludePath = false;
82  
83 /**
84 * @var string[]
85 * @psalm-var array<string, string>
86 */
87 private $classMap = array();
88  
89 /** @var bool */
90 private $classMapAuthoritative = false;
91  
92 /**
93 * @var bool[]
94 * @psalm-var array<string, bool>
95 */
96 private $missingClasses = array();
97  
98 /** @var ?string */
99 private $apcuPrefix;
100  
101 /**
102 * @var self[]
103 */
104 private static $registeredLoaders = array();
105  
106 /**
107 * @param ?string $vendorDir
108 */
109 public function __construct($vendorDir = null)
110 {
111 $this->vendorDir = $vendorDir;
112 self::initializeIncludeClosure();
113 }
114  
115 /**
116 * @return string[]
117 */
118 public function getPrefixes()
119 {
120 if (!empty($this->prefixesPsr0)) {
121 return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
122 }
123  
124 return array();
125 }
126  
127 /**
128 * @return array[]
129 * @psalm-return array<string, array<int, string>>
130 */
131 public function getPrefixesPsr4()
132 {
133 return $this->prefixDirsPsr4;
134 }
135  
136 /**
137 * @return array[]
138 * @psalm-return array<string, string>
139 */
140 public function getFallbackDirs()
141 {
142 return $this->fallbackDirsPsr0;
143 }
144  
145 /**
146 * @return array[]
147 * @psalm-return array<string, string>
148 */
149 public function getFallbackDirsPsr4()
150 {
151 return $this->fallbackDirsPsr4;
152 }
153  
154 /**
155 * @return string[] Array of classname => path
156 * @psalm-return array<string, string>
157 */
158 public function getClassMap()
159 {
160 return $this->classMap;
161 }
162  
163 /**
164 * @param string[] $classMap Class to filename map
165 * @psalm-param array<string, string> $classMap
166 *
167 * @return void
168 */
169 public function addClassMap(array $classMap)
170 {
171 if ($this->classMap) {
172 $this->classMap = array_merge($this->classMap, $classMap);
173 } else {
174 $this->classMap = $classMap;
175 }
176 }
177  
178 /**
179 * Registers a set of PSR-0 directories for a given prefix, either
180 * appending or prepending to the ones previously set for this prefix.
181 *
182 * @param string $prefix The prefix
183 * @param string[]|string $paths The PSR-0 root directories
184 * @param bool $prepend Whether to prepend the directories
185 *
186 * @return void
187 */
188 public function add($prefix, $paths, $prepend = false)
189 {
190 if (!$prefix) {
191 if ($prepend) {
192 $this->fallbackDirsPsr0 = array_merge(
193 (array) $paths,
194 $this->fallbackDirsPsr0
195 );
196 } else {
197 $this->fallbackDirsPsr0 = array_merge(
198 $this->fallbackDirsPsr0,
199 (array) $paths
200 );
201 }
202  
203 return;
204 }
205  
206 $first = $prefix[0];
207 if (!isset($this->prefixesPsr0[$first][$prefix])) {
208 $this->prefixesPsr0[$first][$prefix] = (array) $paths;
209  
210 return;
211 }
212 if ($prepend) {
213 $this->prefixesPsr0[$first][$prefix] = array_merge(
214 (array) $paths,
215 $this->prefixesPsr0[$first][$prefix]
216 );
217 } else {
218 $this->prefixesPsr0[$first][$prefix] = array_merge(
219 $this->prefixesPsr0[$first][$prefix],
220 (array) $paths
221 );
222 }
223 }
224  
225 /**
226 * Registers a set of PSR-4 directories for a given namespace, either
227 * appending or prepending to the ones previously set for this namespace.
228 *
229 * @param string $prefix The prefix/namespace, with trailing '\\'
230 * @param string[]|string $paths The PSR-4 base directories
231 * @param bool $prepend Whether to prepend the directories
232 *
233 * @throws \InvalidArgumentException
234 *
235 * @return void
236 */
237 public function addPsr4($prefix, $paths, $prepend = false)
238 {
239 if (!$prefix) {
240 // Register directories for the root namespace.
241 if ($prepend) {
242 $this->fallbackDirsPsr4 = array_merge(
243 (array) $paths,
244 $this->fallbackDirsPsr4
245 );
246 } else {
247 $this->fallbackDirsPsr4 = array_merge(
248 $this->fallbackDirsPsr4,
249 (array) $paths
250 );
251 }
252 } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
253 // Register directories for a new namespace.
254 $length = strlen($prefix);
255 if ('\\' !== $prefix[$length - 1]) {
256 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
257 }
258 $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
259 $this->prefixDirsPsr4[$prefix] = (array) $paths;
260 } elseif ($prepend) {
261 // Prepend directories for an already registered namespace.
262 $this->prefixDirsPsr4[$prefix] = array_merge(
263 (array) $paths,
264 $this->prefixDirsPsr4[$prefix]
265 );
266 } else {
267 // Append directories for an already registered namespace.
268 $this->prefixDirsPsr4[$prefix] = array_merge(
269 $this->prefixDirsPsr4[$prefix],
270 (array) $paths
271 );
272 }
273 }
274  
275 /**
276 * Registers a set of PSR-0 directories for a given prefix,
277 * replacing any others previously set for this prefix.
278 *
279 * @param string $prefix The prefix
280 * @param string[]|string $paths The PSR-0 base directories
281 *
282 * @return void
283 */
284 public function set($prefix, $paths)
285 {
286 if (!$prefix) {
287 $this->fallbackDirsPsr0 = (array) $paths;
288 } else {
289 $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
290 }
291 }
292  
293 /**
294 * Registers a set of PSR-4 directories for a given namespace,
295 * replacing any others previously set for this namespace.
296 *
297 * @param string $prefix The prefix/namespace, with trailing '\\'
298 * @param string[]|string $paths The PSR-4 base directories
299 *
300 * @throws \InvalidArgumentException
301 *
302 * @return void
303 */
304 public function setPsr4($prefix, $paths)
305 {
306 if (!$prefix) {
307 $this->fallbackDirsPsr4 = (array) $paths;
308 } else {
309 $length = strlen($prefix);
310 if ('\\' !== $prefix[$length - 1]) {
311 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
312 }
313 $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
314 $this->prefixDirsPsr4[$prefix] = (array) $paths;
315 }
316 }
317  
318 /**
319 * Turns on searching the include path for class files.
320 *
321 * @param bool $useIncludePath
322 *
323 * @return void
324 */
325 public function setUseIncludePath($useIncludePath)
326 {
327 $this->useIncludePath = $useIncludePath;
328 }
329  
330 /**
331 * Can be used to check if the autoloader uses the include path to check
332 * for classes.
333 *
334 * @return bool
335 */
336 public function getUseIncludePath()
337 {
338 return $this->useIncludePath;
339 }
340  
341 /**
342 * Turns off searching the prefix and fallback directories for classes
343 * that have not been registered with the class map.
344 *
345 * @param bool $classMapAuthoritative
346 *
347 * @return void
348 */
349 public function setClassMapAuthoritative($classMapAuthoritative)
350 {
351 $this->classMapAuthoritative = $classMapAuthoritative;
352 }
353  
354 /**
355 * Should class lookup fail if not found in the current class map?
356 *
357 * @return bool
358 */
359 public function isClassMapAuthoritative()
360 {
361 return $this->classMapAuthoritative;
362 }
363  
364 /**
365 * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
366 *
367 * @param string|null $apcuPrefix
368 *
369 * @return void
370 */
371 public function setApcuPrefix($apcuPrefix)
372 {
373 $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
374 }
375  
376 /**
377 * The APCu prefix in use, or null if APCu caching is not enabled.
378 *
379 * @return string|null
380 */
381 public function getApcuPrefix()
382 {
383 return $this->apcuPrefix;
384 }
385  
386 /**
387 * Registers this instance as an autoloader.
388 *
389 * @param bool $prepend Whether to prepend the autoloader or not
390 *
391 * @return void
392 */
393 public function register($prepend = false)
394 {
395 spl_autoload_register(array($this, 'loadClass'), true, $prepend);
396  
397 if (null === $this->vendorDir) {
398 return;
399 }
400  
401 if ($prepend) {
402 self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
403 } else {
404 unset(self::$registeredLoaders[$this->vendorDir]);
405 self::$registeredLoaders[$this->vendorDir] = $this;
406 }
407 }
408  
409 /**
410 * Unregisters this instance as an autoloader.
411 *
412 * @return void
413 */
414 public function unregister()
415 {
416 spl_autoload_unregister(array($this, 'loadClass'));
417  
418 if (null !== $this->vendorDir) {
419 unset(self::$registeredLoaders[$this->vendorDir]);
420 }
421 }
422  
423 /**
424 * Loads the given class or interface.
425 *
426 * @param string $class The name of the class
427 * @return true|null True if loaded, null otherwise
428 */
429 public function loadClass($class)
430 {
431 if ($file = $this->findFile($class)) {
432 $includeFile = self::$includeFile;
433 $includeFile($file);
434  
435 return true;
436 }
437  
438 return null;
439 }
440  
441 /**
442 * Finds the path to the file where the class is defined.
443 *
444 * @param string $class The name of the class
445 *
446 * @return string|false The path if found, false otherwise
447 */
448 public function findFile($class)
449 {
450 // class map lookup
451 if (isset($this->classMap[$class])) {
452 return $this->classMap[$class];
453 }
454 if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
455 return false;
456 }
457 if (null !== $this->apcuPrefix) {
458 $file = apcu_fetch($this->apcuPrefix.$class, $hit);
459 if ($hit) {
460 return $file;
461 }
462 }
463  
464 $file = $this->findFileWithExtension($class, '.php');
465  
466 // Search for Hack files if we are running on HHVM
467 if (false === $file && defined('HHVM_VERSION')) {
468 $file = $this->findFileWithExtension($class, '.hh');
469 }
470  
471 if (null !== $this->apcuPrefix) {
472 apcu_add($this->apcuPrefix.$class, $file);
473 }
474  
475 if (false === $file) {
476 // Remember that this class does not exist.
477 $this->missingClasses[$class] = true;
478 }
479  
480 return $file;
481 }
482  
483 /**
484 * Returns the currently registered loaders indexed by their corresponding vendor directories.
485 *
486 * @return self[]
487 */
488 public static function getRegisteredLoaders()
489 {
490 return self::$registeredLoaders;
491 }
492  
493 /**
494 * @param string $class
495 * @param string $ext
496 * @return string|false
497 */
498 private function findFileWithExtension($class, $ext)
499 {
500 // PSR-4 lookup
501 $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
502  
503 $first = $class[0];
504 if (isset($this->prefixLengthsPsr4[$first])) {
505 $subPath = $class;
506 while (false !== $lastPos = strrpos($subPath, '\\')) {
507 $subPath = substr($subPath, 0, $lastPos);
508 $search = $subPath . '\\';
509 if (isset($this->prefixDirsPsr4[$search])) {
510 $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
511 foreach ($this->prefixDirsPsr4[$search] as $dir) {
512 if (file_exists($file = $dir . $pathEnd)) {
513 return $file;
514 }
515 }
516 }
517 }
518 }
519  
520 // PSR-4 fallback dirs
521 foreach ($this->fallbackDirsPsr4 as $dir) {
522 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
523 return $file;
524 }
525 }
526  
527 // PSR-0 lookup
528 if (false !== $pos = strrpos($class, '\\')) {
529 // namespaced class name
530 $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
531 . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
532 } else {
533 // PEAR-like class name
534 $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
535 }
536  
537 if (isset($this->prefixesPsr0[$first])) {
538 foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
539 if (0 === strpos($class, $prefix)) {
540 foreach ($dirs as $dir) {
541 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
542 return $file;
543 }
544 }
545 }
546 }
547 }
548  
549 // PSR-0 fallback dirs
550 foreach ($this->fallbackDirsPsr0 as $dir) {
551 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
552 return $file;
553 }
554 }
555  
556 // PSR-0 include paths.
557 if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
558 return $file;
559 }
560  
561 return false;
562 }
563  
564 /**
565 * @return void
566 */
567 private static function initializeIncludeClosure()
568 {
569 if (self::$includeFile !== null) {
570 return;
571 }
572  
573 /**
574 * Scope isolated include.
575 *
576 * Prevents access to $this/self from included files.
577 *
578 * @param string $file
579 * @return void
580 */
581 self::$includeFile = \Closure::bind(static function($file) {
582 include $file;
583 }, null, null);
584 }
585 }