File indexing completed on 2024-05-12 06:02:11

0001 <?php
0002 
0003 /*
0004  * This file is part of Composer.
0005  *
0006  * (c) Nils Adermann <naderman@naderman.de>
0007  *     Jordi Boggiano <j.boggiano@seld.be>
0008  *
0009  * For the full copyright and license information, please view the LICENSE
0010  * file that was distributed with this source code.
0011  */
0012 
0013 namespace Composer\Autoload;
0014 
0015 /**
0016  * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
0017  *
0018  *     $loader = new \Composer\Autoload\ClassLoader();
0019  *
0020  *     // register classes with namespaces
0021  *     $loader->add('Symfony\Component', __DIR__.'/component');
0022  *     $loader->add('Symfony',           __DIR__.'/framework');
0023  *
0024  *     // activate the autoloader
0025  *     $loader->register();
0026  *
0027  *     // to enable searching the include path (eg. for PEAR packages)
0028  *     $loader->setUseIncludePath(true);
0029  *
0030  * In this example, if you try to use a class in the Symfony\Component
0031  * namespace or one of its children (Symfony\Component\Console for instance),
0032  * the autoloader will first look for the class under the component/
0033  * directory, and it will then fallback to the framework/ directory if not
0034  * found before giving up.
0035  *
0036  * This class is loosely based on the Symfony UniversalClassLoader.
0037  *
0038  * @author Fabien Potencier <fabien@symfony.com>
0039  * @author Jordi Boggiano <j.boggiano@seld.be>
0040  * @see    http://www.php-fig.org/psr/psr-0/
0041  * @see    http://www.php-fig.org/psr/psr-4/
0042  */
0043 class ClassLoader
0044 {
0045     // PSR-4
0046     private $prefixLengthsPsr4 = array();
0047     private $prefixDirsPsr4 = array();
0048     private $fallbackDirsPsr4 = array();
0049 
0050     // PSR-0
0051     private $prefixesPsr0 = array();
0052     private $fallbackDirsPsr0 = array();
0053 
0054     private $useIncludePath = false;
0055     private $classMap = array();
0056 
0057     private $classMapAuthoritative = false;
0058 
0059     public function getPrefixes()
0060     {
0061         if (!empty($this->prefixesPsr0)) {
0062             return call_user_func_array('array_merge', $this->prefixesPsr0);
0063         }
0064 
0065         return array();
0066     }
0067 
0068     public function getPrefixesPsr4()
0069     {
0070         return $this->prefixDirsPsr4;
0071     }
0072 
0073     public function getFallbackDirs()
0074     {
0075         return $this->fallbackDirsPsr0;
0076     }
0077 
0078     public function getFallbackDirsPsr4()
0079     {
0080         return $this->fallbackDirsPsr4;
0081     }
0082 
0083     public function getClassMap()
0084     {
0085         return $this->classMap;
0086     }
0087 
0088     /**
0089      * @param array $classMap Class to filename map
0090      */
0091     public function addClassMap(array $classMap)
0092     {
0093         if ($this->classMap) {
0094             $this->classMap = array_merge($this->classMap, $classMap);
0095         } else {
0096             $this->classMap = $classMap;
0097         }
0098     }
0099 
0100     /**
0101      * Registers a set of PSR-0 directories for a given prefix, either
0102      * appending or prepending to the ones previously set for this prefix.
0103      *
0104      * @param string       $prefix  The prefix
0105      * @param array|string $paths   The PSR-0 root directories
0106      * @param bool         $prepend Whether to prepend the directories
0107      */
0108     public function add($prefix, $paths, $prepend = false)
0109     {
0110         if (!$prefix) {
0111             if ($prepend) {
0112                 $this->fallbackDirsPsr0 = array_merge(
0113                     (array) $paths,
0114                     $this->fallbackDirsPsr0
0115                 );
0116             } else {
0117                 $this->fallbackDirsPsr0 = array_merge(
0118                     $this->fallbackDirsPsr0,
0119                     (array) $paths
0120                 );
0121             }
0122 
0123             return;
0124         }
0125 
0126         $first = $prefix[0];
0127         if (!isset($this->prefixesPsr0[$first][$prefix])) {
0128             $this->prefixesPsr0[$first][$prefix] = (array) $paths;
0129 
0130             return;
0131         }
0132         if ($prepend) {
0133             $this->prefixesPsr0[$first][$prefix] = array_merge(
0134                 (array) $paths,
0135                 $this->prefixesPsr0[$first][$prefix]
0136             );
0137         } else {
0138             $this->prefixesPsr0[$first][$prefix] = array_merge(
0139                 $this->prefixesPsr0[$first][$prefix],
0140                 (array) $paths
0141             );
0142         }
0143     }
0144 
0145     /**
0146      * Registers a set of PSR-4 directories for a given namespace, either
0147      * appending or prepending to the ones previously set for this namespace.
0148      *
0149      * @param string       $prefix  The prefix/namespace, with trailing '\\'
0150      * @param array|string $paths   The PSR-4 base directories
0151      * @param bool         $prepend Whether to prepend the directories
0152      *
0153      * @throws \InvalidArgumentException
0154      */
0155     public function addPsr4($prefix, $paths, $prepend = false)
0156     {
0157         if (!$prefix) {
0158             // Register directories for the root namespace.
0159             if ($prepend) {
0160                 $this->fallbackDirsPsr4 = array_merge(
0161                     (array) $paths,
0162                     $this->fallbackDirsPsr4
0163                 );
0164             } else {
0165                 $this->fallbackDirsPsr4 = array_merge(
0166                     $this->fallbackDirsPsr4,
0167                     (array) $paths
0168                 );
0169             }
0170         } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
0171             // Register directories for a new namespace.
0172             $length = strlen($prefix);
0173             if ('\\' !== $prefix[$length - 1]) {
0174                 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
0175             }
0176             $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
0177             $this->prefixDirsPsr4[$prefix] = (array) $paths;
0178         } elseif ($prepend) {
0179             // Prepend directories for an already registered namespace.
0180             $this->prefixDirsPsr4[$prefix] = array_merge(
0181                 (array) $paths,
0182                 $this->prefixDirsPsr4[$prefix]
0183             );
0184         } else {
0185             // Append directories for an already registered namespace.
0186             $this->prefixDirsPsr4[$prefix] = array_merge(
0187                 $this->prefixDirsPsr4[$prefix],
0188                 (array) $paths
0189             );
0190         }
0191     }
0192 
0193     /**
0194      * Registers a set of PSR-0 directories for a given prefix,
0195      * replacing any others previously set for this prefix.
0196      *
0197      * @param string       $prefix The prefix
0198      * @param array|string $paths  The PSR-0 base directories
0199      */
0200     public function set($prefix, $paths)
0201     {
0202         if (!$prefix) {
0203             $this->fallbackDirsPsr0 = (array) $paths;
0204         } else {
0205             $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
0206         }
0207     }
0208 
0209     /**
0210      * Registers a set of PSR-4 directories for a given namespace,
0211      * replacing any others previously set for this namespace.
0212      *
0213      * @param string       $prefix The prefix/namespace, with trailing '\\'
0214      * @param array|string $paths  The PSR-4 base directories
0215      *
0216      * @throws \InvalidArgumentException
0217      */
0218     public function setPsr4($prefix, $paths)
0219     {
0220         if (!$prefix) {
0221             $this->fallbackDirsPsr4 = (array) $paths;
0222         } else {
0223             $length = strlen($prefix);
0224             if ('\\' !== $prefix[$length - 1]) {
0225                 throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
0226             }
0227             $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
0228             $this->prefixDirsPsr4[$prefix] = (array) $paths;
0229         }
0230     }
0231 
0232     /**
0233      * Turns on searching the include path for class files.
0234      *
0235      * @param bool $useIncludePath
0236      */
0237     public function setUseIncludePath($useIncludePath)
0238     {
0239         $this->useIncludePath = $useIncludePath;
0240     }
0241 
0242     /**
0243      * Can be used to check if the autoloader uses the include path to check
0244      * for classes.
0245      *
0246      * @return bool
0247      */
0248     public function getUseIncludePath()
0249     {
0250         return $this->useIncludePath;
0251     }
0252 
0253     /**
0254      * Turns off searching the prefix and fallback directories for classes
0255      * that have not been registered with the class map.
0256      *
0257      * @param bool $classMapAuthoritative
0258      */
0259     public function setClassMapAuthoritative($classMapAuthoritative)
0260     {
0261         $this->classMapAuthoritative = $classMapAuthoritative;
0262     }
0263 
0264     /**
0265      * Should class lookup fail if not found in the current class map?
0266      *
0267      * @return bool
0268      */
0269     public function isClassMapAuthoritative()
0270     {
0271         return $this->classMapAuthoritative;
0272     }
0273 
0274     /**
0275      * Registers this instance as an autoloader.
0276      *
0277      * @param bool $prepend Whether to prepend the autoloader or not
0278      */
0279     public function register($prepend = false)
0280     {
0281         spl_autoload_register(array($this, 'loadClass'), true, $prepend);
0282     }
0283 
0284     /**
0285      * Unregisters this instance as an autoloader.
0286      */
0287     public function unregister()
0288     {
0289         spl_autoload_unregister(array($this, 'loadClass'));
0290     }
0291 
0292     /**
0293      * Loads the given class or interface.
0294      *
0295      * @param  string    $class The name of the class
0296      * @return bool|null True if loaded, null otherwise
0297      */
0298     public function loadClass($class)
0299     {
0300         if ($file = $this->findFile($class)) {
0301             includeFile($file);
0302 
0303             return true;
0304         }
0305     }
0306 
0307     /**
0308      * Finds the path to the file where the class is defined.
0309      *
0310      * @param string $class The name of the class
0311      *
0312      * @return string|false The path if found, false otherwise
0313      */
0314     public function findFile($class)
0315     {
0316         // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
0317         if ('\\' == $class[0]) {
0318             $class = substr($class, 1);
0319         }
0320 
0321         // class map lookup
0322         if (isset($this->classMap[$class])) {
0323             return $this->classMap[$class];
0324         }
0325         if ($this->classMapAuthoritative) {
0326             return false;
0327         }
0328 
0329         $file = $this->findFileWithExtension($class, '.php');
0330 
0331         // Search for Hack files if we are running on HHVM
0332         if ($file === null && defined('HHVM_VERSION')) {
0333             $file = $this->findFileWithExtension($class, '.hh');
0334         }
0335 
0336         if ($file === null) {
0337             // Remember that this class does not exist.
0338             return $this->classMap[$class] = false;
0339         }
0340 
0341         return $file;
0342     }
0343 
0344     private function findFileWithExtension($class, $ext)
0345     {
0346         // PSR-4 lookup
0347         $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
0348 
0349         $first = $class[0];
0350         if (isset($this->prefixLengthsPsr4[$first])) {
0351             foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
0352                 if (0 === strpos($class, $prefix)) {
0353                     foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
0354                         if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
0355                             return $file;
0356                         }
0357                     }
0358                 }
0359             }
0360         }
0361 
0362         // PSR-4 fallback dirs
0363         foreach ($this->fallbackDirsPsr4 as $dir) {
0364             if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
0365                 return $file;
0366             }
0367         }
0368 
0369         // PSR-0 lookup
0370         if (false !== $pos = strrpos($class, '\\')) {
0371             // namespaced class name
0372             $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
0373                 . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
0374         } else {
0375             // PEAR-like class name
0376             $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
0377         }
0378 
0379         if (isset($this->prefixesPsr0[$first])) {
0380             foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
0381                 if (0 === strpos($class, $prefix)) {
0382                     foreach ($dirs as $dir) {
0383                         if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
0384                             return $file;
0385                         }
0386                     }
0387                 }
0388             }
0389         }
0390 
0391         // PSR-0 fallback dirs
0392         foreach ($this->fallbackDirsPsr0 as $dir) {
0393             if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
0394                 return $file;
0395             }
0396         }
0397 
0398         // PSR-0 include paths.
0399         if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
0400             return $file;
0401         }
0402     }
0403 }
0404 
0405 /**
0406  * Scope isolated include.
0407  *
0408  * Prevents access to $this/self from included files.
0409  */
0410 function includeFile($file)
0411 {
0412     include $file;
0413 }