ThinkPHP5.1 源码浅析(二)自动加载机制

栏目: PHP · 发布时间: 6年前

内容简介:继 生命周期的第二篇,大家尽可放心,不会随便鸽文章的第一篇中,我们提到了入口脚本,也说了,里面注册了自动加载的功能php 的自动加载是

继 生命周期的第二篇,大家尽可放心,不会随便鸽文章的

第一篇中,我们提到了入口脚本,也说了,里面注册了自动加载的功能

自动加载机制

php 的自动加载是 Loader 类中实现的,这个类在 base.php 中被引入

//base .php
// 载入Loader类
require __DIR__ . '/library/think/Loader.php';

// 注册自动加载
Loader::register();

我们程序在这里执行了 Loader 中静态方法 ,同时这也是一个全部的类 register() 我们进入 Loader.php ,按照上面执行顺序看看其核心是什么?

register()方法执行流程

ThinkPHP5.1 源码浅析(二)自动加载机制

注册系统自动加载

此方法行数过长,我们一点一点来分析

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

这就是注册我们的自动加载函数, $autoload 这个变量是传的参数,考虑到你可以自己实现自己的加载类,为了方便拓展,TP可以让你自己实现自己的类加载方法。

如果不了解这个函数的同学,请看文章最顶部的那个连接,上面有详细讲解。

Composer自动加载支持

$rootPath = self::getRootPath();
        self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;

        // Composer自动加载支持
        if (is_dir(self::$composerPath)) {
            if (is_file(self::$composerPath . 'autoload_static.php')) {
                require self::$composerPath . '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::$composerPath);
            }
        }

为了支持 composer 拓展,在自动注册时候,把composer 也顺带一起注册了,方便对拓展的调用。

autoload_static.php中的变量加载进内存中有一个难题: 由于autoload_static.php 文件中的类名一直在变化,我们无法得到固定的类名。 (如我系统中 类名为 ComposerStaticInit5109814b18095308ffe89ba7a1be18df

为了把 require self::$composerPath . 'autoload_static.php'; 中 的属性 载入进程序中,在这里我们换了一种形式

首先,获取程序中加载的所有类名,然后取我们最后一个加载的类名(即数组中的最后一个)。

$declaredClass = get_declared_classes(); 
$composerClass = array_pop($declaredClass);

拿到了我们的类名,调用 property_exists($composerClass, $attr) 检查类中是否存在指定的属性

疑问: composer_static 的参数代表是什么?

foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr)  中后面 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files')的作用是什么?

classMap(命名空间映射)

public static $classMap = array (

      'App\\Http\\Controllers\\Auth\\ForgotPasswordController'
              => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',

      'App\\Http\\Controllers\\Auth\\LoginController'
              => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',

      'App\\Http\\Controllers\\Auth\\RegisterController'
              => __DIR__ . '/../..' ,
              ……
)

直接命名空间全名与目录的映射,简单粗暴,也导致这个数组相当的大。

PSR4 标准顶级命名空间映射数组:

public static $prefixLengthsPsr4 = array(
      'p' => array (
        'phpDocumentor\\Reflection\\' => 25,
    ),
      'S' => array (
        'Symfony\\Polyfill\\Mbstring\\' => 26,
        'Symfony\\Component\\Yaml\\' => 23,
        'Symfony\\Component\\VarDumper\\' => 28,
        ...
    ),
  ...);

  public static $prefixDirsPsr4 = array (
      'phpDocumentor\\Reflection\\' => array (
        0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
        1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
        2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
    ),
       'Symfony\\Polyfill\\Mbstring\\' => array (
        0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
    ),
      'Symfony\\Component\\Yaml\\' => array (
        0 => __DIR__ . '/..' . '/symfony/yaml',
    ),
  ...)

PSR4 标准顶级命名空间映射用了两个数组,第一个是用命名空间第一个字母作为前缀索引,然后是 顶级命名空间,但是最终并不是文件路径,而是 顶级命名空间的长度。为什么呢?

因为 PSR4 标准是用顶级命名空间目录替换顶级命名空间,所以获得顶级命名空间的长度很重要。

具体说明这些数组的作用:

