ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

ClassLoader.php (13451B)


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