vendor/composer/ClassLoader.php line 578

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