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

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

内容简介: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 社区

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

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

查看所有标签

猜你喜欢:

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

The Tangled Web

The Tangled Web

Michal Zalewski / No Starch Press / 2011-11-26 / USD 49.95

"Thorough and comprehensive coverage from one of the foremost experts in browser security." -Tavis Ormandy, Google Inc. Modern web applications are built on a tangle of technologies that have been de......一起来看看 《The Tangled Web》 这本书的介绍吧!

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

各进制数互转换器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具