vendor/composer/ClassLoader.php line 571

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