您好,登錄后才能下訂單哦!
現(xiàn)在很多主流框架都用到了composer,包管理實(shí)在是方便?,F(xiàn)在我就以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(這項(xiàng)最重要) //分析見解釋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'), );
很明顯僅僅返回一個數(shù)組
再看看這語句含義
$loader->set($namespace, $path);
追蹤進(jìn)去,調(diào)用的是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'), );
這個文件看過來,發(fā)現(xiàn)和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(注意這里長度計(jì)算實(shí)際為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', );
經(jīng)過上面分析,現(xiàn)在我們直接來看ClassLoader的addClassMap方法:
public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /* 方法執(zhí)行完 $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
關(guān)鍵代碼$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); }
看完實(shí)際上就是通過當(dāng)前類的loadClass注冊了,假如new yii\mongodb\Connection找不到的時候就會調(diào)用ClassLoader的loadClass方法,于是我們看看loadClass方法
if ($file = $this->findFile($class)) { includeFile($file); return true; }
看完繼續(xù)打開這個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默認(rèn)為false if ($this->classMapAuthoritative) { return false; } $file = $this->findFileWithExtension($class, '.php'); // 如果運(yùn)行在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肯定會進(jìn)入到這行
$file = $this->findFileWithExtension($class, '.php'); //實(shí)際上$this->findFileWithExtension('yii\\mongodb\\Connection.php')
于是我們繼續(xù)讀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設(shè)置過此數(shù)組于是找到了 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', );
再分析調(diào)用語句
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流程都分析完畢,小弟才疏學(xué)淺,若有哪里不足,歡迎指正!!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。