vendor/composer/ClassLoader.php line 423

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