在研究 Laravel 源碼的過程中,我遇到的第一個(gè)問題就是:Laravel 的中使用到的那些類是如何被加載進(jìn)來的蚜锨?
在 Laravel 的入口文件的第二行代碼中,引入了 bootstrap 文件夾下的 app.php 文件屑彻。而在這個(gè)文件的開頭嘱腥,一個(gè)名為 Application 的類就被初始化录别,這個(gè)文件在此之前并未引入,程序順利執(zhí)行邻吞。
很顯然组题,這個(gè)自動(dòng)加載的問題是在入口文件的第一行被解決的。在第一行引入的文件中抱冷,引入了 vendor 文件夾的 autoload.php 文件崔列。這個(gè)文件與 composer 有關(guān),Laravel 的自動(dòng)加載是借助了 composer旺遮。
composer 自動(dòng)加載
那么 composer 是如何做到自動(dòng)加載的呢赵讯?
打開 autoload.php 文件,這里面只有兩行代碼耿眉。第一行引入了一個(gè)文件边翼,第二行調(diào)用了該文件中的類的 getLoader 方法。ComposerAutoloaderInit5ba1fc8bf6c79636a218962c1a6da048 這個(gè)類名之所以看起來奇怪鸣剪,是因?yàn)橐乐诡惷麤_突而使用了 hash组底。
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit5ba1fc8bf6c79636a218962c1a6da048::getLoader();
在 getLoader 方法中,\Composer\Autoload\ClassLoader 被初始化為 $loader筐骇,然后 $loader 這個(gè)加載器的四個(gè)屬性 $prefixLengthsPsr4债鸡、$prefixDirsPsr4、$prefixesPsr0铛纬、$classMap 被依次填充厌均,前兩個(gè)對(duì)應(yīng)著 psr-4 規(guī)范,后面兩個(gè)分別對(duì)應(yīng) 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;
}
填充的內(nèi)容來自于 vendor/composer 文件夾下的這三個(gè)文件:autoload_classmap棺弊、autoload_namespaces、autoload_psr4擒悬。打開這些文件模她,每一個(gè)文件都返回了一個(gè)包含了很多文件路徑的數(shù)組。這些則來自于 composer.json茄螃。
"autoload": {
"classmap": [
"database"
],
"psr-4": {
"App\\": "app/"
}
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
]
},
不只是 Laravel 的根目錄存在 composer.json缝驳,vendor 文件夾下每一個(gè)由 composer 安裝的依賴庫,都存在著一個(gè)這樣的文件归苍。
$loader 加載器被填充后用狱,調(diào)用了它的 register 方法,在這個(gè)方法中拼弃,使用了 spl_autoload_register 注冊(cè)該類的 loadClass 方法作為 __autoload 的實(shí)現(xiàn)夏伊。需要用到一個(gè)類的時(shí)候,就會(huì)調(diào)用 loadClass 方法吻氧。
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
緊接著溺忧,引入了 autoload_files咏连,與其他三個(gè)不同,它返回的數(shù)組鲁森,數(shù)組中定義的多個(gè)文件會(huì)被提前加載祟滴,而不是用到時(shí)才加載。如其中的 '/laravel/framework/src/Illuminate/Foundation/helpers.php'歌溉、'/laravel/framework/src/Illuminate/Support/helpers.php'垄懂。正是由于這兩個(gè)文件被提前加載,其中的定義的框架方法才可以被在項(xiàng)目中任意使用痛垛。
// vendor/laravel/framework/composer.json
"autoload": {
"files": [
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
],
"psr-4": {
"Illuminate\\": "src/Illuminate/"
}
},
最后草慧,$loader 被返回。接下來就可以在項(xiàng)目中沒有顧忌的使用各種類和方法了匙头,而不需要把 include 寫滿每一個(gè)角落漫谷。當(dāng)然,前提是需要符合 psr-4 的規(guī)范蹂析。