深度分析thinkphp类的自动加载

类的自动加载是框架中非常重要的特性,它允许你在使用类时无需手动包含或引入对应的文件。类的自动加载实现起来很简单,只需这样的一个函数spl_autoload_register就能实现。但框架都有各自的加载规范,并不是所有类都能被自动加载,因此这节内容大家还可以了解到PSR-4的自动加载规范,另外也可以弄明白通过composer引入进来的类是如何被加载的。

带着我们的好奇心开始我们thinkphp源码之旅,打开入口文件public/index.php

require __DIR__ . '/../vendor/autoload.php';// 执行HTTP应用并响应
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

第一行代码就是载入composer的自动加载文件autoload.php,实现类的自动加载。

我们接下来重点研究一下autoload.php,在vender目录下可以找到该文件

if (PHP_VERSION_ID < 50600) {/**版本相关的限制,省略代码**/
}
// 引入autoload_real.php,这个类是由composer自动生产的
require_once __DIR__ . '/composer/autoload_real.php';// 调用该类里面的getLoader方法,这个类名有点长,这也是composer自动生成的
return ComposerAutoloaderInit71a72e019c6be19dac67146f3f5fb8de::getLoader();

接下来看看getLoader()方法做了什么

public static function getLoader()
{	// 如果$loader不为空,说明已经经过一些列的初始化了,就直接返回了if (null !== self::$loader) {return self::$loader;}// php版本相关的检查,这里就不细讲require __DIR__ . '/platform_check.php';// spl_autoload_register这个函数很重要,后面类的自动加载就是用这个函数,这里先给大家预热一波,这个函     // 数的用法,具体的大家可以看看文档:https://www.php.net/manual/zh/function.spl-autoload-          register// 这行代码的意思就是把当前类里的loadClassLoader函数作为__autoload 的实现,/*public static function loadClassLoader($class){if ('Composer\Autoload\ClassLoader' === $class) {// 其实就是引入当ClassLoader类require __DIR__ . '/ClassLoader.php';}}*/spl_autoload_register(array('ComposerAutoloaderInit71a72e019c6be19dac67146f3f5fb8de', 'loadClassLoader'), true, true);// new ClassLoader的时候,会自动执行前面装载的函数loadClassLoader,引入ClassLoader.php// 这样就实现了类的自动加载(引入)// 问题1:为什么这里不使用require直接引入,这样不是更简单一些吗??self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));	   // 这里是删除loadClassLoader,释放资源spl_autoload_unregister(array('ComposerAutoloaderInit71a72e019c6be19dac67146f3f5fb8de', 'loadClassLoader'));// 这个文件定义了一些变量,里面是Psr4的相关协议,后面类的自动加载的时候会使用到,等下会重点讲这个东西require __DIR__ . '/autoload_static.php';// 这段代码是执行一个回调函数,等下会让你看明白call_user_func(\Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::getInitializer($loader));$loader->register(true);$filesToLoad = \Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$files;$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;require $file;}}, null, null);foreach ($filesToLoad as $fileIdentifier => $file) {$requireFile($fileIdentifier, $file);}return $loader;
}

现在我们看看autoload_static.php文件内容

class ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de
{	// 使用composer加载进来的类,你可以使用composer require topthink/think-captcha// 引入验证码类,你会发现这里会多了一项内容,试试看!public static $files = array ('9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php','35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php','0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php','667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',);// 一时半会不知道怎么描述这个东西,只好直白一点了// $prefixLengthsPsr4是个二维数组,think\\trace\\这是命名空间,作为键名,然后长度作为值,注意这里// “\\”只能算一个字符,因为反斜杠是转义符,最外层是使用命名空间的第一个字符作为键名public static $prefixLengthsPsr4 = array ('t' => array ('think\\trace\\' => 12,'think\\' => 6,),// 省略部分代码);// 这个变量定义的是命名空间对应的目录,就是对目录进行归类,后面自动加载类的时候,只有满足了这些对应关系的     // 类才能被加载,后面你将深有体会public static $prefixDirsPsr4 = array ('think\\trace\\' => array (0 => __DIR__ . '/..' . '/topthink/think-trace/src',),'think\\' => array (0 => __DIR__ . '/..' . '/topthink/framework/src/think',1 => __DIR__ . '/..' . '/topthink/think-filesystem/src',2 => __DIR__ . '/..' . '/topthink/think-helper/src',3 => __DIR__ . '/..' . '/topthink/think-orm/src',),// 省略部分代码);// extend是不是很熟悉,自定义的类就是放在这个目录public static $fallbackDirsPsr0 = array (0 => __DIR__ . '/../..' . '/extend',);// 这个可以理解为缓存变量,后面也会用到public static $classMap = array ('Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',);// 这是一个初始化函数,实现对象之间的变量复制,简单的说就是把一个类里面的成员变量的值赋给另一个类public static function getInitializer(ClassLoader $loader){	// 这里返回的是一个Closure对象,Closure::bind后面很多地方都用到这个函数// 大家可以看官方文档:https://www.php.net/manual/zh/closure.bindreturn \Closure::bind(function () use ($loader) {// 这里的$loader其实就是ClassLoader类,这个函数的功能就是将当前类的这些成员变量的值赋值给// ClassLoader.php这个类里面的成员变量$loader->prefixLengthsPsr4 = ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$prefixLengthsPsr4;$loader->prefixDirsPsr4 = ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$prefixDirsPsr4;$loader->fallbackDirsPsr0 = ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$fallbackDirsPsr0;$loader->classMap = ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$classMap;}, null, ClassLoader::class);}
}

我们再回来看看这行代码

// call_user_func函数的作用就是把第一个参数作为回调函数调用,也就是说把Closure对象作为一个函数调用,实现
// 对象与对象之间的变量复制,call_user_func具体用法可以看文档:
// https://www.php.net/manual/zh/function.call-user-func
call_user_func(\Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::getInitializer($loader));

继续阅读源码

// 这个函数的核心代码
$loader->register(true);// 下面的代码是引入composer加载进来的类,获取autoload_static.php里面的$files
$filesToLoad = \Composer\Autoload\ComposerStaticInit71a72e019c6be19dac67146f3f5fb8de::$files;// 这里使用了内置函数Closure::bind定义了一个匿名函数,这个函数的作用其实就是引入相关类
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {// 判断全局变量是否有该类已经被引入的标识if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {// 存储一个标识,下次就不用重复引入$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;require $file;}
}, null, null);
// 这里就是一个循环调用,引入相关类
foreach ($filesToLoad as $fileIdentifier => $file) {$requireFile($fileIdentifier, $file);
}
return $loader;

这部分代码中,我们又接触到了Closure::bind,它绑定了一个静态的匿名函数,这函数里面的内容是这样的:先判断类是否被引入过,如果没有,则使用require引入,并且在全局变量中存储一个加载的标识。

简单的讲你可以把它看成一个函数,它就像你平时写的函数一样

function requireFile($fileIdentifier, $file){……………………………………
}   

其实很多人会有这样的一个疑问,为什么要使用Closure::bind,而不是直接在foreach里面写逻辑,另外写一个函数也行?答案就留给大家思考。

接下来就重点看看最核心的一个函数register(true)

public function register($prepend = false)
{	// 类的自动加载注册函数,一切逻辑都在loadClass这个函数里面spl_autoload_register(array($this, 'loadClass'), true, $prepend);if (null === $this->vendorDir) {return;}if ($prepend) {self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;} else {unset(self::$registeredLoaders[$this->vendorDir]);self::$registeredLoaders[$this->vendorDir] = $this;}
}

重点就在第一行代码,spl_autoload_register装载了loadClass这样一个函数

