阅读 99

自学TP5源码(二)注册自动加载机制

主要细看一下 register 这个函数,在上一章节的结尾头有\think\Loader::register();


/**
     * 注册自动加载机制
     * @access public
     * @param  callable $autoload 自动加载处理方法
     * @return void
     */
    public static function register($autoload = null)
    {
        // 注册系统自动加载
        spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);

        // Composer 自动加载支持
        if (is_dir(VENDOR_PATH . 'composer')) {
            if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) {
                require VENDOR_PATH . 'composer' . DS . '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::addNamespace([
            'think'    => LIB_PATH . 'think' . DS,
            'behavior' => LIB_PATH . 'behavior' . DS,
            'traits'   => LIB_PATH . 'traits' . DS,
        ]);

        // 加载类库映射文件
        if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
            self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
        }

        self::loadComposerAutoloadFiles();

        // 自动加载 extend 目录
        self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
    }

复制代码
1. 注册系统自动加载

主要是用了函数 spl_autoload_register 函数里写到的是

spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
复制代码

说明 如果参数里传了要注册加载的函数(里面写了需要引入require的类),那么就直接调用,否则,就是将Loader类里的 autoload 方法传进去,然后第二个参数为true,意思是无法成功注册类的时候,抛出异常,第三个参数为 true 的意思是将其要注册的类排在队列之首。

下面看看autuload 方法。

/**
     * 自动加载
     * @access public
     * @param  string $class 类名
     * @return bool
     */
    public static function autoload($class)
    {
        // 首先检测了是否存在命名空间别名数组是否有值,
        if (!empty(self::$namespaceAlias)) {    // 如果有
            $namespace = dirname($class);   // 获取该类的目录结构
            if (isset(self::$namespaceAlias[$namespace])) { // 查看数组里是否有存
                $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); // 将类完整的命名空间拼接起来
                if (class_exists($original)) {  // 如果存在该类
                    return class_alias($original, $class, false);   // 将其命类别名
                }
            }
        }

        if ($file = self::findFile($class)) {   // 查找文件 (后面再详看)
            // 非 Win 环境不严格区分大小写
            if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) {
                __include_file($file); // 引入include该文件
                return true;
            }
        }

        return false;
    }
复制代码
2. Composer 自动加载支持
// 如果根目录vendor下面有composer目录
if (is_dir(VENDOR_PATH . 'composer')) {
    // 如果php版本大于5.6, 并且composer目录下有 autoload_static.php 文件
    if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) {
        // 将该php文件引入进来。
        require VENDOR_PATH . 'composer' . DS . 'autoload_static.php';
        // 获取引入过的所有的类名
        $declaredClass = get_declared_classes();
        // 弹出刚才引入的那个
        $composerClass = array_pop($declaredClass);
        // 循环这些字符串命名
        foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
            // 如果有该属性,就在loader类里也存该属性
            if (property_exists($composerClass, $attr)) {
                self::${$attr} = $composerClass::${$attr};
            }
        }
    } else {
        // 否则 注册 composer 自动加载
        self::registerComposerLoader();
    }
}
复制代码

这里TP里 是有 autoload_static.php 这个文件 ,所以走上面那个 if 里的程序。 继续往下走。

3. 注册命名空间定义
.
.
self::addNamespace([
    'think'    => LIB_PATH . 'think' . DS,
    'behavior' => LIB_PATH . 'behavior' . DS,
    'traits'   => LIB_PATH . 'traits' . DS,
]);
.
.
复制代码

这里的关键就是Loader 类里的 addNamespace 函数了。

/**
     * 注册命名空间
     * @access public
     * @param  string|array $namespace 命名空间
     * @param  string       $path      路径
     * @return void
     */
    public static function addNamespace($namespace, $path = '')
    {
        // 如果是数组  刚才传的是一个数组。
        if (is_array($namespace)) {
            foreach ($namespace as $prefix => $paths) {     // 进行遍历
                self::addPsr4($prefix . '\\', rtrim($paths, DS), true);
            }
        } else {
            self::addPsr4($namespace . '\\', rtrim($path, DS), true);
        }
    }