假如我们找 Symfony\Polyfill\Mbstring\example 这个命名空间,通过前缀索引和字符串匹配我们得到了

'Symfony\\Polyfill\\Mbstring\\' => 26,

这条记录,键是顶级命名空间,值是命名空间的长度。拿到顶级命名空间后去 $prefixDirsPsr4 数组 获取它的映射目录数组: (注意映射目录可能不止一条)

<?php
  'Symfony\\Polyfill\\Mbstring\\' => array (
              0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
          )

然后我们就可以将命名空间 Symfony\\Polyfill\\Mbstring\\example 前26个字符替换成目录 __DIR__ . '/..' . '/symfony/polyfill-mbstring ,我们就得到了 __DIR__ . '/..' . '/symfony/polyfill-mbstring/example.php ,先验证磁盘上这个文件是否存在,如果不存在接着遍历。如果遍历后没有找到,则加载失败。

注: 其实作为一个web框架,composer里面的东西,不应该由ThinkPHP关心的,但由于 TP5 自己原生的框架包 的设计没有完全包容 composer, 所在注册自动加载的时候会拿去其属性值自己来使用(仅限自己理解,如果与您观点不同欢迎讨论)

注册命名空间定义

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

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

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

这后面的代码都大同小异,都是把 所需要用到的类,映射到 Psr4空间 这个静态变量中。到时候方便我们使用命名空间进行调用。

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

在 TP5 代码下执行 php think optimize:autoload 就会在runtime下生成 classmap.php 文件,文件形式

return [
    'app\\index\\controller\\Index' => 'D:/app/tp5/application/' . 'index/controller/Index.php',
    'think\\App' => 'D:/app/tp5/thinkphp/library/' . '/think/App.php',
    'think\\Build' => 'D:/app/tp5/thinkphp/library/' . '/think/Build.php',
    'think\\Cache' => 'D:/app/tp5/thinkphp/library/' . '/think/Cache.php',
    'think\\Collection' => 'D:/app/tp5/thinkphp/library/' . '/think/Collection.php',
    ...
    ]

生成类库映射文件,会在 runtime 目录下面生成 classmap.php 文件,生成的类库映射文件会扫描系统目录和应用目录的类库。在之后碰到了之后直接拿来用,提高系统自动加载的性能。

register() 函数这里就大概分析结束了。 这里我们就讲完了 注册自动加载。

使用自动加载

我们在 register 中定义了我们自动加载函数式 Loader::autoload() 方法。 我们就小试牛刀,在我们的 base.php 中,我们加载完 自动加载机制后,就会加载我们的异常处理

// 载入Loader类
require __DIR__ . '/library/think/Loader.php';

// 注册自动加载
Loader::register();

// 注册错误和异常处理机制
Error::register();

在这时的状态里 Error 不存在,所有会进入我们的自动加载方法中重新试一下。

//函数整体内容
public static function autoload($class)
    {
        if (isset(self::$classAlias[$class])) {
            return class_alias(self::$classAlias[$class], $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;
        }
    }

我们截取片段一点一点分析。

if (isset(self::$classAlias[$class])) {
            return class_alias(self::$classAlias[$class], $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;
        }

findFile($class) 如果我们之前缓存了 classMap 在runtime文件夹下,那么他会直接返回。(这也就是为什么我们缓存 classMap 会提升性能的原因),如果没有缓存就配合我们之前存储映射关系的静态数组 prefixDirsPsr4 ,和 prefixLengthsPsr4 来找寻文件的目录,速度会相对慢很多。 如果没有找到那么就返回空, spl_autoload_register 会判断没有找到该类,抛出错误。

如果找到就消除 linux 和 window 对路径名称的差异。(linux 严格区分大小写,而win 没有严格区分)

这里主要是担心在window环境下,路径名称大小写没分,所以我们根据linux的目录规则重写了文件路径

之后再加我们的目录文件


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Byte of Python

A Byte of Python

Swaroop C H / Lulu Marketplace / 2008-10-1 / USD 27.98

'A Byte of Python' is a book on programming using the Python language. It serves as a tutorial or guide to the Python language for a beginner audience. If all you know about computers is how to save t......一起来看看 《A Byte of Python》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器