内容简介:Laravel 之道第三章:步调 Composer 自动加载原理
开始前导语
Step.001
: 指当前调试所在步骤
项目根目录/public/index.php
: 当前调试的 PHP 文件位置
#10
: 当前调试行所在文件的行号
Step.001--项目根目录/public/index.php
#10
define('LARAVEL_START', microtime(true));
- 源码理解
执行当前行,定义了一个 LARAVEL_START
的常量,并赋值了小数点化的微秒数。作用就是记录 Laravel 启动的时间点
- 知识扩展
Step.002--项目根目录/public/index.php
#24
require __DIR__.'/../vendor/autoload.php';
- 源码理解
执行当前行,将导入 项目根目录/vendor/autoload.php
文件,并执行此文件,将此文件中的变量、类、函数注入 PHP 运行内存中。
- 额外注意
在 PhpStrom 步调工具,如果点击步入函数按钮,将进入 项目根目录/vendor/autoload.php
中进行步调;如果点击跳过函数按钮,将在当前文件调到下一有效行,并把 项目根目录/vendor/autoload.php
中的变量、类、函数直接注入内存中,无论 项目根目录/vendor/autoload.php
内部调用多少类、多少函数,直接全部执行完毕;
在这里,我们用步入按钮,即进入 项目根目录/vendor/autoload.php
中
- 知识扩展
__DIR__
: 返回当前文件所在绝对路径,记住是当前文件哦;如果 index.php
包含其它路径的 PHP 文件,而这个其它路径的 PHP 文件中的 __DIR__
返回不是 index.php
所在路径,而是那个其它路径的 PHP 文件所在的绝对路径
Step.003--项目根目录/vendor/autoload.php
#05
require_once __DIR__ . '/composer/autoload_real.php';
- 执行结果
进入 项目根目录/vendor/composer/autoload_real.php
。由于此文件只有一个类和一个函数,文件执行后,仅仅将类和函数注入内存,以便之后的实例化和调用;因此,此步用跳过函数按钮
Step.004--项目根目录/vendor/autoload.php
#07
return ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd::getLoader();
- 源码理解
整个 项目根目录/vendor/autoload.php
返回了 ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd::getLoader()
静态方法的返回值。而 ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd
类就在上一步 项目根目录/vendor/composer/autoload_real.php
文件中,因此,此处调用此类的静态方法,用步入按钮进入 autoload_real.php
文件中,并执行 getLoader
静态方法,查看具体如何执行的
Step.005之前先看一眼 项目根目录/vendor/composer/autoload_real.php
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'));
$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\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::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\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
Step.005--项目根目录/vendor/composer/autoload_real.php
#18
if (null !== self::$loader) {
return self::$loader;
}
18 行是 if 语句所在行
- 源码理解
这段是检测当前类中 $loader
静态属性有没有赋过值,就是类似一种缓存检测读取,用来提升项目整体效率,防止重复实例化,也可以理解成单例模式。由于第一次运行,$loader
肯定是 null
。if 里面的语句不执行。
Step.006--项目根目录/vendor/composer/autoload_real.php
#22
spl_autoload_register(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'), true, true);
- 源码理解
将 ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd::loadClassLoader($class)
静态方法,注入到 spl_autoload_register 自动加载队列中,并放入队列首部,之后 new 一个不存在内存的类时会优先执行此加载方法。
spl_autoload_register() 理解:
第一个参数: 这是一个包含类和静态方法的数组;意思指,如果以后要 new 一个不存在内存中的类,会把这个全类名(包含命名空间)作为参数传入 loadClassLoader
静态方法中,并执行静态方法。
第二个参数:true
时,开启异常提示,当 spl_autoload_register() 出现错误,会抛出异常
第三个参数: true
时,将第一个参数(那个数组)放入自动加载队列之首,这样会优先执行的
- 知识扩展
Step.007--项目根目录/vendor/composer/autoload_real.php
#23
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
- 源码理解
此行等同于
$loader = new \Composer\Autoload\ClassLoader();
self::$loader = $loader;
重点看 new \Composer\Autoload\ClassLoader()
,记得上一步注册的自动加载函数吗。当前 new 的就是一个不存在内存中的类,因此会把 \Composer\Autoload\ClassLoader
这个类的全类名传入到 loadClassLoader
静态方法中,并执行。注意:\Composer\Autoload\ClassLoader
要去掉开头的 \
哦,即 Composer\Autoload\ClassLoader
,别问为什么, PHP 就是这样规定的,加载参数的全类名不包含开头的 \
- 执行结果
进入下列方法中
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
Step.008--项目根目录/vendor/composer/autoload_real.php
#11 & #12
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
11 行是 if 所在行
- 源码理解
如果 Composer\Autoload\ClassLoader
全等于 $class
,则执行 require __DIR__ . '/ClassLoader.php';
。因为由上一步得知 $class
就是 Composer\Autoload\ClassLoader
故执行 if 里面的语句
- 执行结果
进入 项目根目录/vendor/composer/ClassLoader.php
, 并开始执行。因为这个文件就是一个类和一个函数,故跳过,直接将其注入内存,以后实例化后,在进去看看
Step.009--项目根目录/vendor/composer/autoload_real.php
#24
spl_autoload_unregister(array('ComposerAutoloaderInit5df9ac896280b53fb5464c83eae493fd', 'loadClassLoader'));
- 源码理解
将 loadClassLoader
静态方法,从 PHP 自动加载队列中删除掉
Step.010--项目根目录/vendor/composer/autoload_real.php
#26
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
- 源码理解
可以看出,$useStaticLoader
是一个布尔值。
第一个表达式 PHP_VERSION_ID >= 50600
: 由于我用的是 PHP7.2 故,此表达式是 true
,与运算未短路,执行下一表达式
第二个表达式 !defined('HHVM_VERSION')
: 没有用 HHVM 虚拟机则返回 true
,我的确没用,未短路,继续下一个表达式
第三个表达式 (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded())
分解如下
第三个表达式中第一个表达式 !function_exists('zend_loader_file_encoded')
: 如果不存在 zend_loader_file_encoded
函数返回true。经过测试我 PHP 中的确没有 zend_loader_file_encoded
函数,故与第三表达式中第二表达式短路。
整体返回 true
Step.011--项目根目录/vendor/composer/autoload_real.php
#28 & #30
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::getInitializer($loader));
} else {
// 不执行,先不用看了
}
28 行是 if 下一行
- 源码理解
执行 项目根目录/vendor/composer/autoload_static.php
,将里面的 ComposerStaticInit5df9ac896280b53fb5464c83eae493fd
类注入内存。然后调用这个类的 getInitializer
静态方法,并将 $loader
作为参数传入进去。getInitializer
静态方法将会返回一个 Closure
闭包函数(或称闭包对象)。然后通过 call_user_func()
执行此闭包函数。
看一下 getInitializer
静态方法样子
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixesPsr0;
$loader->classMap = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$classMap;
}, null, ClassLoader::class);
}
getInitializer(ClassLoader $loader)
: 中的 ClassLoader 是类型约束的意思。因为 项目根目录/vendor/composer/autoload_static.php
所在的命名空间是 Composer\Autoload
,并且没有关于 ClassLoader 类的 use 语句,因此 $loader
必须是 Composer\Autoload\ClassLoader
类的对象。否则报错
- 关于闭包函数(闭包对象)
PHP 里面实现像 JavaScript 中的闭包,是通过一个内置对象实现的,这个对象是 Closure
。
凡是对象都有属性和方法,同样 Closure
对象也有属性和方法。你肯定会问,既然是对象,怎么能通过 Object()
或者 call_user_func('Object')
来执行呢。那是因为 Closure
有一个魔术方法 __invoke()
。关于此方法看这 __invoke()魔术方法
- 关于闭包函数中的 use
function () use ($loader) {}
因为闭包函数内与外是不同的空间,因此内部无法直接使用外部的变量的。只能通过 use 将外部的变量存放到 Closure
对象的 static
属性中,是以数组的形式存放的哦。这样在执行 __invoke()
时,就调用 static
中的键值对就可以了
-
注:$this 无需使用 use 引用可直接在
Closure
对象中使用。这个我理解的是,可能在类中声明的闭包对象默认继承自当前类,不知道对不 - 关于闭包函数的参数
会存放在闭包对象的 parameter
属性中,也是以数组的形式存放
- 一个闭包对象的数据详情
Step.012--项目根目录/vendor/composer/autoload_static.php
#3710 到 #3713
$loader->prefixLengthsPsr4 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$prefixesPsr0;
$loader->classMap = ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$classMap;
- 源码理解
将 ComposerStaticInit5df9ac896280b53fb5464c83eae493fd
类中关于 Psr4
Psr2
classMap
静态属性赋值给 ClassLoader
类对象 $loader
中的属性中。这些值都是一些全类名与类所在的绝对路径的键值对数组,例如 classMap
Step.013--项目根目录/vendor/composer/autoload_real.php
#48
$loader->register(true);
- 源码理解
调用 $loader
对象的 register
方法,将 true
传入。即进入 项目根目录/vendor/composer/ClassLoader.php
文件的第 302 行
Step.014--项目根目录/vendor/composer/ClassLoader.php
#302
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
302 行是方法体内的唯一一行
- 源码理解
将当前对象 $loader
的 loadClass
方法,注入 PHP 自动加载队列中,可以抛出异常,放入队列尾部
- 关于
loadClass
方法如何执行,后期实例化容器类的时候再看吧
Step.015--项目根目录/vendor/composer/autoload_real.php
#50
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd::$files;
} else {
// 这里不执行,不用看了
}
50 行是 if 所在行
- 源码理解
把 Composer\Autoload\ComposerStaticInit5df9ac896280b53fb5464c83eae493fd
类中的 $files
属性取出赋值给 $includeFiles
,看一下 $includeFiles
值的样子
Step.016--项目根目录/vendor/composer/autoload_real.php
#55
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file);
}
- 源码理解
遍历 $includeFiles
数组,将数组的 key 和 value 作为参数来调用 composerRequire5df9ac896280b53fb5464c83eae493fd
函数
Step.017--项目根目录/vendor/composer/autoload_real.php
#63
function composerRequire5df9ac896280b53fb5464c83eae493fd($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
- 源码理解
通过上一步,得知,此函数将被遍历执行数次,分别将 $includeFiles
数组的值(文件)包含执行。并设定 $GLOBALS['__composer_autoload_files']
全局键名,来记录上面的文件是否被包含执行过,防止重复执行。
- 关于包含了那些文件
项目根目录/vendor/symfony/polyfill-mbstring/bootstrap.php
: Symfony 组件根据 PHP 的 mb_string 扩展定义了一些字符串处理的函数
项目根目录/vendor/symfony/polyfill-php72/bootstrap.php
: 如果 PHP 版本小于7.2,则此文件会定义7.2版本新特性函数供低版本的 PHP 使用
项目根目录/vendor/symfony/var-dumper/Resources/functions/dump.php
: 传说中 dump 和 dd 函数就在这里面哦
项目根目录/vendor/symfony/polyfill-ctype/bootstrap.php
: 如果 PHP 内置没有ctype 系列函数, 则定义的这些函数
项目根目录/vendor/swiftmailer/swiftmailer/lib/swift_required.php
: 注册了 Swift 相关类的自动加载函数
项目根目录/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
: Laravel 的相关辅助函数
项目根目录/vendor/laravel/framework/src/Illuminate/Support/helpers.php
: 也是 Laravel 的相关辅助函数
项目根目录/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php
: 定义一个深度拷贝功能的一个函数,执行此函数返回是深度拷贝的相关服务对象
项目根目录/vendor/psy/psysh/src/functions.php
: psy 相关的一些辅助函数
Step.018--项目根目录/vendor/composer/autoload_real.php
#59
return $loader;
- 源码理解
返回到第 4 步,由第 4 步返回到第 2 步,回到 项目根目录/public/index.php
。因为 index.php
并没有接受返回值,所以 return $loader
在这里是没有意义的;不过我们可以在 index.php
文件中去到 $loader
的值,然后对 $classMap
填充,来实现自定义目录的类自动加载哦。
例如,我定义一个类,放到 项目跟目录/MyClass
目录下
<?php
namespace MyClass;
class My {
// ...
}
我只需要在 index.php
做如下修改,即可实现自己随意定义的目录和类
$loader = require __DIR__.'/../vendor/autoload.php';
$loader->addClassMap([
'MyClass\\My' => __DIR__. '/..' . '/MyClass/My.php',
]);
不建议上面这种做法,你应该将你的服务类注册到 Laravel 中,从服务中去取更好一些。当然如果遇到非要从自己定义的路径和文件中取类,可以采用上面这种方式
键 MyClass\\My
: \\
两个反斜杠是转意\
意思,将其以字符串形式显示。
我们是一群被时空压迫的孩子。 ---- 爱因斯坦
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Laravel 之道第二章:关于 PhpStrom 步调工具的介绍
- Laravel 之道第四章:步调 Laravel 容器 Application 的初始化
- 介绍同步加载、异步加载、延迟加载[原创]
- .net加载失败的程序集重新加载
- 虚拟机类加载机制:类加载时机
- 探秘类加载器和类加载机制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
哥德尔、艾舍尔、巴赫
[美] 侯世达 / 严勇、刘皓明、莫大伟 / 商务印书馆 / 1997-5 / 88.00元
集异璧-GEB,是数学家哥德尔、版画家艾舍尔、音乐家巴赫三个名字的前缀。《哥德尔、艾舍尔、巴赫书:集异璧之大成》是在英语世界中有极高评价的科普著作,曾获得普利策文学奖。它通过对哥德尔的数理逻辑,艾舍尔的版画和巴赫的音乐三者的综合阐述,引人入胜地介绍了数理逻辑学、可计算理论、人工智能学、语言学、遗传学、音乐、绘画的理论等方面,构思精巧、含义深刻、视野广阔、富于哲学韵味。 中译本前后费时十余年,......一起来看看 《哥德尔、艾舍尔、巴赫》 这本书的介绍吧!