Subversion Repositories oidplus

Rev

Rev 958 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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