程序地址
index.php
require __DIR__.'/../vendor/autoload.php';
跟踪:index.php > /vendor/autoload.php
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit148a9da910429c7c016377bec97d275e::getLoader();
到达 composer 工作区
什么是自动加载?
$userModel = new \App\User();
或
use App\User;
$userModel = new User();
composer 自动加载的流程
实现自动加载的过程可简单分为三步:
- 1.类文件归纳
- 2.类文件提取
- 3.类文件查找
一.类文件归纳 驱动:composer
根据加载标准,一共有四种加载标准(files,classmap,psr-4,psr-0),将映射关系分门别类写入不同的文件,以数组保存,比如 psr-4 标准的加载形式,写入 autoload_psr4.php
当在控制台执行composer require | update
引入一个组件时包,读取组建包的composer.json
中的"autoload"
配置,分别按照每个组建包配置的自动加载规则,扫描组件中的文件,将其写入不同加载标准的 php 文件数组中。
下面演示 laravel 四种声明加载标准的组件,是如何归纳的。
"autoload": {
"files": [
"src/Illuminate/Foundation/helpers.php"
],
"classmap": [
"hamcrest"
]
"psr-4": {
"App\\": "app/"
},
"psr-0": {
"Mockery": "library/"
}
},
1. files 写入 autoload_files.php
文件预加载,框架启动时便被 include ,通常文件中提供一些函数方法方便我们使用,如经常用的 dd()
。
"files:["src/Illuminate/Foundation/helpers.php"]"
写入/composer/autoload_files.php
,
autoload_files.php >
$vendorDir . '/laravel/framework/src/Illuminate/Foundation/helpers.php',
);
2.classmap 写入 autoload_classmap.php
直接映射文件真实路径。 这是简单粗暴的,因此这种方式效率是最高的。
"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 文件夹下所有文件
);
当 use Hamcrest\\Arrays\\IsArray;
时,可直接在 classmap 的数组中找到它,是不是很粗暴呢。
3. psr-4 写入 autoload_psr4.php
这是最常用的加载标准。"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
执行 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
缓存文件。
4.psr-0 写入 autoload_namespaces.php
和 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_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;
}
}
不够直观?
第二、三步原理简化版
'/www/wwwroot/laravel/app/User.php',
// '\\App\\Test' => '/www/wwwroot/laravel/app/Test.php',
);
public function set($namespace, $path){
$map[$namespace] = $path;
}
public function register() {
spl_autoload_register(array($this, 'loadClass'));
}
public function loadClass($class) {
if ($file = $this->findFile($class)) {
include $file;
return true;
}
}
public function findFile($class) {
return isset($map[$class]) ? $map[$class] : false;
}
}
$loader = new ClassLoader();
$loader->register();
$map = [
'\\App\\User' => '/www/wwwroot/laravel/app/User.php',
'\\App\\Test' => '/www/wwwroot/laravel/app/Test.php',
];
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$user = new \App\User();