Laravel 之道第三章:步调 Composer 自动加载原理

栏目: 编程工具 · 发布时间: 6年前

内容简介:Laravel 之道第三章:步调 Composer 自动加载原理

开始前导语

Step.001: 指当前调试所在步骤

项目根目录/public/index.php: 当前调试的 PHP 文件位置

#10: 当前调试行所在文件的行号

Step.001--项目根目录/public/index.php #10

define('LARAVEL_START', microtime(true));
  • 源码理解

执行当前行,定义了一个 LARAVEL_START 的常量,并赋值了小数点化的微秒数。作用就是记录 Laravel 启动的时间点

  • 知识扩展

microtime 函数详解

Step.002--项目根目录/public/index.php #24

require __DIR__.'/../vendor/autoload.php';
  • 源码理解

执行当前行,将导入 项目根目录/vendor/autoload.php 文件,并执行此文件,将此文件中的变量、类、函数注入 PHP 运行内存中。

  • 额外注意

在 PhpStrom 步调工具,如果点击步入函数按钮,将进入 项目根目录/vendor/autoload.php 中进行步调;如果点击跳过函数按钮,将在当前文件调到下一有效行,并把 项目根目录/vendor/autoload.php 中的变量、类、函数直接注入内存中,无论 项目根目录/vendor/autoload.php 内部调用多少类、多少函数,直接全部执行完毕;

在这里,我们用步入按钮,即进入 项目根目录/vendor/autoload.php

  • 知识扩展

__DIR__: 返回当前文件所在绝对路径,记住是当前文件哦;如果 index.php 包含其它路径的 PHP 文件,而这个其它路径的 PHP 文件中的 __DIR__ 返回不是 index.php 所在路径,而是那个其它路径的 PHP 文件所在的绝对路径

Step.003--项目根目录/vendor/autoload.php #05

require_once __DIR__ . '/composer/autoload_real.php';
  • 执行结果

进入 项目根目录/vendor/composer/autoload_real.php。由于此文件只有一个类和一个函数,文件执行后,仅仅将类和函数注入内存,以便之后的实例化和调用;因此,此步用跳过函数按钮

Step.004--项目根目录/vendor/autoload.php #07

return ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd::getLoader();
  • 源码理解

整个 项目根目录/vendor/autoload.php 返回了 ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd::getLoader() 静态方法的返回值。而 ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd 类就在上一步 项目根目录/vendor/composer/autoload_real.php 文件中,因此,此处调用此类的静态方法,用步入按钮进入 autoload_real.php 文件中,并执行 getLoader 静态方法,查看具体如何执行的


Step.005之前先看一眼 项目根目录/vendor/composer/autoload_real.php

<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd
{
    private static $loader;

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

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}

Step.005--项目根目录/vendor/composer/autoload_real.php #18

if (null !== self::$loader) {
    return self::$loader;
}

18 行是 if 语句所在行

  • 源码理解

这段是检测当前类中 $loader 静态属性有没有赋过值,就是类似一种缓存检测读取,用来提升项目整体效率,防止重复实例化,也可以理解成单例模式。由于第一次运行,$loader 肯定是 null。if 里面的语句不执行。

Step.006--项目根目录/vendor/composer/autoload_real.php #22

spl_autoload_register(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'), true, true);
  • 源码理解

ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd::loadClassLoader($class) 静态方法,注入到 spl_autoload_register 自动加载队列中,并放入队列首部,之后 new 一个不存在内存的类时会优先执行此加载方法。

spl_autoload_register() 理解:

第一个参数: 这是一个包含类和静态方法的数组;意思指,如果以后要 new 一个不存在内存中的类,会把这个全类名(包含命名空间)作为参数传入 loadClassLoader 静态方法中,并执行静态方法。

第二个参数:true 时,开启异常提示,当 spl_autoload_register() 出现错误,会抛出异常

第三个参数: true 时,将第一个参数(那个数组)放入自动加载队列之首,这样会优先执行的

  • 知识扩展

spl_autoload_register 官方文档

Step.007--项目根目录/vendor/composer/autoload_real.php #23

self::$loader = $loader = new \Composer\Autoload\ClassLoader();
  • 源码理解

此行等同于

$loader = new \Composer\Autoload\ClassLoader();
self::$loader = $loader;

重点看 new \Composer\Autoload\ClassLoader(),记得上一步注册的自动加载函数吗。当前 new 的就是一个不存在内存中的类,因此会把 \Composer\Autoload\ClassLoader 这个类的全类名传入到 loadClassLoader 静态方法中,并执行。注意:\Composer\Autoload\ClassLoader 要去掉开头的 \ 哦,即 Composer\Autoload\ClassLoader ,别问为什么, PHP 就是这样规定的,加载参数的全类名不包含开头的 \

  • 执行结果

进入下列方法中

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

Step.008--项目根目录/vendor/composer/autoload_real.php #11 & #12

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

11 行是 if 所在行

  • 源码理解

如果 Composer\Autoload\ClassLoader 全等于 $class ,则执行 require __DIR__ . '/ClassLoader.php';。因为由上一步得知 $class 就是 Composer\Autoload\ClassLoader 故执行 if 里面的语句

  • 执行结果

进入 项目根目录/vendor/composer/ClassLoader.php, 并开始执行。因为这个文件就是一个类和一个函数,故跳过,直接将其注入内存,以后实例化后,在进去看看

Step.009--项目根目录/vendor/composer/autoload_real.php #24

spl_autoload_unregister(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'));
  • 源码理解

loadClassLoader 静态方法,从 PHP 自动加载队列中删除掉

Step.010--项目根目录/vendor/composer/autoload_real.php #26

$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
  • 源码理解

可以看出,$useStaticLoader 是一个布尔值。

第一个表达式 PHP_VERSION_ID >= 50600: 由于我用的是 PHP7.2 故,此表达式是 true,与运算未短路,执行下一表达式

第二个表达式 !defined('HHVM_VERSION'): 没有用 HHVM 虚拟机则返回 true,我的确没用,未短路,继续下一个表达式

第三个表达式 (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded())分解如下

第三个表达式中第一个表达式 !function_exists('zend_loader_file_encoded'): 如果不存在 zend_loader_file_encoded 函数返回true。经过测试我 PHP 中的确没有 zend_loader_file_encoded 函数,故与第三表达式中第二表达式短路。

整体返回 true

Step.011--项目根目录/vendor/composer/autoload_real.php #28 & #30

if ($useStaticLoader) {
    require_once __DIR__ . '/autoload_static.php';

    call_user_func(\Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::getInitializer($loader));
} else {
    // 不执行,先不用看了
}

28 行是 if 下一行

  • 源码理解

执行 项目根目录/vendor/composer/autoload_static.php,将里面的 ComposerStaticInit5df9ac896280b53fb5464c83eae493fd 类注入内存。然后调用这个类的 getInitializer 静态方法,并将 $loader 作为参数传入进去。getInitializer 静态方法将会返回一个 Closure 闭包函数(或称闭包对象)。然后通过 call_user_func() 执行此闭包函数。

看一下 getInitializer 静态方法样子

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

    }, null, ClassLoader::class);
}

getInitializer(ClassLoader $loader): 中的 ClassLoader 是类型约束的意思。因为 项目根目录/vendor/composer/autoload_static.php 所在的命名空间是 Composer\Autoload,并且没有关于 ClassLoader 类的 use 语句,因此 $loader必须是 Composer\Autoload\ClassLoader类的对象。否则报错

  • 关于闭包函数(闭包对象)

PHP 里面实现像 JavaScript 中的闭包,是通过一个内置对象实现的,这个对象是 Closure

凡是对象都有属性和方法,同样 Closure 对象也有属性和方法。你肯定会问,既然是对象,怎么能通过 Object() 或者 call_user_func('Object') 来执行呢。那是因为 Closure 有一个魔术方法 __invoke()。关于此方法看这 __invoke()魔术方法

  • 关于闭包函数中的 use
function () use ($loader) {}

因为闭包函数内与外是不同的空间,因此内部无法直接使用外部的变量的。只能通过 use 将外部的变量存放到 Closure 对象的 static 属性中,是以数组的形式存放的哦。这样在执行 __invoke() 时,就调用 static 中的键值对就可以了

  • 注:$this 无需使用 use 引用可直接在 Closure 对象中使用。这个我理解的是,可能在类中声明的闭包对象默认继承自当前类,不知道对不

  • 关于闭包函数的参数

会存放在闭包对象的 parameter 属性中,也是以数组的形式存放

  • 一个闭包对象的数据详情

Laravel 之道第三章:步调 Composer 自动加载原理

Step.012--项目根目录/vendor/composer/autoload_static.php #3710 到 #3713

$loader->prefixLengthsPsr4 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixesPsr0;
$loader->classMap = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$classMap;
  • 源码理解

ComposerStaticInit5df9ac896280b53fb5464c83eae493fd 类中关于 Psr4 Psr2 classMap 静态属性赋值给 ClassLoader 类对象 $loader 中的属性中。这些值都是一些全类名与类所在的绝对路径的键值对数组,例如 classMap

Laravel 之道第三章:步调 Composer 自动加载原理

Step.013--项目根目录/vendor/composer/autoload_real.php #48

$loader->register(true);
  • 源码理解

调用 $loader 对象的 register 方法,将 true 传入。即进入 项目根目录/vendor/composer/ClassLoader.php 文件的第 302 行

