简单分析composer如何自动加载文件
1、使用composer init 进行初始化,如下图所示:


目录结构如图所示:

2、从vendor/autoload.php开始分析
我们发现autoload.php文件引入了vendor/composer/autoload_real.php文件
= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());if ($useStaticLoader) {require __DIR__ . '/autoload_static.php';call_user_func(\Composer\Autoload\ComposerStaticInit6123050cbfe39c30c2c64e101f1dfb9c::getInitializer($loader));} else {$map = require __DIR__ . '/autoload_namespaces.php';foreach ($map as $namespace => $path) {$loader->set($namespace, $path);}$map = require __DIR__ . '/autoload_psr4.php';foreach ($map as $namespace => $path) {$loader->setPsr4($namespace, $path);}$classMap = require __DIR__ . '/autoload_classmap.php';if ($classMap) {$loader->addClassMap($classMap);}}$loader->register(true);return $loader;}
}
3、从getLoader方法开始分析如何加载类文件
spl_autoload_register(
$autoload_function,$throw=true,$prepend= false)函数
autoload_function:欲注册的自动装载函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数spl_autoload()。
throw:此参数设置了autoload_function无法成功注册时, spl_autoload_register()是否抛出异常。
prepend:如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。详见:PHP: spl_autoload_register - Manual
所以,这段代码在注册ComposerAutoloaderInitxxx的loadClassLoader方法
spl_autoload_register(array('ComposerAutoloaderInit6123050cbfe39c30c2c64e101f1dfb9c', 'loadClassLoader'), true, true);
其中loadClassLoader($class)方法和spl_autoload_register给的示例代码类似(如下图所示),主要是加载ClassLoader.php文件

