thinkphp6.0 底层源码分析 - 类的自动加载、配置文件初始化

2023-10-21 07:22:25 浏览数 (3)

概述

因为工作的需要,深入研究了一下thinkphp的源码,也算是对php知识的一个回归,工作这么多年,我一直坚信php是最好的Web编程语言,它可以做到成本和效率的一个平衡,知其然,更要知其所以然才是高手修炼之道

类的自动加载

不管是tp,yaf 还是yii ,所有的php框架都是从自动加载类库文件开始的,如果你不知道如何下手,就打开入口文件,从分析类的自动加载开始。

thinkphp6使用了composer去加载类库,整个composer的实现原理是:首先将各个使用了不同psr规范的类或映射类,以某种形式存储,然后当类找不到的时候,通过与存储的数据匹配,找到类所在的路径,然后去加载。

实际上composer总共有四种规范的文件需要加载,分别是:psr0、psr4、类映射、公共函数文件。

1.使用了单例模式,原理:简化后,psr0,psr4,classmap每个分类对应一个数组,类名在这三个数组进行检索,检索完成后,include

代码语言:txt复制
public static function getLoader()
{
    if (null !== self::$loader) {
        return self::$loader;
    }
}

2.此处先注册自动加载未定义类,紧跟着注销,是因为只加载并实例化classLoader类,其他类的加载,使用composer提供的方法,而不是自定义的。

代码语言:txt复制
spl_autoload_register(array('ComposerAutoloaderInit1283bda52466502421173f3a3bffb31b', 'loadClassLoader'), true, true);
self::$loader = $loader = new ComposerAutoloadClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit1283bda52466502421173f3a3bffb31b', 'loadClassLoader'));

3.php版本大于5.6且未使用hhvm且没有启用zenGaurd加密扩展,即可使用静态加载,composer install 后,从各个vendor库的composer.json中读取autoload属性。

代码语言:txt复制
$useStaticLoader = PHP_VERSION_ID >= 50600 &&
!defined('HHVM_VERSION') &&
(!function_exists('zend_loader_file_encoded') ||
!zend_loader_file_encoded());

这里使用到了一个技巧,若对象类的成员属性是private,同时已经实现了set方法,现在需要实现同样的功能,直接复制给private成员属性。若是常用方法是将private属性变成public,或者修改set方法,或添加新的方法。但这里使用了系统类Closure的属性,可以通过bind方法,使用到了目标对象的private属性。

代码语言:txt复制
public static function getInitializer(ClassLoader $loader)
{
    return Closure::bind(function () use ($loader) {
        $loader->prefixLengthsPsr4 = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$prefixLengthsPsr4;
        $loader->prefixDirsPsr4 = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$prefixDirsPsr4;
        $loader->fallbackDirsPsr0 = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$fallbackDirsPsr0;
        $loader->classMap = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$classMap;

    }, null, ClassLoader::class);
}

框架初始化执行流程

thinkphp6.0 应用的初始化做了大量的操作,其主要的操作有:加载环境变量、加载配置文件,加载语言包、监听 AppInit、initializers 数组包含的类的初始化。

代码语言:txt复制
public function run(Request $request = null): Response
{
    //初始化
    $this->initialize();

    //自动创建request对象
    $request = $request ?? $this->app->make('request', [], true);
    $this->app->instance('request', $request);

    try {
        $response = $this->runWithRequest($request);
    } catch (Throwable $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    }
    return $response;
}

1.加载环境变量

重点强调一下在初始化加载initialize中,和底下的$this->lang->load$this->config->load都是一样的,都是加载对应文件中的数组。

代码语言:txt复制
// 加载环境变量
if (is_file($this->rootPath . '.env')) {
    $this->env->load($this->rootPath . '.env');
}


object(thinkEnv)#8 (1) {
  ["data":protected]=>
  array(14) {
    ["APP_DEBUG"]=>
    string(4) "true"
    ["DATABASE_CHARSET"]=>
    string(7) "utf8mb4"
    ["PROJECT_WS_DOMAIN"]=>
    string(15) "wss://127.0.0.1"
  }
}

2.调试模式设置

$this->debugModeInit() 运行原理详见注释,需要注意的是,这里不知道是不是源码中的Bug,!$this->appDebug 恒为true (有时间在做ob缓存和依赖注入的知识点)。

代码语言:txt复制
protected function debugModeInit(): void
{
    // 应用调试模式
    if (!$this->appDebug) {
        $this->appDebug = $this->env->get('app_debug') ? true : false;
        // 关闭错误显示
        ini_set('display_errors', 'Off');
    }
    // 如果不是命令行模式
    if (!$this->runningInConsole()) {
        // 重新申请一块比较大的buffer
        if (ob_get_level() > 0) {
            // 相当于ob_get_contents() 和 ob_clean()
            // 获取缓冲区内容并删除缓冲区内容
            $output = ob_get_clean();
        }
        // 开启新的缓冲区控制
        ob_start();
        if (!empty($output)) {
            // 由于开启了新的缓冲区控制,
            // 这里不会直接输出$output
            // 而是等到依次执行了ob_flush()和flash()之后才将内容输出到浏览器
            echo $output;
        }
    }
}
  1. 加载应用文件和配置等操作

在加载全局初始化文件的时候,加载是有顺序的,首先加载app目录下的common.php文件和系统下的helper.php文件,然后加载config目录下的所有php文件,最后加载event事件和service服务文件。

代码语言:txt复制
protected function load(): void
{
    $appPath = $this->getAppPath();
    # 首先加载app目录下的common.php文件和系统下的helper.php文件
    if (is_file($appPath . 'common.php')) {
        include_once $appPath . 'common.php';
    }

    include_once $this->thinkPath . 'helper.php';

    $configPath = $this->getConfigPath();

    $files = [];

    if (is_dir($configPath)) {
        $files = glob($configPath . '*' . $this->configExt);
    }

    foreach ($files as $file) {
        $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
    }
    # 然后加载config目录下的所有php文件
    # 最后加载event事件和service服务文件
    if (is_file($appPath . 'event.php')) {
        $this->loadEvent(include $appPath . 'event.php');
    }

    if (is_file($appPath . 'service.php')) {
        $services = include $appPath . 'service.php';
        foreach ($services as $service) {
            $this->register($service);
        }
    }

}

4.初始化错误和异常处理、注册系统服务和初始化系统服务

最后,初始化错误和异常处理、注册系统服务和初始化系统服务,这几行代码做了比较多的操作:分别实例化包含在里面的类,然后调用其init方法。initializers 数组的值如下:

代码语言:txt复制
 // 初始化
foreach ($this->initializers as $initializer) {
    $this->make($initializer)->init($this);
}
代码语言:txt复制
protected $initializers = [
    Error::class,
    RegisterService::class,
    BootService::class,
];

结尾

thinkphp6.0的类的自动加载和初始化就介绍到这里了,知其然,更要知其所以然才是高手修炼之道。

1 人点赞