Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]
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 (self::$includeFile)($file); 433 434 return true; 435 } 436 437 return null; 438 } 439 440 /** 441 * Finds the path to the file where the class is defined. 442 * 443 * @param string $class The name of the class 444 * 445 * @return string|false The path if found, false otherwise 446 */ 447 public function findFile($class) 448 { 449 // class map lookup 450 if (isset($this->classMap[$class])) { 451 return $this->classMap[$class]; 452 } 453 if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 454 return false; 455 } 456 if (null !== $this->apcuPrefix) { 457 $file = apcu_fetch($this->apcuPrefix.$class, $hit); 458 if ($hit) { 459 return $file; 460 } 461 } 462 463 $file = $this->findFileWithExtension($class, '.php'); 464 465 // Search for Hack files if we are running on HHVM 466 if (false === $file && defined('HHVM_VERSION')) { 467 $file = $this->findFileWithExtension($class, '.hh'); 468 } 469 470 if (null !== $this->apcuPrefix) { 471 apcu_add($this->apcuPrefix.$class, $file); 472 } 473 474 if (false === $file) { 475 // Remember that this class does not exist. 476 $this->missingClasses[$class] = true; 477 } 478 479 return $file; 480 } 481 482 /** 483 * Returns the currently registered loaders indexed by their corresponding vendor directories. 484 * 485 * @return self[] 486 */ 487 public static function getRegisteredLoaders() 488 { 489 return self::$registeredLoaders; 490 } 491 492 /** 493 * @param string $class 494 * @param string $ext 495 * @return string|false 496 */ 497 private function findFileWithExtension($class, $ext) 498 { 499 // PSR-4 lookup 500 $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 501 502 $first = $class[0]; 503 if (isset($this->prefixLengthsPsr4[$first])) { 504 $subPath = $class; 505 while (false !== $lastPos = strrpos($subPath, '\\')) { 506 $subPath = substr($subPath, 0, $lastPos); 507 $search = $subPath . '\\'; 508 if (isset($this->prefixDirsPsr4[$search])) { 509 $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 510 foreach ($this->prefixDirsPsr4[$search] as $dir) { 511 if (file_exists($file = $dir . $pathEnd)) { 512 return $file; 513 } 514 } 515 } 516 } 517 } 518 519 // PSR-4 fallback dirs 520 foreach ($this->fallbackDirsPsr4 as $dir) { 521 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 522 return $file; 523 } 524 } 525 526 // PSR-0 lookup 527 if (false !== $pos = strrpos($class, '\\')) { 528 // namespaced class name 529 $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 530 . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 531 } else { 532 // PEAR-like class name 533 $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 534 } 535 536 if (isset($this->prefixesPsr0[$first])) { 537 foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 538 if (0 === strpos($class, $prefix)) { 539 foreach ($dirs as $dir) { 540 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 541 return $file; 542 } 543 } 544 } 545 } 546 } 547 548 // PSR-0 fallback dirs 549 foreach ($this->fallbackDirsPsr0 as $dir) { 550 if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 551 return $file; 552 } 553 } 554 555 // PSR-0 include paths. 556 if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 557 return $file; 558 } 559 560 return false; 561 } 562 563 private static function initializeIncludeClosure(): void 564 { 565 if (self::$includeFile !== null) { 566 return; 567 } 568 569 /** 570 * Scope isolated include. 571 * 572 * Prevents access to $this/self from included files. 573 * 574 * @param string $file 575 * @return void 576 */ 577 self::$includeFile = static function($file) { 578 include $file; 579 }; 580 } 581 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body