TP5学习-自动加载

0x01 注册自动加载

在tp5中,从base.php载入框架,第一件事就是实现自动加载。

首先,载入Loader

thinkphp\base.php

1
2
3
4
require think;
require __DIR__.'library/think/Loader.php';
# 调用的第一个方法
Loader::register();

thinkphp\library\think\Loader.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 注册自动加载机制
public static function register($autoload = ''){
// 注册系统自动加载
# 当类找不到时候, 调用`autoload`方法
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);

# 根目录
$rootPath = self::getRootPath();

# 首先加载的是composer里的内容,定位到vendor/composer
self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;

# 然后就可以自动加载支持


// Composer自动加载支持
if (is_dir(self::$composerPath))
{
# 找compsoer 下的`autoload_static.php`文件
if (is_file(self::$composerPath . 'autoload_static.php'))
require self::$composerPath . 'autoload_static.php';
# 内置函数:get_declared_classes()

Composer自动加载支持

首先自动加载的是composer里的内容,composer的内容默认在vendor中。

他会去找其中的autoload_static.php这里边有两个数组:

  1. $prefixLengthsPsr4:二维数组,先通过索引找命名空间,再映射到命名空间名的长度,以替换成路径。
  2. $prefixDirPsr4:命名空间到他的路径位置索引

这里存在一个问题, autoload_Static.php里的类名是随机的:这里存在一个问题, autoload_Static.php里的类名是随机的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 拿到类名后检查类中是否存在指定属性
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr)
{
if (property_exists($composerClass, $attr))
{
# 有的话就注册该属性
self::${$attr} = $composerClass::${$attr};
}
}
}
else
{
# 如果composer中没有`autoload_static.php`通过这里分别加载:
# `autoload_namespaces` `autoload_psr4` `autoload_classmap` `autoload_files`
self::registerComposerLoader(self::$composerPath);
}
}

我们通过composer加载的组件都可以通过这里加载框架中有引用。

1
2
3
4
5
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);

这里找到了thinkphp/library/目录下的两个文件夹,是框架级的类库。将这两个路径以数组的形式传入addNamespace方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注册命名空间
public static function addNamespace($namespace, $path = '')
{
# 列出 thinkphp/library/ 目录下的两个文件夹
if (is_array($namespace)) {
foreach ($namespace as $prefix => $paths) {
# 调用此方法加载框架里的内容
self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true);
}
} else {
self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true);
}
}

这里是数组,就解数组然后通过addPsr4这个方法将该属性注册到之前composer加载的类库中。与其合并在$prefixLengthsPsr4$prefixDirPsr4当中。

类库映射文件以及extend目录

  • 类库映射关系,效率会比通过命名空间定位更高[1]
1
2
3
4
5
6
7
8
9
10
11
// 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'))
{
# 此文件为runtime文件夹下通过`php think optimiz:autoload`生成的`classmap.php`文件
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}

// 自动加载extend目录
# 用户扩展,可以在此导入用户自建的类库,供上层应用使用
self::addAutoLoadDir($rootPath . 'extend');
}

通过addClassMap方法来实现类别名的映射,类库映射文件通过php think optimiz:autoload命令在runtime目录下生成。

1
2
3
4
5
6
7
8
9
// 注册classmap
public static function addClassMap($class, $map = '')
{
if (is_array($class)) {
self::$classMap = array_merge(self::$classMap, $class);
} else {
self::$classMap[$class] = $map;
}
}

$class都导入到了self::$classMap的静态属性中。

接下来,加载extend目录,这个目录下通常编写一些用户的基础类库,为了上层应用能加载到他。

1
2
3
// 自动加载extend目录
# 用户扩展
self::addAutoLoadDir($rootPath . 'extend');

0x02 使用流程

base.php中注册自动加载完成后开始注册错误和异常处理机制Error::register();这个类不存在,激活spl_autoload_register调用autoload方法传入think\Error

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
 // 自动加载
public static function autoload($class)
{

# 是否存在别名
# 这里的别名在`base.php`最后一步有加载一个别名列表
if (isset(self::$classAlias[$class]))
{
# 调用内部函数`class_alias`注册一个别名
return class_alias(self::$classAlias[$class], $class);
}


if ($file = self::findFile($class))
{

// Win环境严格区分大小写
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME))
{
return false;
}

__include_file($file);
return true;
}
}

这里主要是调用了self::findFile这个方法, 这里会查找类库映射找到

1
think/Error = /Applications/MAMP/htdocs/learn_tp5/learn_tp5/thinkphp/library//think/Error.php

通过这条映射将该文件加载。如果没找到就会向后找注册Psr4属性(classMap->prefix->fallback)。再比如把runtime下的类库映射文件classmap.php删除,就会触发 查找 PSR-4这一段:

如果我们开发了一个类库,放入到extends文件夹下,如:

1
2
3
4
5
6
7
8
9
10
11
//extend/mytools/sayHi.php
<?php

namespace mytools;

class sayHi{
public static function sayHi(){
echo 'Hi~';
}
}
?>

在控制器中即可加载:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\controller;

use mytools\sayHi;

class Index
{
//...
public function test(){
sayHi::sayHi();
}
}

0x03 原理

这一部分涉及了spl_autoload_register命名空间以及Psr4的知识可参考[2]