// 对下面这行代码做简单说明:// 实例化\Composer\Autoload\ClassLoader这个的时候,
// 因为已经注册了loadClassLoader方法,所以会自动加载ClassLoader.php文件。// \dirname(\dirname(__FILE__))即为vendor目录self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));// 注销已注册的 __autoload() 函数
spl_autoload_unregister(array('ComposerAutoloaderInit6123050cbfe39c30c2c64e101f1dfb9c', 'loadClassLoader'));
判断是否使用静态加载
// PHP_VERSION_ID:php版本
// HHVM_VERSION:
// zend_loader_file_encoded:// 从单词的意思上,应该是在判断是否使用静态加载
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
如果$useStaticLoader为true,加载autoload_static.php文件;否则,加载autoload_namespaces.php、autoload_psr4.php、autoload_classmap.php文件。
最后执行调用注册方法
// 调用\Composer\Autoload\ClassLoader下的register方法
// self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));$loader->register(true);
4、分析ClassLoader.php
在autoload_real.php,实例化Composer\Autoload\ClassLoader类时,自动加载类文件
// 类似_autoload($class)的用法,然后加载类文件
public static function loadClassLoader($class)
{if ('Composer\Autoload\ClassLoader' === $class) {// 加载ClassLoader.php类文件require __DIR__ . '/ClassLoader.php';}
}
由于ClassLoader.php代码有点多,找主要的分析一下:
set方法
public function set($prefix, $paths)
{if (!$prefix) {$this->fallbackDirsPsr0 = (array) $paths;} else {$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;}
}
setPsr4方法
public function setPsr4($prefix, $paths)
{if (!$prefix) {$this->fallbackDirsPsr4 = (array) $paths;} else {// 获取命名空间的长度$length = strlen($prefix);// 判断是否以\结尾if ('\\' !== $prefix[$length - 1]) {throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");}// $prefix[0]是命名空间的第一个字符$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;$this->prefixDirsPsr4[$prefix] = (array) $paths;}
}
addClassMap方法
public function addClassMap(array $classMap)
{if ($this->classMap) {$this->classMap = array_merge($this->classMap, $classMap);} else {$this->classMap = $classMap;}
}
register方法
public function register($prepend = false)
{// 自动注册当前类的loadClass方法spl_autoload_register(array($this, 'loadClass'), true, $prepend);// 判断vendorDir是否为nullif (null === $this->vendorDir) {return;}if ($prepend) {// 注意:区分 array_merge 和 + 的数组操作self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;} else {unset(self::$registeredLoaders[$this->vendorDir]);self::$registeredLoaders[$this->vendorDir] = $this;}
}
其中spl_autoload_register(array($this, 'loadClass'), true, $prepend)注册了loadClass方法
当实例化其他类的时候(比如我们使用composer require xxx,然后实例化xxx类),会调用loadClass方法,然后加载相关文件
// 类似autoload_real.php文件中的loadClassLoader方法public function loadClass($class)
{if ($file = $this->findFile($class)) {includeFile($file); // include $file;return true;}return null;
}
我们再来看看findFile方法
public function findFile($class)
{// class map lookup// 首先查找classMap数组中是否有这个 类=>文件路径 ,存在就直接返回if (isset($this->classMap[$class])) {return $this->classMap[$class];}if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {return false;}if (null !== $this->apcuPrefix) {$file = apcu_fetch($this->apcuPrefix.$class, $hit);if ($hit) {return $file;}}$file = $this->findFileWithExtension($class, '.php');// Search for Hack files if we are running on HHVMif (false === $file && defined('HHVM_VERSION')) {$file = $this->findFileWithExtension($class, '.hh');}if (null !== $this->apcuPrefix) {apcu_add($this->apcuPrefix.$class, $file);}if (false === $file) {// Remember that this class does not exist.$this->missingClasses[$class] = true;}return $file;
}
findFileWithExtension方法,主要找到对应文件,并判断文件是否存在
private function findFileWithExtension($class, $ext)
{// PSR-4 lookup// strtr转换指定字符,将 \\ 转换成文件分隔符 \// 比如:$class = 'Demo\Demo\Test';// $logicalPathPsr4 = 'Demo\Demo\Test.php';$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;// 获取类名的第一个字符$first = $class[0];if (isset($this->prefixLengthsPsr4[$first])) {$subPath = $class;// 判断类名是否含有\\while (false !== $lastPos = strrpos($subPath, '\\')) {$subPath = substr($subPath, 0, $lastPos);$search = $subPath . '\\';if (isset($this->prefixDirsPsr4[$search])) {$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);foreach ($this->prefixDirsPsr4[$search] as $dir) {if (file_exists($file = $dir . $pathEnd)) {return $file;}}}}}// PSR-4 fallback dirsforeach ($this->fallbackDirsPsr4 as $dir) {// 处理文件路径 并 判断文件是否存在if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {return $file;}}// PSR-0 lookup// 判断类名是否含有\\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// strtr转换指定字符,将 _ 转换成文件分隔符 \$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 dirsforeach ($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;}return false;
}
在autoload_real.php打印$loader的属性信息
register(true);// 打印以下内容
// psr0
var_dump($loader->getPrefixes(),$loader->getFallbackDirs());// psr4
var_dump($loader->getPrefixesPsr4(),$loader->getFallbackDirsPsr4());// 直接添加 '命名空间+类名'=>'文件路径'
var_dump($loader->getClassMap());return $loader;
打印信息如图所示:

5、分析autoload_static.php
array('命名空间+类名' => '命名空间+类名'字符长度)public static $prefixLengthsPsr4 = array ('D' => array ('Demo\\Demo\\' => 10,),);// '命名空间+类名' 对应 目录public static $prefixDirsPsr4 = array ('Demo\\Demo\\' => array (0 => __DIR__ . '/../..' . '/src',),);// '命名空间+类名' 对应 文件public static $classMap = array ('Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',);public static function getInitializer(ClassLoader $loader){return \Closure::bind(function () use ($loader) {$loader->prefixLengthsPsr4 = ComposerStaticInit6123050cbfe39c30c2c64e101f1dfb9c::$prefixLengthsPsr4;$loader->prefixDirsPsr4 = ComposerStaticInit6123050cbfe39c30c2c64e101f1dfb9c::$prefixDirsPsr4;$loader->classMap = ComposerStaticInit6123050cbfe39c30c2c64e101f1dfb9c::$classMap;}, null, ClassLoader::class);}
}
Closure::bind复制一个闭包,绑定指定的$this对象和类作用域。
closure:需要绑定的匿名函数。
newThis:需要绑定到匿名函数的对象,或者null创建未绑定的闭包。
newScope:想要绑定给闭包的类作用域,或者 'static' 表示不改变。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。 详见:PHP: Closure::bind - Manual
在autoload_real.php文件中,通过call_user_func来调用getInitializer方法
// autoload_real.php @generated by Composerrequire __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit6123050cbfe39c30c2c64e101f1dfb9c::getInitializer($loader));
call_user_func把第一个参数作为回调函数调用
callback:将被调用的回调函数(callable)。
parameter:0个或以上的参数,被传入回调函数。详见:PHP: call_user_func - Manual
6、分析autoload_namespaces.php(可以直接跳过)
在autoload_real.php文件中,接收autoload_namespaces.php返回值,并进行循环遍历,处理命名空间与文件路径
// autoload_real.php @generated by Composer$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {$loader->set($namespace, $path);
}
7、分析autoload_psr4.php(可以直接跳过)
array($baseDir . '/src'),
);
在autoload_real.php文件中,接收autoload_psr4.php返回值,并进行循环遍历,处理命名空间与文件路径
// autoload_real.php @generated by Composer$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {$loader->setPsr4($namespace, $path);
}
8、分析autoload_classmap.php(可以直接跳过)
$vendorDir . '/composer/InstalledVersions.php',
);
在autoload_real.php文件中,接收autoload_classmap.php返回值
// autoload_real.php @generated by Composer$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {$loader->addClassMap($classMap);
}
我们发现并没有InstalledVersions.php这个文件,所以我们需要执行composer udpate

InstalledVersions.php文件处理 installed.php 等相关信息, installed.php内容如下:
array('pretty_version' => '1.0.0+no-version-set','version' => '1.0.0.0','type' => 'library','install_path' => __DIR__ . '/../../','aliases' => array(),'reference' => NULL,'name' => 'demo/demo','dev' => true,),'versions' => array('demo/demo' => array('pretty_version' => '1.0.0+no-version-set','version' => '1.0.0.0','type' => 'library','install_path' => __DIR__ . '/../../','aliases' => array(),'reference' => NULL,'dev_requirement' => false,),),
);
以上是简单分析了下composer如何加载文件,有些不清楚的地方还是需要编写相关代码,添加断点等方式(比如在源码中var_dump打印结果)去查看运行结果更加直观。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