public function loadClass($class)
{	// 判断“被引入的类”文件是否存在if ($file = $this->findFile($class)) {// self::$includeFile当前类的成员变量,它是一个Closure对象,在初始化当前类里面就已经被定义了/*public function __construct($vendorDir = null){$this->vendorDir = $vendorDir;// 这里定义了这个Closure对象self::initializeIncludeClosure();}*/$includeFile = self::$includeFile;$includeFile($file);return true;}return null;
}

接下来我们看看$this->findFile($class)

public function findFile($class)
{// 判断当前类的成员变量classMap是否存储了“被引入类”的路径,这个变量的初始化内容其实就                	 // 是 autoload_static.php的$classMap if (isset($this->classMap[$class])) {return $this->classMap[$class];}// 判断“被引入类”是否存在,不存在直接返回falseif ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {return false;}// 这段代码其实就是从缓存中获取类的路径,目的就是提高框架的初始化速度,因为框架每次运行都要引入几十个类。if (null !== $this->apcuPrefix) {// 获取缓存内容,apcu_fetch函数大家可以看官方文档// https://www.php.net/manual/zh/function.apcu-fetch$file = apcu_fetch($this->apcuPrefix.$class, $hit);if ($hit) {return $file;}}// 这个函数的核心代码$file = $this->findFileWithExtension($class, '.php');// 这段代码是跟黑客相关的,防止黑客入侵一些hh类型文件if (false === $file && defined('HHVM_VERSION')) {$file = $this->findFileWithExtension($class, '.hh');}// 这里就是把加载类路径缓存起来if (null !== $this->apcuPrefix) {// apcu_add跟apcu_fetch一样,去看看官方文档apcu_add($this->apcuPrefix.$class, $file);}if (false === $file) {// 如果这个文件不存在,就存一个标识,下次就直接返回false即可$this->missingClasses[$class] = true;}return $file;
}

下面我们来看看这个函数中最核心的一行代码

$file = $this->findFileWithExtension($class, '.php');

进入findFileWithExtension

// 我们以一个例子来讲,new think\Exception()这是框架载入的第一个类,此时传进来
// 的$class是think\Exception
private function findFileWithExtension($class, $ext)
{	// $logicalPathPsr4 = think\Exception.php$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;// 获取第一个字符"t",为什么?$first = $class[0];// 判断prefixLengthsPsr4这个数组中是否存在“t”这个元素,这里的prefixLengthsPsr4就是我们前面提到		// psr4协议规范的内容,你可以打开autoload_static.php看看,很显然是存在的/*'t' => array ('think\\trace\\' => 12,'think\\captcha\\' => 14,'think\\' => 6,),*/if (isset($this->prefixLengthsPsr4[$first])) {$subPath = $class;while (false !== $lastPos = strrpos($subPath, '\\')) {$subPath = substr($subPath, 0, $lastPos);// 这里的目的就是得到think\\这样的一个命名空间$search = $subPath . '\\';// 那接下来就是找该命名空间下面的目录/*'think\\' => array (0 => __DIR__ . '/..' . '/topthink/framework/src/think',1 => __DIR__ . '/..' . '/topthink/think-filesystem/src',2 => __DIR__ . '/..' . '/topthink/think-helper/src',3 => __DIR__ . '/..' . '/topthink/think-orm/src',),*/if (isset($this->prefixDirsPsr4[$search])) {$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);foreach ($this->prefixDirsPsr4[$search] as $dir) {// 遍历这四个目录,看看是否可以找到think\Exception.phpif (file_exists($file = $dir . $pathEnd)) {// 最后返回F:\phpstudy_pro\WWW\thinkphp8\vendor// \composer/../topthink/framework/src/think\Exception.phpreturn $file;}}}}}// PSR-4 fallback dirsforeach ($this->fallbackDirsPsr4 as $dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {return $file;}}// 后面这部分代码是涉及到PSR-0,这里就不讲了,框架好像也并没有使用这种协议,但好像有个比较特别的地方// PSR-0 fallback dirs// 我们在autoload_static.php中看到$fallbackDirsPsr0这样一个变量而不是$fallbackDirsPsr4,// 这样很让人费解,我也不知道是什么原因// 这段代码其实就是定义了类的扩展目录,也就是说你自己的类放在extend这个目录里面会被框架自动加载foreach ($this->fallbackDirsPsr0 as $dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {return $file;}}return false;
}

