温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

composer autoload源码分析

发布时间:2020-09-11 11:04:31 来源:网络 阅读:1777 作者:china_lx1 栏目:web开发

现在很多主流框架都用到了composer,包管理实在是方便。现在我就以yii2来举例追踪一遍composer autoload流程

第一步上yii2的web/index.php(入口文件)

<?php defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); require __DIR__ . '/../vendor/autoload.php';     require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php'; $config = require __DIR__ . '/../config/web.php'; (new yii\web\Application($config))->run();


看到第五行,有引入vendor/autoload.php,于是我打开这个文件:

<?php // autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e::getLoader();


打开这个vendor/composer/autoload_real.php,找到ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e这个类的getLoader方法

public static function getLoader() {     if (null !== self::$loader) {         return self::$loader;     }     spl_autoload_register(array('ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e', 'loadClassLoader'), true, true);     self::$loader = $loader = new \Composer\Autoload\ClassLoader();     spl_autoload_unregister(array('ComposerAutoloaderInitc9edea7c33c2cce60a5a807424f71d3e', 'loadClassLoader'));     //psr0  分析见解释1-1     $map = require __DIR__ . '/autoload_namespaces.php';     foreach ($map as $namespace => $path) {         $loader->set($namespace, $path);     }          //psr4(这项最重要)    //分析见解释1-2     $map = require __DIR__ . '/autoload_psr4.php';      foreach ($map as $namespace => $path) {         $loader->setPsr4($namespace, $path);         }          //分析见解释1-3     $classMap = require __DIR__ . '/autoload_classmap.php';     if ($classMap) {         $loader->addClassMap($classMap);     }          $loader->register(true);    //分析见解释1-4          //分析见解释1-5     $includeFiles = require __DIR__ . '/autoload_files.php';     foreach ($includeFiles as $fileIdentifier => $file) {         composerRequired991c576f7683adde3c22cdd0fe508de($fileIdentifier, $file);     }          return $loader; }


解释1-1

首先来看看这个autoload_namespaces.php

<?php // autoload_namespaces.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array(    'Imagine' => array($vendorDir . '/imagine/imagine/lib'),     'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),     'Diff' => array($vendorDir . '/phpspec/php-diff/lib'), );

很明显仅仅返回一个数组

再看看这语句含义

$loader->set($namespace, $path);

追踪进去,调用的是ClassLoader的set方法:

public function set($prefix, $paths)     //以$prefix='Imagine', $paths = array($vendorDir . '/imagine/imagine/lib')来举例 {     if (!$prefix) {         $this->fallbackDirsPsr0 = (array) $paths;     } else {         $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;         //得到$this->prefixesPsr0['I']['Imagine'] = array($vendorDir . '/imagine/imagine/lib')     } }


解释1-2

打开vendor/composer/autoload_psr4.php

<?php // autoload_psr4.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array(     'yii\\swiftmailer\\' => array($vendorDir . '/yiisoft/yii2-swiftmailer'),     'yii\\redis\\' => array($vendorDir . '/yiisoft/yii2-redis'),     'yii\\mongodb\\' => array($vendorDir . '/yiisoft/yii2-mongodb'), );

这个文件看过来,发现和psr0的namespace.php很像,只是这里加了命名空间

和psr0一样,我们来看看这行

$loader->setPsr4($namespace, $path);

打开ClassLoader的setPsr4方法

public function setPsr4($prefix, $paths) //以$prefix='yii\\mongodb\\', $paths = array($vendorDir . '/yiisoft/yii2-mongodb')来举例 {     if (!$prefix) {         $this->fallbackDirsPsr4 = (array) $paths;     } else {             //字符串'yii\\mongodb\\'长度为12(注意这里长度计算实际为yii\mongodb\)         $length = strlen($prefix);                     if ('\\' !== $prefix[$length - 1]) {             throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");         }         //$this->prefixLengthsPsr4['y']['yii\\mongodb\\'] = 12;         $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;         //$this->prefixLengthsPsr4['y']['yii\\mongodb\\'] = array($vendorDir . '/yiisoft/yii2-mongodb')         $this->prefixDirsPsr4[$prefix] = (array) $paths;         } }


解释1-3(类名和类文件具体路径直接映射表)

打开autoload_classmap.php

<?php // autoload_classmap.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array(     'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php',     'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php',     'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', );

经过上面分析,现在我们直接来看ClassLoader的addClassMap方法:

public function addClassMap(array $classMap) {     if ($this->classMap) {         $this->classMap = array_merge($this->classMap, $classMap);     } else {         $this->classMap = $classMap;     } } /* 方法执行完 $this->classMap = [     'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php',     'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php',     'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', ]; */


解释1-4

关键代码$loader->register(true);

打开loader的register方法

/**  * Registers this instance as an autoloader.  *  * @param bool $prepend Whether to prepend the autoloader or not  */ public function register($prepend = false) {     spl_autoload_register(array($this, 'loadClass'), true, $prepend); }

看完实际上就是通过当前类的loadClass注册了,假如new yii\mongodb\Connection找不到的时候就会调用ClassLoader的loadClass方法,于是我们看看loadClass方法

if ($file = $this->findFile($class)) {     includeFile($file);     return true; }

看完继续打开这个findFile方法

public function findFile($class) {     // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731     if ('\\' == $class[0]) {         $class = substr($class, 1);     }     // 这里解释1-3解释过了     if (isset($this->classMap[$class])) {         return $this->classMap[$class];     }     //$this->classMapAuthoritative默认为false     if ($this->classMapAuthoritative) {         return false;     }     $file = $this->findFileWithExtension($class, '.php');     // 如果运行在HHVM     if ($file === null && defined('HHVM_VERSION')) {         $file = $this->findFileWithExtension($class, '.hh');     }     if ($file === null) {         //记住这个类不存在         return $this->classMap[$class] = false;     }     return $file; }

这个方法都好理解,假如是new yii\mongodb\Connection肯定会进入到这行

$file = $this->findFileWithExtension($class, '.php');    //实际上$this->findFileWithExtension('yii\\mongodb\\Connection.php')

于是我们继续读findFileWithExtension方法

private function findFileWithExtension($class, $ext)    //举例$class = 'yii\\mongodb\\Connection', $ext = '.php' {     // PSR-4寻找     //会得到$logicalPathPsr4 = 'yii/mongodb/Connection.php';     $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;     $first = $class[0];     //由于我们之前setPsr4设置过此数组于是找到了     if (isset($this->prefixLengthsPsr4[$first])) {                 //$length 为字符串'yii\\mongodb\\'长度         foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {                 if (0 === strpos($class, $prefix)) {                 foreach ($this->prefixDirsPsr4[$prefix] as $dir) {                     //$dir为autoload_psr4.php映射的路径,substr($logicalPathPsr4, $length)                     //刚好可以得到Connection.php                     //最终得到$file完整路径                     if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {                         return $file;                     }                 }             }         }     }     // PSR-4 fallback dirs     foreach ($this->fallbackDirsPsr4 as $dir) {         if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {             return $file;         }     }     // PSR-0同理     if (false !== $pos = strrpos($class, '\\')) {         // namespaced class name         $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)             . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);     } else {         // PEAR-like class name         $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;     }     if (isset($this->prefixesPsr0[$first])) {         foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {             if (0 === strpos($class, $prefix)) {                 foreach ($dirs as $dir) {                     if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {                         return $file;                     }                 }             }         }     }     // PSR-0 fallback dirs     foreach ($this->fallbackDirsPsr0 as $dir) {         if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {             return $file;         }     }     // PSR-0 include paths.     if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {         return $file;     } }

通过这个方法找到类文件的完整路径,最后只需要include($file)即可,到此composer autoload自动加载基本完毕


解释1-5

我们还是先看看autoload_files.php文件是什么鬼

<?php // autoload_files.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array(     '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',     '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', );

再分析调用语句

composerRequired991c576f7683adde3c22cdd0fe508de($fileIdentifier, $file);

再看看这个方法:

function composerRequired991c576f7683adde3c22cdd0fe508de($fileIdentifier, $file)     /* $fileIdentifier = '2cffec82183ee1cea088009cef9a6fc3' $file = $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php' */ {     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {         require $file;         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;     } }


到此,整个composer autoload流程都分析完毕,小弟才疏学浅,若有哪里不足,欢迎指正!!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI