ThinkPHP6 的自动加载

这是两篇参考文章

laravel自动加载的描述

PSR0 和 PSR4的区别

事实上,thinkphp 框架用的是 composer 的自动加载

1 项目入口文件引入 composer 自动加载的初始化类

public/index.php 中第一句

namespace think;
require __DIR__ . '/../vendor/autoload.php';

接下来 vender/autoload.php

require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868::getLoader();

这里引入的 autoload_real.php 中定义的 ComposerAutoloaderInit 类名后面带了一串哈希值,可以这样做的原因可能是为了保证类名的唯一性,防止和用户自定义的类名冲突

2 getLoader 方法

2.1 单例

public static function getLoader()
{
    // 单例模式,这个很好理解
    if (null !== self::$loader) {
        return self::$loader;
    }
    ...
}

2.2 实例化ClassLoader核心类

public static function getLoader()
{
    ...
    spl_autoload_register(array('ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868', 'loadClassLoader'), true, true);
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    spl_autoload_unregister(array('ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868', 'loadClassLoader'));
    ...
}

loadClassLoader 方法

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

这里它先将本类的 loadClassLoader 注册为自动加载方法,用完后又注销掉,而 loadClassLoader 方法唯一的用处就是引入 ClassLoader.php 文件,为什么不直接引入而要绕这么一圈呢?

前面介绍 laravel自动加载的文章中有对这里做了解释,但是我还是看不明白

2.3 将autoload_static.php中定义的自动加载映射关系合并到ClassLoader类中

ClassLoader 类的 prefixLengthsPsr4、prefixDirsPsr4 等属性是私有的,要操作这些私有属性,这里有两种方式,第一种是使用匿名函数绑定 Closure::bind,第二种是调用 ClassLoader 类的公有方法

第二种方式很好理解,我们来看下第一种方式

require_once __DIR__ . '/autoload_static.php';
// ComposerStaticInit00730f6ba716ad33a51233e55145c868::getInitializer 方法返回一个匿名函数对象,call_user_func 执行这个匿名函数
call_user_func(\Composer\Autoload\ComposerStaticInit00730f6ba716ad33a51233e55145c868::getInitializer($loader));

看下 getInitializer 方法

public static function getInitializer(ClassLoader $loader)
{
    return \Closure::bind(function () use ($loader) {
        $loader->prefixLengthsPsr4 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$prefixLengthsPsr4;
        $loader->prefixDirsPsr4 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$prefixDirsPsr4;
        $loader->fallbackDirsPsr0 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$fallbackDirsPsr0;

    }, null, ClassLoader::class);
}

关于 Closure::bind 方法,文档中给的解释是:复制一个闭包,绑定指定的$this对象和类作用域。不太好理解
第一个参数是要绑定的匿名函数,这个容易理解

第二个参数是匿名函数中 $this 指代的对象,如果需要的话,方式是 new ClassName(),不需要就如文中传 null

第三个参数指定匿名函数的类作用域,简单来说就是如果匿名函数中有用到 private 或 protected 的属性或方法时,这里传入对应的类名,如$loader->prefixLengthsPsr4 是私用的,这里传的就是 $loader 实例对应的类名。如果将prefixLengthsPsr4
改为公有的,ComposerStaticInit...中的属性改为私有的,那这个参数就要填ComposerStaticInit...的类名了

我们知道,类对象的传递都是引用传递,所以这里匿名函数中修改 $loader 的属性值,ComposerAutoloaderInit...中定义的 $loader也修改了

2.4 PSR0 和 PSR4 的区别

在类名中使用下划线没有任何特殊含义

命名空间与文件目录的映射方法有所调整,假如我们有一个命名空间: Foo/class ,Foo 是顶级命名空间,其存在着用户定义的与目录的映射关系: "Foo/"=>"src/" 按照PSR0标准,映射后的文件目录是:src/Foo/class.php, 但是按照 PSR4标准,映射后的文件目录就会是:src/class.php

PSR4的命名空间最后一位必须是\

PSR0 的最后一个\后如果有_,将会转化为分隔符

2.5 将ClassLoader类的loadClass方法注册为自动加载函数

public function register($prepend = false)
{
    // sql_autoload_register 注册的自动加载函数是一个队列的形式,如果你有自己的自动加载方法,需要在 composer的自动加载找不到的情况下调用,就用 sql_autoload_register 注册你的,第三个参数传false
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

2.6 loadClass方法中最主要的findFileWithExtension方法

private function findFileWithExtension($class, $ext)
{
    // PSR-4 lookup
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

    // 先获取首字母,查找prefixLengthsPsr4中是否有匹配
    // 截取最后一个分隔符之前的部分,查找prefixDirsPsr4中是否有匹配,这里用的while循环,没有找到的话就继续截取上一个分隔符前面部分
    // 问题:为什么不是从prefixLengthsPsr4首字母对应的值中匹配?难道是因为prefixDirsPsr4的数量不多的原因么?
    $first = $class[0];
    if (isset($this->prefixLengthsPsr4[$first])) {
        $subPath = $class;
        while (false !== $lastPos = strrpos($subPath, '\\')) {
            $subPath = substr($subPath, 0, $lastPos);
            $search = $subPath . '\\';
            if (isset($this->prefixDirsPsr4[$search])) {
                $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                foreach ($this->prefixDirsPsr4[$search] as $dir) {
                    if (file_exists($file = $dir . $pathEnd)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    // 如果上面没有找到,就去extend文件夹下去找(对这里的fallbackDirsPsr4 来说就是这样的),TP的官方文档中也有说在extend中的每一个文件夹都是一个自定义根命名空间
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }
    // 后面的是PSR0的匹配,就不看了
    .
    .
    return false;
}

2.7 includeFile

注意下面的注释部分,这个函数被放在了类的外面,以防止引入的文件中调用$this或self
include和require的区别中有一点,require放在程序的最前面,include可以放在任意位置,所以这里需要用 include

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 1
蔺焕然

这不就是 composer 的加载原理吗

4年前 评论
lxdong12 (楼主) 4年前

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