相信看到这里,大家对类的自动加载有了一定的认识。

记得我刚才出来那会,犯过这样的一个错误,就是把一个项目中通过composer引入的类,复制到另一个项目,发现运行不了,阅读源码之后才发现了真实的原因.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/809046.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

fatal: Need to specify how to reconcile divergent branches.

报错 hint: You have divergent branches and need to specify how to reconcile them. hint: You can do so by running one of the following commands sometime before hint: your next pull: hint: hint: git config pull.rebase false # merge (the default strategy) hi…

最优算法100例之41-用两个栈实现队列

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 用两个栈实现队列 题解报告 stack<int> stack1; stack<int> stack2; void push(int node) {stack1.push(node);…

2024-4-11-arm作业

汇编实现三个灯的闪烁 源代码&#xff1a; .text .global _start _start: 时钟使能LDR r0,0x50000A28ldr r1,[r0]orr r1,r1,#(0x1<<4)str r1,[r0]设置PE10输出LDR r0,0x50006000ldr r1,[r0]bic r1,r1,#(0x3<<20)orr r1,r1,#(0x1<<20)str r1,[r0]设置PE1…

飞机降落蓝桥杯[2023蓝桥省赛B组]

2023蓝桥省赛B组 B题 飞机降落 题解 标准深搜板子题&#xff0c;难度不大 #include<bits/stdc.h> using namespace std; #define MAX 10 struct node{int t,d,l;//t:飞机到达时间 d:飞机最大盘旋时间 l:飞机降落所需时间bool v;//标记此架飞机是否被搜索过 用于剪枝 };…

OpenResty,Nginx实现接口验签与黑名单控制

介绍 nginx与openresty是两种优秀知名的7层负载均衡软件&#xff0c;nginx以其出色的性能和稳定性成为首选&#xff0c;而openresty则是在Nginx基础上构建的&#xff0c;支持嵌入Lua语言&#xff0c;大幅提升了开发效率。 安装OpenResty 版本 openresty-1.25.3.1-win64下载地…

brpc: bthread使用

使用bthread并发编程 #include <gflags/gflags.h> #include <butil/logging.h> #include <bthread/bthread.h>static void* func(void* args) {std::string* num static_cast<std::string*>(args);for(int i 0; i < 5; i) {LOG(INFO) << *…

mybatis的一对多

业务&#xff1a;通常主表从表 查询&#xff0c;一对多关系&#xff0c;通常是先查主表&#xff0c;然后拿主表的 关联字段与从表关联。在代码中 通常用for 循环等方法给 从表的数据赋值&#xff0c;很麻烦&#xff0c;&#xff0c;&#xff0c;很麻烦。。。。 用mybatis的…

一个PDF文件含有多篇不同的内容,如何把这些内容分离出来?

一&#xff0c;PDF的含义 PDF&#xff0c;全称Portable Document Format&#xff0c;即便携式文档格式&#xff0c;是一种由Adobe Systems开发的文件格式&#xff0c;用于呈现文档&#xff0c;包括文本、图像、向量图形、字体、颜色、页面布局等&#xff0c;并可在不同的操作系…

FPGA在医疗的应用,以4K医疗内窥镜为例

前言 随着技术的发展&#xff0c;医学影像作为科学技术的主要成就之一&#xff0c;在无创诊断和治疗领域已经有了多种应用。其中一个应用是内窥镜&#xff0c;在20世纪90年代&#xff0c;当利用电荷耦合装置将图像传输到显示器上成为可能时&#xff0c;内窥镜变得更加广泛。为…

怎么做预约小程序_探索我们的全新预约小程序

在繁忙的现代生活中&#xff0c;无论是想预约一次美容护理&#xff0c;还是预定一家心仪的餐厅&#xff0c;亦或是安排一次专业的咨询服务&#xff0c;我们都希望能够在最短的时间内完成这些操作&#xff0c;节省时间和精力。如今&#xff0c;一款全新的预约小程序应运而生&…

C语言——实践小游戏(贪吃蛇)代码版

大家好久不见&#xff0c;我是残念我回来了&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流 本文由&#xff1a;残念ing原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&#x…

定期与设定域名地址交互工具

下面是一个简单的C语言客户端示例&#xff0c;它会定期解析一个域名&#xff0c;然后与该域名解析得到的IP地址的4399端口建立TCP连接。客户端会持续监听来自服务器的命令&#xff0c;执行这些命令&#xff0c;并将执行结果返回给服务器。 请注意&#xff0c;这个示例没有包含…

【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试之【回溯】2023C-加密算法【欧弟算法】全网注释最详细分类最全的华为OD真题题解

有LeetCode算法/华为OD考试扣扣交流群可加 948025485 可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1336了解算法冲刺训练 文章目录 题目描述与示例题目描述输入描述输出描述示例一输入输出 示例二输入输出 解题思路代码pythonJavaC时空复杂度 华为OD算法…

从 Oracle 到 MySQL 数据库的迁移之旅

文章目录 引言一、前期准备工作1.搭建新的MySQL数据库2 .建立相应的数据表2.1 数据库兼容性分析2.1.1 字段类型兼容性分析2.1.2 函数兼容性分析2.1.3 是否使用存储过程&#xff1f;存储过程的个数&#xff1f;复杂度&#xff1f;2.1.4 是否使用触发器&#xff1f;个数&#xff…

Paper Reading: MixTeacher:半监督目标检测中利用混合尺度教师挖掘有前景的标签

目录 简介目标/动机工作重点方法训练 实验总结 简介 题目&#xff1a;《MixTeacher: Mining Promising Labels with Mixed Scale Teacher for Semi-Supervised Object Detection》&#xff0c; CVPR 2023 日期&#xff1a;2023.3.16 单位&#xff1a;腾讯&#xff0c;上海交…

竞赛 图像识别-人脸识别与疲劳检测 - python opencv

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是…

Android MVVM架构学习——ViewModel DataBinding

关于MVVM架构&#xff0c;我并不想花篇幅去做重复性的描述&#xff0c;网上一搜都是一堆讲解&#xff0c;大家可以自行了解&#xff0c;我所做的只是以最简单的例子&#xff0c;最有效的步骤&#xff0c;从零开始&#xff0c;去实现一个相对有点学习参考价值的项目。 先来看本…

安卓开发LinearLayout的属性极其用法

线性布局&#xff08;LinearLayout&#xff09;是 Android 开发中常用的布局之一&#xff0c;它可以按照水平&#xff08;horizontal&#xff09;或垂直&#xff08;vertical&#xff09;方向排列子视图。以下是线性布局的一些常用属性和用法&#xff1a; 1. **android:orient…

计算机网络——NAT技术

目录 前言 前篇 引言 SNAT&#xff08;Source Network Address Translation&#xff09;源网络地址转换 SNAT流程 确定性标记 DNAT&#xff08;Destination Network Address Translation&#xff0c;目标网络地址转换&#xff09; NAT技术重要性 前言 本博客是博主用于…

windows10下Linux子系统(ubuntu22.04)安装docker(二进制)和kubectl

下载安装docker wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.6.tgz tar xf docker-20.10.6.tgz chown -R root.root docker cp docker/* /usr/bin/ groupadd docker mkdir /var/lib/docker /etc/docker配置文件 vim /etc/docker/daemon.json …