composer 自动加载
2017-07-18
composer 自动加载
入口
在laravel项目中,所有的依赖包都存在于vendor目录下,对应的类自动加载源码也存在于vendor目录中(由入口文件public/index.php可追踪到),首先打开目录我们可以在最外层找到 autoload.php
对应代码如下
// 引入了真正的aotuload
require_once __DIR__ . '/composer/autoload_real.php';
// 调用了autoload核心函数
return ComposerAutoloaderInit9a624ef1199d9f899a6118c386c86aa3::getLoader();
核心
追踪到autoload.php时,可以找到自动加载最核心的代码 getLoader
,可以通过以下问题来理清自动加载的流程:
自动加载的规则
vendor中的每个依赖都有composer.json文件,其中
autoload
这个字段告诉composer该依赖遵循了php哪种自动加载的规则,使得composer可以将它的规则写入对应的文件中以实现自动加载。目前composer中支持四种规则分别为psr-0
、psr-4
、classmap
、files
。
源码分析
- classmap
// 以hamcrest/hamcrest-php为例
// composer.json 中定义了该插件使用了classmap的规则
{
"autoload": {
"classmap": ["hamcrest"],
"files": ["hamcrest/Hamcrest.php"]
},
}
// 目录结构
vendor
└── hamcrest
└── hamcrest-php
├──hamcrest
├──Hamcrest
├── Arrays
│ ├── IsArray.php
│ ├── IsArrayContaining.php
│ ├── IsArrayContainingInAnyOrder.php
...
// 对应 autoload_static.php中展现
// classmap
public static $classMap = array (
'Hamcrest\\Arrays\\IsArray' => __DIR__ . '/..' . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArray.php',
'Hamcrest\\Arrays\\IsArrayContaining' => __DIR__ . '/..' . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContaining.php',
'Hamcrest\\Arrays\\IsArrayContainingInAnyOrder' => __DIR__ . '/..' . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContainingInAnyOrder.php',
)
- psr-0
// 以jakub-onderka/php-console-color为例
// composer.json 中定义了该插件使用了psr-0的规则
{
"autoload": {
"psr-0": {"JakubOnderka\\PhpConsoleColor": "src/"}
}
}
// 目录结构
vendor
└── jakub-onderka
└── php-console-color
├── src
│ └── JakubOnderka
│ └── PhpConsoleColor
│ └── ConsoleColor.php
└── tests
└── JakubOnderka
└── PhpConsoleColor
└── ConsoleColorTest.php
// 对应 autoload_static.php中展现
// psr-0
public static $prefixesPsr0 = array (
'J' =>
array (
'JakubOnderka\\PhpConsoleColor' =>
array (
0 => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src',
),
),
)
// classmap
public static $classMap = array (
'JakubOnderka\\PhpConsoleColor\\ConsoleColor' => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src/JakubOnderka/PhpConsoleColor/ConsoleColor.php',
)
- psr-4
// 以mongodb/mongodb为例
// composer.json 中定义了该插件使用了psr-0的规则
{
"autoload": {
"psr-4": { "MongoDB\\": "src/" },
"files": [ "src/functions.php" ] // files 为全局引用文件注册
}
}
// 目录结构
vendor
└── mongodb
└── mongodb
├── src
│ └── ClassName.php
└── tests
└── Test.php
// 对应 autoload_static.php中展现
// psr-4
public static $prefixLengthsPsr4 = array(
array (
'MongoDB\\' => 8,
),
)
public static $prefixDirsPsr4 = array (
array (
0 => __DIR__ . '/..' . '/mongodb/mongodb/src',
),
)
// classmap
public static $classMap = array (
'MongoDB\\Client' => __DIR__ . '/..' . '/mongodb/mongodb/src/Client.php',
)
- file
// 单个file的引入 在之前psr-4中出现过一个例子,在这里还是使用它
// composer.json
{
"autoload": {
"psr-4": { "MongoDB\\": "src/" },
"files": [ "src/functions.php" ] // files 为全局引用文件注册
}
}
// autoload_static.php
public static $files = array (
'3a37ebac017bc098e9a86b35401e7a68' => __DIR__ . '/..' . '/mongodb/mongodb/src/functions.php',
)
总结
file: 目的是全局引入单个文件,composer中以json数组形式申明,罗列需要引入的文件
classmap:递归给定目录下所有文件/文件夹,对应目录名为命名空间,composer中以json数组形式申明,罗列需要递归的目标目录
psr-0和psr-4: php规范,规定了命名空间的规范,psr-4的目录层级明显较psr-0更简洁,composer中以json对象的形式申明,key值为命名空间,value值指定了文件所在的相对目录
由于psr规范的命名空间解析使用时还需额外判断文件是否存在等才做,增加了io消耗,顾所有psr规则的命名空间会生成一个classmap项储存,优先获取classmap中的对应关系,若匹配不到才会根据psr规则获取对应文件
自动加载函数
源码分析
在获取到所有自动加载类与文件夹对应的数组后(储存于ClassLoad的私有变量中),代码执行到
$loader->register(true);
执行完这句代码,所有的可自动加载的类通过spl_autoload_register()
注册
下面通过跟踪register学习注册的过程composer做了些什么
// 以下代码位于vendeor/composer/ClassLoader.php中
// register函数中只处理classmap,psr-0,psr-4规则的类
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend); // 指定了自动加载函数
}
// 加载类之前需要检查文件是否存在
// 其中includeFile()较为简单,重点在findFile()
public function loadClass($class)
{
if ($file = $this->findFile($class)){
includeFile($file);
return true;
}
}
function includeFile($file) {
include $file;
}
public function findFile($class) {
// 首先先从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;
}
}
// 以上方式查找失败,遵循psr-0,psr-4寻找对应文件
$file = $this->findFileWithExtension($class, '.php');
// 没有命中,并且定义了hhvm版本号,查找.hh文件
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
// 命中文件后存入缓存,下次查找文件可从缓存中获取
if (null !== $this->apcuPrefixF) {
apcu_add($this->apcuPrefix.$class, $file);
}
// 命中失败存在missingClasses数组中,下次查找直接返回失败
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
}
以上过程只覆盖了classmap, psr-0, psr-4,在之前介绍的四种规则种的file并没有在这里处理,因为class文件,在使用中才会动态引入,file会在代码执行之前完成所有文件引入,所以file文件中通常是一些全局函数,变量和一些必须的前置处理。
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
// 判断是否使用静态调用
if ($useStaticLoader) {
// 从autoload_static.php中获取files
$includeFiles = Composer\Autoload\ComposerStaticInit9a624ef1199d9f899a6118c386c86aa3::$files;
} else {
// 从autolaod_files.php中获取files
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire9a624ef1199d9f899a6118c386c86aa3($fileIdentifier, $file);
}
function composerRequire9a624ef1199d9f899a6118c386c86aa3($fileIdentifier, $file){
// 已经require的文件在全局中标识,以防多次引入
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
}
总结
自动加载文件分使用时加载和立即加载,classmap,psr-0,psr-4规则的文件在使用时才会动态引入,file文件会在getload时全局引入。使用时引入的文件中,引入classmap中记载的命名空间最省资源。
没有在classmap中找到的命名空间,无论是否最后找到对应文件都有所记录已节省下次搜索的时间。(没找到的记录在ClassLoader中的
$missingClasses
数组里,搜索到的记录在apcu中留档)