在研究 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 的规范。