Step.014--项目根目录/vendor/composer/ClassLoader.php #302

public function register($prepend = false)
{
    spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}

302 行是方法体内的唯一一行

  • 源码理解

将当前对象 $loaderloadClass 方法,注入 PHP 自动加载队列中,可以抛出异常,放入队列尾部

  • 关于 loadClass 方法如何执行,后期实例化容器类的时候再看吧

Step.015--项目根目录/vendor/composer/autoload_real.php #50

if ($useStaticLoader) {
    $includeFiles = Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$files;
} else {
    // 这里不执行,不用看了
}

50 行是 if 所在行

  • 源码理解

Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd 类中的 $files 属性取出赋值给 $includeFiles,看一下 $includeFiles 值的样子

Laravel 之道第三章:步调 Composer 自动加载原理

Step.016--项目根目录/vendor/composer/autoload_real.php #55

foreach ($includeFiles as $fileIdentifier => $file) {
    composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file);
}
  • 源码理解

遍历 $includeFiles 数组,将数组的 key 和 value 作为参数来调用 composerRequire5df9ac896280b53fb5464c83eae493fd 函数

Step.017--项目根目录/vendor/composer/autoload_real.php #63

function composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
  • 源码理解

通过上一步,得知,此函数将被遍历执行数次,分别将 $includeFiles 数组的值(文件)包含执行。并设定 $GLOBALS['__composer_autoload_files'] 全局键名,来记录上面的文件是否被包含执行过,防止重复执行。

  • 关于包含了那些文件

项目根目录/vendor/symfony/polyfill-mbstring/bootstrap.php: Symfony 组件根据 PHP 的 mb_string 扩展定义了一些字符串处理的函数

项目根目录/vendor/symfony/polyfill-php72/bootstrap.php: 如果 PHP 版本小于7.2,则此文件会定义7.2版本新特性函数供低版本的 PHP 使用

项目根目录/vendor/symfony/var-dumper/Resources/functions/dump.php: 传说中 dump 和 dd 函数就在这里面哦

项目根目录/vendor/symfony/polyfill-ctype/bootstrap.php: 如果 PHP 内置没有ctype 系列函数, 则定义的这些函数

项目根目录/vendor/swiftmailer/swiftmailer/lib/swift_required.php: 注册了 Swift 相关类的自动加载函数

项目根目录/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php: Laravel 的相关辅助函数

项目根目录/vendor/laravel/framework/src/Illuminate/Support/helpers.php: 也是 Laravel 的相关辅助函数

项目根目录/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php: 定义一个深度拷贝功能的一个函数,执行此函数返回是深度拷贝的相关服务对象

项目根目录/vendor/psy/psysh/src/functions.php: psy 相关的一些辅助函数

Step.018--项目根目录/vendor/composer/autoload_real.php #59

return $loader;
  • 源码理解

返回到第 4 步,由第 4 步返回到第 2 步,回到 项目根目录/public/index.php。因为 index.php 并没有接受返回值,所以 return $loader 在这里是没有意义的;不过我们可以在 index.php 文件中去到 $loader 的值,然后对 $classMap 填充,来实现自定义目录的类自动加载哦。

例如,我定义一个类,放到 项目跟目录/MyClass 目录下

<?php

namespace MyClass;

class My {
    // ...
}

我只需要在 index.php 做如下修改,即可实现自己随意定义的目录和类

$loader = require __DIR__.'/../vendor/autoload.php';
$loader->addClassMap([
    'MyClass\\My' => __DIR__. '/..' . '/MyClass/My.php',
]);

不建议上面这种做法,你应该将你的服务类注册到 Laravel 中,从服务中去取更好一些。当然如果遇到非要从自己定义的路径和文件中取类,可以采用上面这种方式

MyClass\\My: \\ 两个反斜杠是转意\意思,将其以字符串形式显示。

本文章首发在 Laravel China 社区

我们是一群被时空压迫的孩子。 ---- 爱因斯坦

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Scrum精髓

Scrum精髓

Kenneth Rubin / 姜信宝、米全喜、左洪斌、(审校)徐毅 / 清华大学出版社 / 2014-6-1 / CNY 79.00

短短几年时间,Scrum跃升为敏捷首选方法,在全球各地得以普遍应用。针对如何用好、用巧这个看似简单的框架,本书以通俗易懂的语言、条理清晰的脉络阐述和提炼出Scrum的精髓。全书共4部分23章,阐述了七大核心概念:Scrum框架,敏捷原则,冲刺,需求和用户故事,产品列表,估算与速率,技术债;三大角色:产品负责人,ScrumMaster,开发团队以及Scrum团队构成:Scrum规划原则及四大规划活动......一起来看看 《Scrum精髓》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码