ThinkPHP6 的自动加载
这是两篇参考文章
事实上,thinkphp 框架用的是 composer 的自动加载
1 项目入口文件引入 composer 自动加载的初始化类
public/index.php 中第一句
namespace think;
require __DIR__ . '/../vendor/autoload.php';
接下来 vender/autoload.php
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868::getLoader();
这里引入的 autoload_real.php 中定义的 ComposerAutoloaderInit 类名后面带了一串哈希值,可以这样做的原因可能是为了保证类名的唯一性,防止和用户自定义的类名冲突
2 getLoader 方法
2.1 单例
public static function getLoader()
{
// 单例模式,这个很好理解
if (null !== self::$loader) {
return self::$loader;
}
...
}
2.2 实例化ClassLoader核心类
public static function getLoader()
{
...
spl_autoload_register(array('ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit00730f6ba716ad33a51233e55145c868', 'loadClassLoader'));
...
}
loadClassLoader 方法
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
这里它先将本类的 loadClassLoader 注册为自动加载方法,用完后又注销掉,而 loadClassLoader 方法唯一的用处就是引入 ClassLoader.php 文件,为什么不直接引入而要绕这么一圈呢?
前面介绍 laravel自动加载的文章中有对这里做了解释,但是我还是看不明白
2.3 将autoload_static.php中定义的自动加载映射关系合并到ClassLoader类中
ClassLoader 类的 prefixLengthsPsr4、prefixDirsPsr4 等属性是私有的,要操作这些私有属性,这里有两种方式,第一种是使用匿名函数绑定 Closure::bind,第二种是调用 ClassLoader 类的公有方法
第二种方式很好理解,我们来看下第一种方式
require_once __DIR__ . '/autoload_static.php';
// ComposerStaticInit00730f6ba716ad33a51233e55145c868::getInitializer 方法返回一个匿名函数对象,call_user_func 执行这个匿名函数
call_user_func(\Composer\Autoload\ComposerStaticInit00730f6ba716ad33a51233e55145c868::getInitializer($loader));
看下 getInitializer 方法
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$prefixDirsPsr4;
$loader->fallbackDirsPsr0 = ComposerStaticInit00730f6ba716ad33a51233e55145c868::$fallbackDirsPsr0;
}, null, ClassLoader::class);
}
关于 Closure::bind 方法,文档中给的解释是:复制一个闭包,绑定指定的$this对象和类作用域。不太好理解
第一个参数是要绑定的匿名函数,这个容易理解
第二个参数是匿名函数中 $this 指代的对象,如果需要的话,方式是 new ClassName(),不需要就如文中传 null
第三个参数指定匿名函数的类作用域,简单来说就是如果匿名函数中有用到 private 或 protected 的属性或方法时,这里传入对应的类名,如$loader->prefixLengthsPsr4 是私用的,这里传的就是 $loader 实例对应的类名。如果将prefixLengthsPsr4
改为公有的,ComposerStaticInit...中的属性改为私有的,那这个参数就要填ComposerStaticInit...的类名了
我们知道,类对象的传递都是引用传递,所以这里匿名函数中修改 $loader 的属性值,ComposerAutoloaderInit...中定义的 $loader也修改了
2.4 PSR0 和 PSR4 的区别
在类名中使用下划线没有任何特殊含义
命名空间与文件目录的映射方法有所调整,假如我们有一个命名空间: Foo/class ,Foo 是顶级命名空间,其存在着用户定义的与目录的映射关系: "Foo/"=>"src/" 按照PSR0标准,映射后的文件目录是:src/Foo/class.php, 但是按照 PSR4标准,映射后的文件目录就会是:src/class.php
PSR4的命名空间最后一位必须是\
PSR0 的最后一个\后如果有_,将会转化为分隔符
2.5 将ClassLoader类的loadClass方法注册为自动加载函数
public function register($prepend = false)
{
// sql_autoload_register 注册的自动加载函数是一个队列的形式,如果你有自己的自动加载方法,需要在 composer的自动加载找不到的情况下调用,就用 sql_autoload_register 注册你的,第三个参数传false
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
2.6 loadClass方法中最主要的findFileWithExtension方法
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
// 先获取首字母,查找prefixLengthsPsr4中是否有匹配
// 截取最后一个分隔符之前的部分,查找prefixDirsPsr4中是否有匹配,这里用的while循环,没有找到的话就继续截取上一个分隔符前面部分
// 问题:为什么不是从prefixLengthsPsr4首字母对应的值中匹配?难道是因为prefixDirsPsr4的数量不多的原因么?
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
// 如果上面没有找到,就去extend文件夹下去找(对这里的fallbackDirsPsr4 来说就是这样的),TP的官方文档中也有说在extend中的每一个文件夹都是一个自定义根命名空间
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// 后面的是PSR0的匹配,就不看了
.
.
return false;
}
2.7 includeFile
注意下面的注释部分,这个函数被放在了类的外面,以防止引入的文件中调用$this或self
include和require的区别中有一点,require放在程序的最前面,include可以放在任意位置,所以这里需要用 include
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
这不就是 composer 的加载原理吗