现在很多主流框架都用到了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流程都分析完毕,小弟才疏学浅,若有哪里不足,欢迎指正!!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。