复制代码

拿遍历的第一次进行分析,看是怎样通过 addPsr4 方法添加。

/**
     * 添加 PSR-4 空间
     * @access private
     * @param  array|string $prefix  空间前缀
     * @param  string       $paths   路径
     * @param  bool         $prepend 预先设置的优先级更高
     * @return void
     */
    private static function addPsr4($prefix, $paths, $prepend = false)
    {
    // $prefix: "think\"
    // $paths: "C:\laragon\www\mini-project\thinkphp\library\think"
    // $prepend: true
    
        // 是否传了prefix 
        if (!$prefix) {
            // Register directories for the root namespace.
            // 没有就直接放到 $fallbackDirsPsr4 数组属性里
            // $prepend 决定是否放在数组最前面。
            self::$fallbackDirsPsr4 = $prepend ?
            array_merge((array) $paths, self::$fallbackDirsPsr4) :
            array_merge(self::$fallbackDirsPsr4, (array) $paths);
        
        // 如果 $prefixDirsPsr4 数组里不存在 think\ 这个键名
        } elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);  // 计算键名的长度
            if ('\\' !== $prefix[$length - 1]) {    // 看键名末尾是否有 "\"
                throw new \InvalidArgumentException(
                    "A non-empty PSR-4 prefix must end with a namespace separator."
                );
            }
            
            // 最后按照 autoload_static.php 那种格式存起来。
            self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            self::$prefixDirsPsr4[$prefix]                = (array) $paths;
        
        // 如果有$prefixDirsPsr4对应的键名
        } else {
            // 存起来
            self::$prefixDirsPsr4[$prefix] = $prepend ?
            // Prepend directories for an already registered namespace.
            array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) :
            // Append directories for an already registered namespace.
            array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths);
        }
    }
    
    // 假设以前没有这个 `think/` 这个键名,最后$prefixDirsPsr4数组就应该是
    /**
    $prefixLengthsPsr4 = [
        ...
        't' => [
            ...
            'think\\' => 6
        ],
    ];
    $prefixDirsPsr4 = [
        ...
        'think\\' => ['C:\laragon\www\mini-project\thinkphp\library\think']
    ];
    对比composer目录下的 autoload_static.php文件类里的属性
    **/
    
复制代码

这样就按psr4的规则将这个类存到了数组 $prefixDirsPsr4 和数组 $prefixLengthsPsr4 等着注册就行了。

4. 加载类库映射文件
// 如果 存在文件 /runtime/classmap.php
if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
    // 就添加映射
    self::addClassMap(_include_file(RUNTIME_PATH . 'classmap' . EXT));
}
复制代码

addClassMap方法

/**
 * 注册 classmap
 * @access public
 * @param  string|array $class 类名
 * @param  string       $map   映射
 * @return void
 */
 public static function addClassMap($class, $map = '')
 {
    // 一般映射文件里都是写成数组格式,可以参考composer里的文件。
    if (is_array($class)) {
        self::$classMap = array_merge(self::$classMap, $class);
    } else {
        self::$classMap[$class] = $map;
    }
 }
复制代码

一般情况下是没有的,接着看关键的自动加载composer的文件 file 类型

5. 加载composer autofile 文件
self::loadComposerAutoloadFiles();
复制代码

且细看 这个方法

public static function loadComposerAutoloadFiles()
{
    // 开始将静态变量 $files 里的文件进行遍历 require加载 了
    foreach (self::$files as $fileIdentifier => $file) {
        // 查看是否有全局变量 __composer_autoload_files 如果是空的 就进行记录。
        if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
            // require进来
            __require_file($file);
            // 进行记录
            $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
        }
    }
}
而在开始的时候就已经将 autoload_static.php 里的文件就加载到 files里了。
self::$files = [
    '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
    '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
    'ddc3cd2a04224f9638c5d0de6a69c7e3' => __DIR__ . '/..' . '/topthink/think-migration/src/config.php',
    'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php',
]; 
这样对应着看就很清晰了,就把这4个文件引入了。
复制代码

在后面的章节笔记中,后面就一一看这四个文件。