程序地址
index.php
require __DIR__.'/../vendor/autoload.php';
跟踪:index.php > /vendor/autoload.php
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit148a9da910429c7c016377bec97d275e::getLoader();
到达 composer 工作区
什么是自动加载?
新下载的 laravel5.5 ,App 下默认有 User.php
,是 User 模型(Model)
$userModel = new App\User();
或
use App\User;
$userModel = new User();
如此方便得益于 composer 的自动加载。
composer 自动加载的流程
(一)映射归纳 驱动:composer
根据加载标准(files\classmap\psr-4\psr-0),将映射关系分门别类写入不同的文件,以数组保存
当在控制台执行 composer require | update
引入一个组件时包,读取组建包的 composer.json
中的 "autoload"
配置,分别按照每个组建包配置的自动加载规则,扫描组件中的文件,将其写入不同加载标准的php文件数组中。
那么composer.json 中的自动加载通常有下面几种方式 :(这不是真实的 autoload 配置,这是从 laravel5.5 各个组建包的 composer.json 中拼在一起的)
"autoload": {
这是 laravel/framework 组建包的 composer.json 中的
"files": [
"src/Illuminate/Foundation/helpers.php",
"src/Illuminate/Support/helpers.php"
],
这是 hamcrest/hamcrest-php 组建包的 composer.json 中的
"classmap": [
"hamcrest"
]
这是项目的 composer.json 中的
"psr-4": {
"App\\": "app/"
},
这是 mockery/mockery 组建包的 composer.json 中的
"psr-0": {
"Mockery": "library/"
}
},
files : 文件预加载,框架启动时便被 include ,通常文件中提供一些函数方法方便我们使用,如经常用的 dd()
。
"files:["src/Illuminate/Foundation/helpers.php"]"
写入 /composer/autoload_files.php
,
autoload_files.php >
<?php
return array(
...
'f0906e6318348a765ffb6eb24e0d0938' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
);
classmap : 直接映射文件真实路径。 这是简单粗暴的,因此这种方式效率是最高的。
"classmap": ["hamcrest"],
写入 /composer/autoload_classmap.php
autoload_classmap.php >
return array(
'Hamcrest\\Arrays\\IsArray' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArray.php',
'Hamcrest\\Arrays\\IsArrayContaining' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContaining.php',
'Hamcrest\\Arrays\\IsArrayContainingInAnyOrder' => $vendorDir . '/hamcrest/hamcrest-php/hamcrest/Hamcrest/Arrays/IsArrayContainingInAnyOrder.php',
...// hamcrest 文件夹下所有文件
);
当 use Hamcrest\\Arrays\\IsArray;
时,可直接在 classmap 的数组中找到它,是不是很粗暴呢。
psr-4 :这是最常用的加载标准。"psr-4": {"App\\": "app/"}
写入 autoload_psr4.php
/composer/autoload_psr4.php >
return array(
...
'App\\' => array($baseDir . '/app'),// App 称作 prefix
);
只要是 app/ 目录下的文件,且名命空间符合 psr-4 标准的类文件都能被自动加载。如use App\Test
=> app/Test.php
。而 classmap 方式不可以,这样你可以在 app/ 下自由的增加/删除类文件了。
这里提一下,前面说 classmap 方式是最高效的,而 composer dump-autoload
可以将通过 psr-4 规范加载的文件 “落盘”,即写入 autoload_classmap.php 。从而起到加速的作用,官方建议生产环境时执行 composer dump-autoload --optimize
来优化项目的自动加载速度。
为了说明 composer dump-autoload
的作用,我在 app 目录下建立 Test.php
<?php
namespace App;
class Test{
}
执行 composer dump-autoload
后,发现在autoload_classmap.php
与 autoload_static.php
(稍后再提及)中找到了它。
/composer/autoload_classmap.php && autoload_static.php >
return array(
...
'App\\Test' => __DIR__ . '/../..' . '/app/Test.php',
'App\\User' => $baseDir . '/app/User.php',
)
而 composer dump-autoload --optimize
的作用是进行优化(optimize),清理无效索引空间另外在 /composer
生成了 user
缓存文件。
psr-0 :和 psr-4 类似,只是加载规则有所不同。官方已弃用,但 laravel 有的组建包还是在用的,composer 仍然支持向下兼容。"psr-0": {"Mockery": "library/"}
写入 autoload_namespaces.php
。
/composer/autoload_namespaces.php >
return array(
...
'Mockery' => array($vendorDir . '/mockery/mockery/library'),
);
autoload_static.php
回顾 composer/ 目录:
其中四个文件中保存了四种规范的映射,还有一个 autoload_static.php
文件。它把这四个文件中的所有映射集中在一起,通过 getInitializer()
方法注入到 ClassLoader
的属性中( ClassLoader 是实现自动加载的类,稍后再提)
autoload_static.php >
<?php
namespace Composer\Autoload;
class ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26{
public static $files = array(..);
public static $classMap = array(..);
public static $prefixLengthsPsr4 = array(..);
public static $prefixDirsPsr4 = array(..);
public static $fallbackDirsPsr4 = array(..);
public static $prefixesPsr0 = array(..);
public static function getInitializer(ClassLoader $loader){..};
}
(二) 映射提取 驱动:/composer/autoload_real.php
从四个文件中 或 autoload_static.php 中(因它包含了四个文件的全部映射)将全部映射提取到实现自动加载的类(ClassLoader)中,由 ClassLoader 查找类的映射,实现自动加载,
index.php > /vendor/autoload.php > /composer/autoload_real.php >
//重要概念:当前类的任务是把所有映射注入到ClassLoader中,由 ClassLoader 类实现自动加载。为了更好的说明,以下为简化版代码。
class ComposerAutoloaderInitccb56ced66f82d50b9e1d3fd28a6ab26{
getLoader(){
----STEP1 实例化 ClassLoader-------
require __DIR__ . '/ClassLoader.php';
$loader = new ClassLoader();
----STEP2 提取映射并注入 ClassLoader-------
if ($useStaticLoader) {
//若符合运行环境,优先从 autoload_static.php 提取映射(见前面 autoload_static.php 的介绍)
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26::getInitializer($loader));
} else {
// 否则就从分别从三个文件中提取出来并注入 ClassLoader
//psr-0
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
//psr-4
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
//classmap
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
----STEP3 STEP2完毕,启动 ClassLoader 的自动加载方法-------
$loader->register(true); //spl_autoload_register
----STEP4 处理预加载文件-------
//处理 autoload_files.php 中的预加载文件,由于这些文件需要立即加载,它和类的自动加载是不同的,只要加载后,文件中的函数就能用。
//同理,能从 autoload_static.php 提取,优先从这个文件提取。
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInitccb56ced66f82d50b9e1d3fd28a6ab26::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
require $file;
}
//最后一句
return $loader;
};
}
(三) 实现自动加载 驱动:/composer/ClassLoader.php
ClassLoader.php >
class ClassLoader{
/**
* 将此实例注册为自动加载程序
* -------------------------------------------
* new App/User();时将 'App/User'类命传递给 loadClass('App/User')方法
* 第二参数true debug
* $prepend为true,将 loadClass() 放在自动加载函数栈的第一个,即优先处理。
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* 加载给定的类或接口
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* 查找定义类的文件的路径
* (二)类文件提取 已将所有映射注入到此类中的数组属性中
* 从数组中根据 Key 值取出
* 但并不是那么简单,本人没有太过研究
*/
public function findFile($class){
//省略代码
return $file;
}
}