现在很多主流框架都用到了composer,包管理实在是方便。现在我就以yii2来举例追踪一遍composer autoload流程
专注于为中小企业提供成都网站制作、网站设计、外贸网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业霍尔果斯免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了成百上千家企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
第一步上yii2的web/index.php(入口文件)
run();
看到第五行,有引入vendor/autoload.php,于是我打开这个文件:
打开这个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
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
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
$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文件是什么鬼
$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流程都分析完毕,小弟才疏学浅,若有哪里不足,欢迎指正!!
新闻名称:composerautoload源码分析
文章地址:http://cxhlcq.com/article/jphjji.html