Yii 类自动加载机制解析(二)

上篇文章主要对Yii本身的autoloader实现,本篇将解析composer的autoloader

1、根据Yii入口文件,找到composer的autoloader文件

1
2
3
4
//此文件只是引用了autoload_real.php,并调用类中getLoader()
require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit913f043d81d6924e265f7238bc5588af::getLoader();

2、查看getLoader()方法具体实现

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
private static $loader;

public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}

public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}

//将本类中loadClassLoader静态方法注册为自动加载函数
spl_autoload_register(array('ComposerAutoloaderInit913f043d81d6924e265f7238bc5588af', 'loadClassLoader'), true, true);

//此处实例化\Composer\Autoload\ClassLoader()类,会根据以上自动加载函数引入相应类文件
self::$loader = $loader = new \Composer\Autoload\ClassLoader();

//注销自动加载函数,以防yii核心代码中查找类文件时,无效调用
spl_autoload_unregister(array('ComposerAutoloaderInit913f043d81d6924e265f7238bc5588af', 'loadClassLoader'));

$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';

call_user_func(\Composer\Autoload\ComposerStaticInit913f043d81d6924e265f7238bc5588af::getInitializer($loader));
} else {

//以下引入的3个文件
//autoload_namespaces.php,保存了psr0标准 类命名空间与对应路径映射关系
//autoload_psr4.php,保存了psr4标准 类命名空间与对应路径映射关系
//autoload_classmap.php 保存了类命名空间与具体对应php文件路径映射关系
//此处分别调用相应的set方法,将命名空间与对应路径保存到相应变量中,以便随后自动加载相关类文件
//至于具体set方法实现请查看\Composer\Autoload\ClassLoader类中相应方法

$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);
}
}

//注册类自动加载函数,实现类的自动加载,详情见 3
$loader->register(true);

if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit913f043d81d6924e265f7238bc5588af::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire913f043d81d6924e265f7238bc5588af($fileIdentifier, $file);
}

return $loader;

3、\Composer\Autoload\ClassLoader类中 register() 具体实现

1
2
3
4
5
//将类中loadClass方法注册为自动加载函数
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

4、\Composer\Autoload\ClassLoader类中 loadClass() 具体实现

1
2
3
4
5
6
7
8
9
public function loadClass($class)
{
//找到类对应的文件路径,引入相应文件,完成类的自动加载
if ($file = $this->findFile($class)) {
includeFile($file);

return true;
}
}

5、那composer具体是怎样查找类对应文件路径的呢? 看 findFile()具体实现

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
38
public function findFile($class)
{
//查看classMap变量中是否存在这个类与对应文件的映射关系,有的话直接返回
//其实上面说到的getLoader()方法中因为autoload_classmap.php并将类与路径映射关系保存
//到对应变量中,就是存到了 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;
}
}

//如果没有找到,则调用 findFileWithExtension() 继续找
//findFileWithExtension()具体实现见 6
$file = $this->findFileWithExtension($class, '.php');

// Search for Hack files if we are running on HHVM
if (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;
}

6、查看findFileWithExtension()具体实现

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

//该函数其实就是根据上面
//$loader->set($namespace, $path)和$loader->setPsr4($namespace, $path)
//设置的信息找出类文件的路径信息。
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

$first = $class[0];
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
foreach ($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
$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 dirs
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;
}

return false;
}
}