vendor/composer/ClassLoader.php line 478

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