TP5.1 源码窥探之类的自动加载机制

由于受到疫情影响,小智典营销,线下课预售暂停一段时间,本来推荐使用微信直播的功能,但由于领导层强烈推荐使用第三方工具小鹅通,导致项目暂缓,仅仅对于线上业务进行开发维护。那就趁有时间学习一下新知识。然而学习什么新知识不重要,最重要的就是不要不学习。我也很迷茫,不知道先学什么,总是,这也想学,那也想学,然而到最后还是在原地踏步,痛定思痛,我先定一个计划,说学什么就规定在一段时间内就学这个,其他的先不管。学完一个再说。之前面试的时候,经常有人问,有研究过某个框架的源代码没。那就先从研究框架的源代码吧。由于最近的项目是用TP5.1.39开发的,那就研究tp5.1吧。

TP5.1中类的自动加载机制

首先进入项目入口文件

前提是你安装了tp5.1.39,composer create-project topthink/think=5.1.* tp5

public\index.php

//1、定义命名空间
namespace think;

//2、包含并运行base.php文件
require __DIR__ . '/../thinkphp/base.php';

//3、执行应用并响应
Container::get('app')->run()->send();

进入base.php文件,看看都做了哪些操作

8qKYlt

1、引入Loader类,需要注意的是,仅仅是引入类,并没有执行什么。

8qnsEQ

2、执行Loader::register()方法,注册自动加载机制
8qOnij

  • 我们进入该方法,首先利用spl_autoload_register注册系统自动加载机制,含义是:系统在new一个对象的时候,发现类不存在时,就是调用Loader::autoload的静态方法去引入加载对应的类

  • 然后获取项目的根目录,设置项目的composer的目录,引入autoload_static.php文件,然后获取已经声明的类文件,并弹出最后一个类名,此类名就是autoload_static.php文件中声明的类,并将此类的属性赋值给Loader类的属性,可以把此步骤理解为注册app,think\composer命名空间以及对应的目录

  • 然后再注册think,traits命名空间以及对应的目录

  • 然后注册类名映射,runtime/classmap.php,此类文件是由后期优化生成的,php think optimize:autoload

  • 然后注册自动加载目录extend

3、然后执行Error::register()方法

  • 由于think\Error类并没有事先引入,所以系统调用上一步注册的系统自动加载机制,执行Loader::autoload方法,此时参数为think\Error,进入autoload方法,首先查看类库别名中是否有此类名,如果有的话,就使用class_alias函数起别名,并自动加载。如果不存在类库,那就根据类名去查找文件,也就是执行findFile方法,首先查看是否存在类库映射(classMap),如果存在就返回文件目录地址,不存在的话,就查找psr-4并返回文件目录地址,如果找不到就继续查找psr-4自动加载目录,如果再找不到就查找psr-0规范的文件目录,如果再差找不到,就查找psr-0自动加载目录,返回文件目录地址,最后引入该文件

  • 然后执行register方法,注册异常处理

4、注册类库别名,Loader::addClassAlias,方便autoload

Loader::addClassAlias([
    'App'      => facade\App::class, // 对应的类文件目录 => thinkphp\library\think\facade\App.php
    'Build'    => facade\Build::class,
    'Cache'    => facade\Cache::class,
    'Config'   => facade\Config::class,
    'Cookie'   => facade\Cookie::class,
    'Db'       => Db::class,
    'Debug'    => facade\Debug::class,
    'Env'      => facade\Env::class,
    'Facade'   => Facade::class,
    'Hook'     => facade\Hook::class,
    'Lang'     => facade\Lang::class,
    'Log'      => facade\Log::class,
    'Request'  => facade\Request::class,
    'Response' => facade\Response::class,
    'Route'    => facade\Route::class,
    'Session'  => facade\Session::class,
    'Url'      => facade\Url::class,
    'Validate' => facade\Validate::class,
    'View'     => facade\View::class,
]);

扩展

1、由于autoload_static.php文件中的类名一直在变化,我们无法得到固定的类名,怎么做才能将此类的属性与Loader的属性合并?

//主要使用两个函数 get_declared_classes property_exists
//获取类名
$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};
    }
}

2、为什么使用命名空间映射?
节省资源,方便调用,避免每次循环查找

3、PSR4 标准顶级命名空间映射数组,用了两个数组,第一个是用命名空间第一个字母作为前缀索引,然后是 顶级命名空间,但是最终并不是文件路径,而是 顶级命名空间的长度。为什么呢?
因为 PSR4 标准是用顶级命名空间目录替换顶级命名空间,所以获得顶级命名空间的长度很重要,与Loader::findFile方法中字符串截取拼接类文件所在位置有用到length

4、如果需要增加一个新的命名空间,或者新增一个自动加载目录,你会怎么做?

主要是使用方法Load::addNamespaceLoader::addAutoLoadDir,在Loader内部外部都可以,但为了不修改源代码,建议在入口文件调用添加

5、类库别名class_alias
eg.直接使用Config类的话,自动加载机制会直接调用Loader::$classAlias属性中对应键名的键值类库

namespace app\index\controller;

use Config;//此时的Config类,在编辑器中鼠标点击是跳转不了的

class Index
{
    public function index()
    {
        $appConfig = Config::get('app.');
        var_dump($appConfig);
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
今年不学习,明天惨唧唧。
zs4336
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!