MENU

#02: Composer 加载原理 —— 引导

January 20, 2020 • Laravel 源码

autoload.php

在使用 Composer 的时候,只需要执行 require_once './vendor/autoload.php';,它所执行的是以下代码:

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit09e198beca830cb193176f21ca533986::getLoader();

可以看到,它又引入了 autoload_real.php 文件,并调用了 ComposerAutoloaderInit09e198beca830cb193176f21ca533986::getLoader() 方法。

autoload_real.php

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

    spl_autoload_register(array('ComposerAutoloaderInit09e198beca830cb193176f21ca533986', 'loadClassLoader'), true, true);
    self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    spl_autoload_unregister(array('ComposerAutoloaderInit09e198beca830cb193176f21ca533986', '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\ComposerStaticInit09e198beca830cb193176f21ca533986::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\ComposerStaticInit09e198beca830cb193176f21ca533986::$files;
    } else {
        $includeFiles = require __DIR__ . '/autoload_files.php';
    }
    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire09e198beca830cb193176f21ca533986($fileIdentifier, $file);
    }

    return $loader;
}

这里的大部分代码都属于「引导」部分,代码中已经用空行分隔号每一块代码了,我们可以慢慢拆解:

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

这里是典型的单例模式,目的是为了不让初始化部分的代码重复执行。

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

这里使用 spl_autoload_registerComposerAutoloaderInit09e198beca830cb193176f21ca533986 类的静态方法 loadClassLoader 注册到队列顶部,目的是当后续代码使用了一个未知的类时就会出发到该处理函数。我们查看 loadClassLoader 的源码:

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

显然后续的 self::$loader = $loader = new \Composer\Autoload\ClassLoader(); 就出发了该函数,并且最终把 ClassLoader.php 加载进来了,而后又使用了 spl_autoload_unregisterloadClassLoader 函数剔除出队列。

$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

检测当前环境是否支持静态初始化,金泰吃实话只支持 5.6 以上版本的 PHP 并且不支持 HHVM 虚拟机。

require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit09e198beca830cb193176f21ca533986::getInitializer($loader));

如果支持静态初始化就使用 ComposerStaticInit09e198beca830cb193176f21ca533986::getInitializer 进行初始化:

$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);
}

如果不支持静态初始化就需要调用核心加载类的方法进行逐项注入。

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

对于 file 类型的加载也要分是否支持静态初始化进行的处理。

autoload_static.php

这是自动加载的核心功能类,重点关注 getInitializer 方法是如何进行静态初始化的。

// 全局加载文件
public static $files = array (
    'f32902f145fce7a432f59959f59e5a18' => __DIR__ . '/../..' . '/app/helper.php',
);

// PSR4 命名空间前缀的长度映射
public static $prefixLengthsPsr4 = array (
    'A' => 
    array (
        'App\\' => 4,
    ),
);

// PSR4 命名空间前缀与实际路径的映射
public static $prefixDirsPsr4 = array (
    'App\\' => 
    array (
        0 => __DIR__ . '/../..' . '/app',
    ),
);

// PSR0 命名空间前缀与实际路径的映射
public static $prefixesPsr0 = array (
    'S' => 
    array (
        'Service\\' => 
        array (
            0 => __DIR__ . '/../..' . '/',
        ),
    ),
);

// 类全名与实际文件的映射
public static $classMap = array (
    'Other\\HttpClient' => __DIR__ . '/../..' . '/other/HttpClient.php',
);

看类的属性就知道 Composer 已经将可加载的所有类型映射生成好了。

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

getInitializer 方法实际上就是将以上这些数组注入到 $loader 中,而为什么要用闭包实现呢?这是因为 prefixLengthsPsr4prefixDirsPsr4prefixesPsr0classMap 这些都是 ClassLoader 的私有属性,利用闭包的绑定功能就可以将改变这些私有属性的值。