composer的自动加载机制解读

按照composer文档的说法,如果是composer项目,只需要在开始的时候require 'vendor/autoload.php'即可享受类的自动加载特性。可是这是如何实现的呢?

vendor/autoload.php

以Laravel 5.1项目为例,vendor/autoload.php文件只做了两件事情:

  1. include vendor/composer/autoload_real.php
  2. 调用ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()

vendor/composer/autoload_real.php仅仅定义了ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b类和composerRequireb6d254015e39cf5090fb84fdb1ed664b函数。(b6d254015e39cf5090fb84fdb1ed664b应该是类似id一样的东西,确保每次不同) 接下来我们关注下ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()做了哪些事情。

ComposerAutoloaderInit<id>::getLoader()

首先,这个类的loader只会初始化一次,第二次是直接返回已经存在的loader了:

1
2
3
if (null !== self::$loader) {
return self::$loader;
}

如果是第一次调用,先注册['ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b', 'loadClassLoader'],然后new一个\Composer\Autoload\ClassLoader 作为loader,然后立马取消注册loadClassLoader。 接下来就一步一步处理各种autoload了。

autoload_namespaces.php

直接从vendor/composer/autoload_namespaces.php中返回的就是$namespace$path的一个数组。通过\Composer\Autoload\ClassLoader::set方法来记录这些信息:

1
2
3
4
5
6
7
8
if (!$prefix) {
// 如果namespace/prefix为空,则在其他PSR-0规则中查不到,才会到这个规则中
$this->fallbackDirsPsr0 = (array) $paths;
} else {
// 以后根据对应的namespace/prefix查找类的时候,第一个字母用来加速
// 使用第一个字母作为一级key,可以减少二级key的冲突
$this->prefixesPsr0\[$prefix\[0\]\]\[$prefix\] = (array) $paths;
}

autoload_psr4.php

直接从该文件的返回值是$namespace$path的数组。通过\Composer\Autoload\ClassLoader::setPsr4来记录此信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (!$prefix) {
// 如果namespace/prefix为空,则其他PSR-4规则查找不到的时候,会到此规则中查找
$this->fallbackDirsPsr4 = (array) $paths;
} else {
// TODO
$length = strlen($prefix);
// 略去错误检查

// TODO
$this->prefixLengthsPsr4\[$prefix\[0\]\]\[$prefix\] = $length;
// 用来根据类前缀找path
$this->prefixDirsPsr4\[$prefix\] = (array) $paths;
}

autoload_classmap.php

直接从该文件的返回值是$classname$path的数组。通过\Composer\Autoload\ClassLoader::addClassMap来记录此信息:

1
2
3
4
5
if ($this->classMap) {
$this->classMap = array\_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}

这些都处理完之后,\Composer\Autoload\ClassLoader::register[$this, 'loadClass']注册为autoload函数(通过spl_autoload_register)。

autoload_files.php

直接从该文件的返回值是$fileIdentifier到文件路径的映射,通过之前定义的composerRequireb6d254015e39cf5090fb84fdb1ed664b函数来require每个文件。 这个函数在require文件的时候同时也设置了$GLOBALS['__composer_autoload_files'][$fileIdentifier]的值为true。

\Composer\Autoload\ClassLoader::loadClass

这个函数的作用是装载一个类。

1
2
3
4
5
if ($file = $this->findFile($class)) {
includeFile($file);

return true;
}

\Composer\Autoload\ClassLoader::findFile

这个函数是composer装载类的重点。

  1. 首先是从classMap里面找$class对应的文件,如果有,直接返回。
  2. 然后从prefixDirsPsr4找到前缀符合的文件,如果找到,直接返回。(那个prefixLengthsPsr4就是用来判断需要从$class去掉的前缀长度)
  3. 接下来,直接从fallbackDirsPsr4对应的目录中查找文件。
  4. PSR-0加载
    • namespaced class name变换规则:\A_namespace\a_class=>/A_namespace/a/class.php
    • PEAR-like class name变换规则:\A_namespace\a_class=>/A/namespace/a/class.php
    prefixesPsr0中查找(和prefixDirsPsr4类似),直接找到对应的文件,返回。
  5. 如果没有,直接从fallbackDirsPsr0中尝试加载。

composer的自动加载机制解读

https://robberphex.com/autoload-with-composer/

作者

Robert Lu

发布于

2017-01-15

许可协议

评论