文件:tp5.1\thinkphp\library\think\Loader.php

一、该类文件有几个重要的属性

如果第一次看这个类文件,相信很多人就被难倒在这几个属性上了,不过只要细看这篇文章,
每个小结内容,其实就是围绕着这几个属性来讲解的,看文章的过程请认真关注这几个属性

/**
* 类名映射信息
* @var array
*/
protected static $classMap = [];

/**
* 类库别名
* @var array
*/
protected static $classAlias = [];

/**
* PSR-4
* @var array
*/
private static $prefixLengthsPsr4 = [];
private static $prefixDirsPsr4    = [];
private static $fallbackDirsPsr4  = [];

/**
* PSR-0
* @var array
*/
private static $prefixesPsr0     = [];
private static $fallbackDirsPsr0 = [];

/**
* 需要加载的文件
* @var array
*/
private static $files = [];

/**
* Composer安装路径
* @var string
*/
private static $composerPath;

$prefixLengthsPsr4:存放的是一个二维数组,其中最外层的键值是里层一维数组的键值首字母,
里层的数组内容存储的是 路径=>路径字符串的长度,注意的是\\是转义字符,所以长度是1,不是2

public static $prefixLengthsPsr4 = array (
    't' =>
    array (
        'think\\composer\\' => 15,
    ),
    'a' =>
    array (
        'app\\' => 4,
    ),
 );

$prefixDirsPsr4:存放的也是一个二维数组,键值是命名空间,键值所对应的value值含义是当前命名空间下的所有文件夹路径

public static $prefixDirsPsr4 = array (
    'think\\composer\\' => 
     array (
         0 => __DIR__ . '/..' . '/topthink/think-installer/src',
     ),
     'app\\' => 
     array (
         0 => __DIR__ . '/../..' . '/application',
     ),
);

二、自动导入注册方法

// 注册自动加载机制
public static function register($autoload = '')
{

}

这个函数里面分了几个部分


1. 注册自动导入的函数

这里如果没有定义其它函数,默认就是调用的当前类的 autoload 方法,
后面会详细介绍下 autoload 方法 怎么导入类的

// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);

2. 合并composer属性到当前类属性中

$composerClass 的值是 tp5.1\vendor\composer\autoload_static.php

所以 self::${$attr} = $composerClass::${$attr}; 的工作,
就是把autoload_static.php对应的属性赋值给当前Loader.php对象中

// Composer自动加载支持
$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};
    }
}

3. 注册命名空间

// 注册命名空间定义
self::addNamespace([
    'think'  => __DIR__,
    'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);

其实addNamespace就是为了将以下的内容

array(2) {
  ["think"]=>
  string(53) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think"
  ["traits"]=>
  string(54) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\traits"
}

添加到 $prefixLengthsPsr4$prefixDirsPsr4

$prefixLengthsPsr4 = array(2) {
  ["t"]=>
  array(3) {
    ["think\composer\"]=> int(15)
    ["think\"]=> int(6)
    ["traits\"]=> int(7)
  }
  ["a"]=>
  array(1) {
    ["app\"]=> int(4)
  }
}

$prefixDirsPsr4 = array(4) {
  ["think\composer\"]=>
  array(1) {
    [0]=>  string(78) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\vendor\composer/../topthink/think-installer/src"
  }
  ["app\"]=>
  array(1) {
    [0]=> string(64) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\vendor\composer/../../application"
  }
  ["think\"]=>
  array(1) {
    [0]=> string(53) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think"
  }
  ["traits\"]=>
  array(1) {
    [0]=> string(54) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\traits"
  }
}

4. 生成classmap.php

可以通过命令:php think optimize:autoload,在runtime下生成这个文件,
__include_file 直接返回此文件的数组内容,再通过self::addClassMap把这个数组赋值给self::$classMap

以后如果自动加载的时候,都会先通过这个self::$classMap保存好的映射关系,进行查找类

// 加载类库映射文件
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
    self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}

5. 注册类库目录

// 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend');

其实就是将 extend 目录存储到 self::$fallbackDirsPsr4 属性,作为扩展目录,自动加载的时候也会来此属性目录下查找类

autoload -> findFile,在self::findFile方法下会执行这个属性值的查找

array(1) {
  [0]=> string(37) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\extend"
}

三、自动导入 autoload 方法

// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);

前面设置自动导入的函数,就是这个方法

// 自动加载
public static function autoload($class)
{

}

这个函数,有两个if语句

1. 类别名的使用

首先要知道 class_alias('类名', '类别名'); 的用法,
当我们定义了类的别名,我们就可以使用 new 类别名的方式使用了

if (isset(self::$classAlias[$class])) {
    return class_alias(self::$classAlias[$class], $class);
}

class_alias(self::$classAlias[$class], $class);的使用:

  1. 通过判断是否有别名信息isset(self::$classAlias[$class]
  2. 如果有,就为 self::$classAlias[$class] 这个类,创建一个别名 $class
  3. class_alias 创建别名成功,返回trueautoload方法就结束,
    因为直接使用别名就可以查找到类了,不需要再进行导入

2. 根据参数类名,查找文件

理解$prefixLengthsPsr4$prefixDirsPsr4$fallbackDirsPsr4这三个属性的作用,
是本篇文章的重点,相对其他几个属性的作用,这个几个参数理解比较麻烦。

但是下面我举了一个详细的例子,相信理解起来不难,看例子的时候,请参照TP5.1完整源码进行理解


通过 self::findFile($class) 查找到文件,并加载他

if ($file = self::findFile($class)) {

    // Win环境严格区分大小写
    if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
        return false;
    }

    __include_file($file);
    return true;
}
private static function findFile($class)
{
    ......
    // 查找 PSR-4
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';

    $first = $class[0];
    if (isset(self::$prefixLengthsPsr4[$first])) {
        foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
            if (0 === strpos($class, $prefix)) {
                foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
                    if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                        return $file;
                    }
                }
            }
        }
    }

    // 查找 PSR-4 fallback dirs
    foreach (self::$fallbackDirsPsr4 as $dir) {
        if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }
    ......
}

举例: 加载 "D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think\response\Html.php"

  1. self::findFile($class) 中的参数 $class 值为think\response\Html
  2. think\response\Html 转换为操作系统对应的路径分隔符,加上.php
    这演示的是win10系统,转换后得到的值是 $logicalPathPsr4 = 'think\response\Html.php'
  3. $class 值的第一个首字母为 t,遍历 self::$prefixLengthsPsr4['t']
    ["t"]=>
    array(3) {
     ["think\composer\"]=>
     int(15)
     ["think\"]=>
     int(6)
     ["traits\"]=>
     int(7)
    }
    
  4. 遍历 self::$prefixLengthsPsr4['t']时,将key的值作为$prefix
    如果 0 === strpos($class, $prefix),也就是当 0 === strpos('think\response\Html', 'think\')匹配成功
  5. 此时我们遍历 foreach (self::$prefixDirsPsr4[$prefix] as $dir)
    也就是 foreach (self::$prefixDirsPsr4['think\'] as $dir)
    ["think\"]=>
    array(1) {
     [0]=>
     string(53) "D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think"
    }
    
  6. 此时遍历,当 $dir = D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think时,
    if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
     return $file;
    }
    
    $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length) 就相当于是
    'D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think' . '\' . substr('think\response\Html.php', 6);
    'D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think' . '\' . response\Html.php';
    'D:\UPUPW_NP7.2_64\htdocs\tp5.1\thinkphp\library\think\response\Html.php';
    
    至此,我们就找到对应的类文件位置了