Laravel 源码解析之 composer 自动加载

在研究 Laravel 源码的过程中,我遇到的第一个问题就是:Laravel 的中使用到的那些类是如何被加载进来的?

在 Laravel 的入口文件的第二行代码中,引入了 bootstrap 文件夹下的 app.php 文件。而在这个文件的开头,一个名为 Application 的类就被初始化,这个文件在此之前并未引入,程序顺利执行。

很显然,这个自动加载的问题是在入口文件的第一行被解决的。在第一行引入的文件中,引入了 vendor 文件夹的 autoload.php 文件。这个文件与 composer 有关,Laravel 的自动加载是借助了 composer。

composer 自动加载

那么 composer 是如何做到自动加载的呢?

打开 autoload.php 文件,这里面只有两行代码。第一行引入了一个文件,第二行调用了该文件中的类的 getLoader 方法。ComposerAutoloaderInit5ba1fc8bf6c79636a218962c1a6da048 这个类名之所以看起来奇怪,是因为要防止类名冲突而使用了 hash。

<?php

// autoload.php @generated by Composer

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

return ComposerAutoloaderInit5ba1fc8bf6c79636a218962c1a6da048::getLoader();

在 getLoader 方法中,\Composer\Autoload\ClassLoader 被初始化为 $loader,然后 $loader 这个加载器的四个属性 $prefixLengthsPsr4、$prefixDirsPsr4、$prefixesPsr0、$classMap 被依次填充,前两个对应着 psr-4 规范,后面两个分别对应 psr-0 和 classMap。

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

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

        $includePaths = require __DIR__ . '/include_paths.php';
        array_push($includePaths, get_include_path());
        set_include_path(join(PATH_SEPARATOR, $includePaths));

        $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\ComposerStaticInit5ba1fc8bf6c79636a218962c1a6da048::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\ComposerStaticInit5ba1fc8bf6c79636a218962c1a6da048::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire5ba1fc8bf6c79636a218962c1a6da048($fileIdentifier, $file);
        }

        return $loader;
    }

填充的内容来自于 vendor/composer 文件夹下的这三个文件:autoload_classmap、autoload_namespaces、autoload_psr4。打开这些文件,每一个文件都返回了一个包含了很多文件路径的数组。这些则来自于 composer.json。

    "autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "App\\": "app/"
        }
    },
    "autoload-dev": {
        "classmap": [
            "tests/TestCase.php"
        ]
    },

不只是 Laravel 的根目录存在 composer.json,vendor 文件夹下每一个由 composer 安装的依赖库,都存在着一个这样的文件。

$loader 加载器被填充后,调用了它的 register 方法,在这个方法中,使用了 spl_autoload_register 注册该类的 loadClass 方法作为 __autoload 的实现。需要用到一个类的时候,就会调用 loadClass 方法。

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

紧接着,引入了 autoload_files,与其他三个不同,它返回的数组,数组中定义的多个文件会被提前加载,而不是用到时才加载。如其中的 '/laravel/framework/src/Illuminate/Foundation/helpers.php'、'/laravel/framework/src/Illuminate/Support/helpers.php'。正是由于这两个文件被提前加载,其中的定义的框架方法才可以被在项目中任意使用。

    // vendor/laravel/framework/composer.json
    "autoload": {
        "files": [
            "src/Illuminate/Foundation/helpers.php",
            "src/Illuminate/Support/helpers.php"
        ],
        "psr-4": {
            "Illuminate\\": "src/Illuminate/"
        }
    },

最后,$loader 被返回。接下来就可以在项目中没有顾忌的使用各种类和方法了,而不需要把 include 写满每一个角落。当然,前提是需要符合 psr-4 的规范。

推荐阅读更多精彩内容