[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/composer/ -> ClassLoader.php (source)

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