这里要介绍的不是composer.phar,而且由php composer.phar install生成的整一个composer文件下的autoload的结构。
想在项目总引入composer进行包管理,都会说将需要的包填到composer.json文件,然后执行composer install,会在vender文件夹下生成autoload.php,项目中引入这个文件就可以实现类的自动加载了。
那引入autoload.php之后到底做了帮我们做了什么,我们一起来看看。
首先查看autoload.php:
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee::getLoader();
这里是require了一个compsoer/autoload_real.php文件,然后返回了一个getLoader()静态方法,看来我们要进去这个方法里看一下。
(ps: autoload_real.php 这个名字中也能看出,这才是真正实现autoload机制的地方)
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
//如果成员$loader已经赋值就直接返回,典型的单例模式
if (null !== self::$loader) {
return self::$loader;
}
//将ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee类中的loadClassLoader方法注册为自动类加载处理函数
spl_autoload_register(array('ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee', 'loadClassLoader'), true, true);
//使用新注册的loadClassLoader方法加载\Composer\Autoload\ClassLoader类,并实例化一个对象
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
//注销loadClassLoader
spl_autoload_unregister(array('ComposerAutoloaderInit621850f9ac0e7f3f7c90ee4affc93eee', 'loadClassLoader'));
//如果php版本大于5.6并且未定义HHVM_VERSION常量,返回true
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
if ($useStaticLoader) {
//autoload_static.php 记录了namespace+类名与类所在文件的映射关系
require_once __DIR__ . '/autoload_static.php';
//降映射关系加载到$loader里
call_user_func(\Composer\Autoload\ComposerStaticInit621850f9ac0e7f3f7c90ee4affc93eee::getInitializer($loader));
} else {
//设置单独namespace对于的文件夹位置的映射关系
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
//符合psr4命名规范的namespace对于的文件夹位置的映射关系
$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);
//需要预先require的文件
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit621850f9ac0e7f3f7c90ee4affc93eee::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire621850f9ac0e7f3f7c90ee4affc93eee($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire621850f9ac0e7f3f7c90ee4affc93eee($fileIdentifier, $file)
{
//循环导入文件,并记录到$GLOBALS['__composer_autoload_files']变量中
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
到这里,对autoload的初始化就完成了,接下来是对一个类的查找工作。
在composer/ClassLoader.php
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
使用本类中的loadClass()方法来处理未找到的类:
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
这方法就是先查找该类是否存在,存在则include进来。再看看findFile()方法是如何查找并返回类的:
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
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);
}
// class map lookup
//在$this->classMap中查找$class,找到就返回文件名
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
//是否有权利访问classMap
if ($this->classMapAuthoritative) {
return false;
}
//上面没匹配到,这里再匹配
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if ($file === null && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if ($file === null) {
// Remember that this class does not exist.
return $this->classMap[$class] = false;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
//将\\替换成/
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
//取类的首字母
$first = $class[0];
//prefixLengthsPsr4中根据首字母进行了分类,可查看autoload_static.php中的prefixLengthsPsr4成员变量
if (isset($this->prefixLengthsPsr4[$first])) {
foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
if (0 === strpos($class, $prefix)) {
foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
//自定义的psr4规则
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
//如果是符合psr0规则的,则替换_为/
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;
}
//prefixesPsr0中根据首字母进行了分类,可查看autoload_static.php中的prefixesPsr0成员变量
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
//用户添加符合psr0的规则
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;
}
}
顺序是先查classMap,因为大部分类的映射关系都在这个变量里,然后下面findFileWithExtension()方法里是以一些其他维度进行查询。
整体class的autoload的init和find就是这样,好像主要的工作还是在php composer.phar install的时候已经完成了,这个时候就已经理出了类名和类所在文件的映射关系。