资讯专栏INFORMATION COLUMN

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

mudiyouyou / 578人阅读

摘要:如果遍历后没有找到,则加载失败。在之后碰到了之后直接拿来用,提高系统自动加载的性能。这里我们就讲完了注册自动加载。使用自动加载我们在中定义了我们自动加载函数式方法。

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

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

本文默认你有自动加载和命名空间的基础。如果没有请 看此篇文章 php 类的自动加载与命名空间

自动加载机制

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

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

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

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

register()方法执行流程

注册系统自动加载

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

// 注册系统自动加载
        spl_autoload_register($autoload ?: "thinkLoader::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 (

      "AppHttpControllersAuthForgotPasswordController"
              => __DIR__ . "/../.." . "/app/Http/Controllers/Auth/ForgotPasswordController.php",

      "AppHttpControllersAuthLoginController"
              => __DIR__ . "/../.." . "/app/Http/Controllers/Auth/LoginController.php",

      "AppHttpControllersAuthRegisterController"
              => __DIR__ . "/../.." ,
              ……
)

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

PSR4 标准顶级命名空间映射数组:
  public static $prefixLengthsPsr4 = array(
      "p" => array (
        "phpDocumentorReflection" => 25,
    ),
      "S" => array (
        "SymfonyPolyfillMbstring" => 26,
        "SymfonyComponentYaml" => 23,
        "SymfonyComponentVarDumper" => 28,
        ...
    ),
  ...);

  public static $prefixDirsPsr4 = array (
      "phpDocumentorReflection" => array (
        0 => __DIR__ . "/.." . "/phpdocumentor/reflection-common/src",
        1 => __DIR__ . "/.." . "/phpdocumentor/type-resolver/src",
        2 => __DIR__ . "/.." . "/phpdocumentor/reflection-docblock/src",
    ),
       "SymfonyPolyfillMbstring" => array (
        0 => __DIR__ . "/.." . "/symfony/polyfill-mbstring",
    ),
      "SymfonyComponentYaml" => array (
        0 => __DIR__ . "/.." . "/symfony/yaml",
    ),
  ...)

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

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

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

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

"SymfonyPolyfillMbstring" => 26,

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

 array (
              0 => __DIR__ . "/.." . "/symfony/polyfill-mbstring",
          )

然后我们就可以将命名空间 SymfonyPolyfillMbstringexample 前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 [
    "appindexcontrollerIndex" => "D:/app/tp5/application/" . "index/controller/Index.php",
    "thinkApp" => "D:/app/tp5/thinkphp/library/" . "/think/App.php",
    "thinkBuild" => "D:/app/tp5/thinkphp/library/" . "/think/Build.php",
    "thinkCache" => "D:/app/tp5/thinkphp/library/" . "/think/Cache.php",
    "thinkCollection" => "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的目录规则重写了文件路径

之后再加我们的目录文件

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31572.html

相关文章

  • Java相关

    摘要:本文是作者自己对中线程的状态线程间协作相关使用的理解与总结,不对之处,望指出,共勉。当中的的数目而不是已占用的位置数大于集合番一文通版集合番一文通版垃圾回收机制讲得很透彻,深入浅出。 一小时搞明白自定义注解 Annotation(注解)就是 Java 提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解) 是一个接口,程序可以通过...

    wangtdgoodluck 评论0 收藏0
  • 微信小程序的require机制浅析

    摘要:注意,这就与普通的脚本引用加载立即执行完全不同了接下来,就轮到微信小程序的函数出场了。所以深入理解微信小程序的模块化机制也是很有价值的 (注: 本文中所列微信小程序工具代码,并非为微信小程序原始代码,而是学习归纳的示意代码) 在学习开发微信小程序中, 分析总结了最近版本微信小程序模块化的函数 require的加载与初始化模块机制,归纳说来,小程序JS模块加载可分为两大步骤:一,JS模块...

    boredream 评论0 收藏0
  • 浅析webpack源码之convert-argv模块(

    摘要:接下来我看看一下函数我们先按照分支走为读取是里的对象,饶了这大的一个圈子,那么接下来一起来看一看对你的输入配置做了怎么样的处理吧 打开webpeck-cli下的convert-argv.js文件 // 定义options为空数组 const options = []; // webpack -d 检查 -d指令 if (argv.d) { //... } ...

    lemon 评论0 收藏0
  • webkit渲染机制浅析

    摘要:模块和将下面的渲染机制,安全机制,插件机制等等隐藏起来,提供一个接口层。进行网页的渲染进程,可能有多个。最后进程将结果由线程传递给进程最后,进程接收到结果并将结果绘制出来。 这是之前在简书上面的处女作,也搬过来了,以后就一直在 segmentfault 上面写文章了,webkit技术内幕-朱永盛是我大四买的书,很旧的一本书了,当时只看了一点点,一直没继续看完它,现在才看完,,,说来惭愧...

    Cobub 评论0 收藏0
  • 【腾讯Bugly干货分享】Android ListView与RecyclerView对比浅析--缓存

    摘要:数据源频繁更新的场景,如弹幕等的优势会非常明显进一步来讲,结论是列表页展示界面,需要支持动画,或者频繁更新,局部刷新,建议使用,更加强大完善,易扩展其它情况如微信卡包列表页两者都,但在使用上会更加方便,快捷。 本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/5811d... 作者:黄宁源 一,背景 RecyclerV...

    wangzy2019 评论0 收藏0

发表评论

0条评论

mudiyouyou

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<