composer加载机制

Published on:
Tags: php composer

入口文件 index.php#

引入 vendor 的 autoload.php 文件

1
require __DIR__ . '/../vendor/autoload.php'; 

vendor/autoload.php 文件#

引入 composer 的 autoload_real.php 文件,然后调用 getLoader 方法

1
2
3
4
5
6
7
8
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit558596bf02a1b04254bb0a6c8d3be828::getLoader();

vendor/composer/autoload_real.php 文件#

该文件包含
ComposerAutoloaderInit558596bf02a1b04254bb0a6c8d3be828
和引入文件函数 composerRequire558596bf02a1b04254bb0a6c8d3be828()

getLoader方法#

版本检测#

首先引入文件:

1
require __DIR__ . '/platform_check.php';

检查 composer 依赖需要的 php 版本与环境的 php 版本是否一致,如果环境的 php 版本低于要求的版本,则会返回错误信息。

实例化 ClassLoader 类#

使用 spl_autoload_register 注册 loadClassLoader 方法:

1
2
3
4
5
6
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}

loadClassLoader 方法接收一个类名参数,当类名是 Composer\Autoload\ClassLoader 的时候,引入同级目录下的 ClassLoader.php 文件。

实例化 \Composer\Autoload\ClassLoader 类,参数是 vendor 目录的绝对路径,返回一个加载器实例 $loader。

1
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));

使用 spl_autoload_unregister 取消注册 loadClassLoader 方法。

初始化 $loader 实例的属性#

初始化 $loader 实例的属性有: prefixLengthsPsr4,prefixDirsPsr4,fallbackDirsPsr0,classMap 。

根据环境变量的值(如php版本,是否定义HHVM_VERSION常量等)判断是否使用静态加载方式。

若使用静态加载方式,则引入同级目录下的 autoload_static.php 文件,并且使用 call_user_func 方法回调 getInitializer 方法,参数是加载器实例$loader。

autoload_static.php 文件定义了以上几个属性,通过 getInitializer 方法实现 $loader 属性初始化;

若不使用静态加载方式,则分别引入同级目录下的 autoload_namespaces.php,autoload_psr4.php,autoload_classmap.php 文件,再调用 $loader 实例的 set(),setPsr4(),addClassMap() 方法实现 $loader 属性初始化;

注册自动加载函数#

使用 $loader 实例调用 register 方法,注册加载函数,register 方法体下面再说。

引入单文件#

通过读取配置文件,获取单文件映射数组,通过 composerRequire558596bf02a1b04254bb0a6c8d3be828 函数引入单文件。

返回加载器 $loader#

最后返回加载器 $loader 。

vendor/composer/ClassLoader.php 文件#

register 方法#

使用 spl_autoload_register 注册加载器方法 loadClass ,并且添加到函数队列之首。

loadClass 方法#

调用 findFile 方法,通过类名查找类,返回类的绝对路径,然后调用 includeFile 方法,引入文件。

findFile 方法#

findFile 方法使用 $loader 加载器在前面初始化的属性,根据类名查找类的绝对路径,后返回。

属性值#

ClassLoader 类有几个重要的属性。

classMap 属性保存类名与文件绝对路径的映射

1
2
3
4
5
6
7
8
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);

prefixLengthsPsr4 和 prefixDirsPsr4 属性保存命名空间与文件相对路径的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 保存命名空间与文件相对路径的映射,
public static $prefixDirsPsr4 = array (
'think\\view\\driver\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-view/src',
),
'think\\trace\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-trace/src',
),
'think\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/framework/src/think',
1 => __DIR__ . '/..' . '/topthink/think-helper/src',
2 => __DIR__ . '/..' . '/topthink/think-orm/src',
3 => __DIR__ . '/..' . '/topthink/think-template/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/app',
),
// 以下省略
);
// 保存命名空间首字母为键的二维数组
public static $prefixLengthsPsr4 = array (
't' =>
array (
'think\\view\\driver\\' => 18,
'think\\trace\\' => 12,
'think\\' => 6,
),
'a' =>
array (
'app\\' => 4,
),
// 以下省略
);

fallbackDirsPsr0 属性保存 $rootPath/extend/ 目录下的文件

1
2
3
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/../..' . '/extend',
);

查找 classMap 类映射#

首先查找类名与文件绝对路径的映射,找到则返回文件路径

1
2
3
4
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}

若以上没有匹配到目标类,则继续向下执行。

调用 findFileWithExtension 方法,使用其余3个属性查找文件路径。

findFileWithExtension 方法#

查找匹配的命名空间#

findFileWithExtension 方法的第一个参数是一个类名,通过类名的首字母判断 prefixLengthsPsr4 属性是否存在对应的命名空间。
如果不存在的话,就进入下一个属性 fallbackDirsPsr0 的判断。

如果存在的话,进入一个 while 循环,直到找到第一个存在的文件路径后返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// PSR-4 lookup
$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;
}
}
}
}
}

while 循环里,首先找到类名里最后一个反斜杠 “" 的位置 $lastPos ,
从第0位到 $lastPos 位截取类名,得到一个字符串 $subPath ,
字符串 $subPath 后拼接一个反斜杠 “",得到一个[可能是的]命名空间 $search ,
prefixDirsPsr4 属性保存命名空间与相对路径的映射,判断 $search 是否存在 prefixDirsPsr4 映射中,
如果存在,则返回一个数组,包含有一个或者多个路径,
从第 $lastPos + 1 位到最后一位截取类名,保留除命中的命名空间外的类名 $pathEnd , $pathEnd 与命名空间映射的相对路径拼接成绝对路径,判断文件路径是否存在,存在则返回文件路径。

目标类在 $rootPath/extend/ 目录#

1
2
3
4
5
6
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}

遍历 fallbackDirsPsr0 属性,与类名拼接成一个路径,文件路径真实存在则返回。