分析ThinkPHP5的源码(1) : 类的自动加载
前文
Composer 下载ThinkPHP5.1
的源码,每个框架它都必须都有一个“类的自动加载”机制 ,我们都知道PHP引入文件是需要require
、 include
才能使用别的类文件中的方法。
比如我需要写一个公共文件 model.php
include "model.php"
include "model2.php"
class User {
//code..
}
但是!如何当公共类库文件很多的时候,每次都需要手动引入,就显得非常麻烦,不利于/不方便维护管理。
所以PHP引入了一个spl_autoload_register()
的类自动加载,TP框架就是借助了 spl_autoload_register()
来完成类的自动加载。
TP5框架入口加载机制
学习框架源码的第一步,先找到入口文件 public/index.php
,然后一步步跟进流程,看下代码执行的过程。
- 第一行:定义命名空间就不赘述了。。
- 第二行:去加载基础文件
base.php
,是位于上一层目录的 think目录下
打开Base.php ,第16行就会去加载 Loader.php
文件 (就是TP5自动加载的类库) ,Loader.php 是TP5封装的底层基础类库。
再引用了核心文件 Loader.php
调用类库的 Loader::register()
的方法
发现都用了
spl_autoload_register()
的系统函数,其他框架也是同理。
都是会在框架的第一步“入门文件”就进行类的自动加载机制,针对底层进行封装。
TP5中的Loader::register() 其实做了2件事:
- 内部自定义一个
autoload()
去进行框架的底层深度封装 - 为了支持 Composer 自动加载 安装第三方的类库插件(Vendor),都是遵循PSR-4的风格统一,那么加载composer类的
autoload_static.php
后,再去加载对应的插件类库。
分析Loader::register的执行流程
如下截图:
-
分析TP5执行
Loader::register()
的注册自动加载方法 ,( 是调用不存在的类的时候才会执行spl_autoload_register()
) -
运用3元运算符,如果有参数就执行其他自定义类,没就 加载本类的
autoload()
方法 -
调用
self::gerRootPath()
获取项目的根目录 -
这里就是走 Composer的加载机制,然后组织一个Vendor的决定路径 ,
self::$composerPath
-
判断是否有Vendor的目录,再判断是否有
auto_static
的文件 ,有就加载composer目录下的auto_static.php
分析Composer自动加载——类文件
在逻辑往下走,判断是否有Vendor的目录,再判断是否有auto_static
的文件 ,有就加载composer目录下的auto_static.php
auto_static.php
发现定义了2个属性分别是: $prefixLengthsPsr4
、$prefixDirsPsr4
,这是什么意思呢?
定义数组,有个key和value,比如 $prefixLengthsPsr4 (PSR4的长度)
t
key代表一个命名空间 ,代表think\composer\
命名空间 ,首字母代表类,把某些类放进去来,15
代表字符的长度。a
key也是代表一个命名空间,代表:app
,用首字母代表,把某些类放进去,4
是这个字符的长度。
2个斜线是转义字符,比如
think\\composer\\
等同于think\composer\
$prefixDirsPsr4 把每个命名空间的类对应的目录列出来
- 比如
think\composer
这个命名空间 在src下 app\\
这个命名空间就在根目录的application
比如通过 Compsoe r安装 swoole、workermam、phpexcel 就把相应的规则追加数组上 填进来。
总结: composer的统一加载机制的地方,每次composer新类库的时候,都会往这个属性追加
命名空间
以及类库的目录路径
。
分析Composer自动加载——属性赋值
回到Loader::register()方法。
// Composer自动加载支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
$declaredClass = get_declared_classes();
$composerClass = array_pop($declaredClass);
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
if (property_exists($composerClass, $attr)) {
self::${$attr} = $composerClass::${$attr};
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
再返回 Loader::register() 方法接着放下分析:
如果存在Vendor目录,再判断是否有 composer_static.php
,有就执行如下:
执行了 get_declared_classes
这个系统函数,返回由当前脚本中已定义类的名字组成的数组。
我们打印这个get_declared_classes
得到目前所有加载的类,最后require加载的是 composer类。
所以,我们上面的 array_pop 弹出最后一个元素,赋值后的 $composerClass
,就是composer的类。
这个命令空间那么长的其实就是 composer目录下的autoload_static.php
再继续往下执行
property_exists : 检查对象或类是否具有该属性
我们知道autoload_static.php
存在有
- prefixLengthsPsr4
- $prefixDirsPsr4
- $classMap
等一众的方法,composer的 stacis.php
这些属性以及值 ,再赋值作为Loader
类的某个属性再处理。
上面的写法等同 :
self::prefixLengthsPsr4
self::prefixDirsPsr4
打印这些属性返回:
为什么这样去做?后续要集合多个属性再Map,加载文件的时候用这些属性。
总结: 把composere下的auto_static的PSR4的属性以及值赋值到Loader的PSR4属性中
分析Composer自动加载——注册命名空间
- 会调用当前
Loader::addNamespace()
方法 ,把当前think
跟traits
核心注册进行。
// 注册命名空间定义
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
- 进去
addNamespace()
方法,我们打印这个 $namespace 参数。
// 注册命名空间
public static function addNamespace($namespace, $path = '')
{
echo "<pre>";
print_r($namespace);exit;
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);
}
}
3. 再接下来,无论是不是数组,都再把命名空间拼接组装好,再调用self::addPsr4()
4. 查看 addPsr4()
方法
注册的时候走的是“296” 行 。
因为之前 self::$prefixDirsPsr4
里的属性只是composer的autp_static.php
上的,只有 think\ 跟 app\ ,并没有think
跟traits
这2个属性。
public static $prefixDirsPsr4 = array (
'think\\composer\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-installer/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/application',
),
);
6. 这样就完成 think、traits下的命名空间注册到Loader类的PSR4属性中。
加载类库映射文件
再往下执行:
- 我们在根目录的
runtime
并没有classmap.php的文件 ,我们可以通过命令生成这个文件
php think optimize:autoload
- 这时候runtime下就有classmap.php ,就是存放一些命名空间映射的文件
<?php
/**
* 类库映射
*/
return [
'app\\index\\controller\\Index' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/application/' . 'index/controller/Index.php',
'think\\App' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/App.php',
'think\\Build' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Build.php',
'think\\Cache' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Cache.php',
'think\\Collection' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Collection.php',
'think\\Config' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Config.php',
'think\\Console' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Console.php',
'think\\Container' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Container.php',
//code.....
]
- 进入这个
self::addClassMap()
方法,看到把当前classmap.php
的数组内容赋值到一个 Loader类的**$classMap** 属性中。
总结: 把tp5的核心的
think
和traits
命名空间注册到PSR4里,方便后续调用,实在属性多个数组统一自动加载。
自动加载extend目录
再往下执行
进去 self::addAutoLoadDir()
方法:
// 注册自动加载类库目录
public static function addAutoLoadDir($path)
{
self::$fallbackDirsPsr4[] = $path;
}
把当前extend
的目录也加载到 Loader类 $fallbackDirsPsr4 的属性中。
总结
- prefixDirsPsr4 (对应类的目录)
- prefixLengthsPsr4 (对应类的命名长度)
- fallbackDirsPsr4 (对应extend的目录)
我们拿到很多变量成员、命令空间、目录路径等都赋值到 Loader类的多个PSR4的属性中。
后续再拿这些属性做自动加载配置。