> ### ThinkPHP实现类的加载核心函数 **spl_autoload_register()**
首先我们来看看魔术方法\_\_autoload(),这是一个自动加载函数,在PHP5中,当我们实例化一个未定义的类时,就会触发此函数。
Goods类
```
class Goods
{
public function order()
{
return 'phone'.PHP_EOL;
}
}
```
test_autoload.php 文件
```
function __autoload($class_name = "") {
echo 'class:' . $class_name . PHP_EOL;
include "./{$class_name}.php";
}
$goods = new Goods();
echo $goods->order();
```
执行结果
```
bash-5.0# php test_autoload.php
class:Goods
phone
```
cli 模式运行 `test_autoload.php` 后正常打印 phone。在` test_autoload.php` 中,由于没有包含Goods.php,在实例化Goods类时,自动调用`_autoload`函数,参数`$class_name`的值即为类名`Goods`,此时`Goods`就被引进来了。在面向对象中这种方法经常使用,可以避免书写过多的引用文件。
>[danger] 注意:`__autoload` 自PHP 7.2.0起已弃用此功能 ,[更多了解/function.autoload.php](https://www.php.net/manual/zh/function.autoload.php)
test_autoload_register.php 文件
```
spl_autoload_register('autoload', true, true);
function autoload($class_name = '')
{
echo 'class:' . $class_name . PHP_EOL;
include "./{$class_name}.php";
}
$goods = new Goods();
echo $goods->order();
```
执行结果
```
bash-5.0# php test_autoload_register.php
class:Goods
phone
```
分析:
将魔术方法`__autoload`换成`autoload`函数。`autoload()`只是一个普通的函数,不会像`__autoload`自动触发,这时`spl_autoload_register()`就起作用了,它告诉PHP碰到没有定义的类就执行`autoload()`函数。
>[warning] `spl_autoload_register` — 注册给定的函数作为 __autoload 的实现,[更多]([https://www.php.net/manual/zh/function.spl-autoload-register.php](https://www.php.net/manual/zh/function.spl-autoload-register.php))
> ### 入口文件
在入口文件中引入了 base.php
```
namespace think;
// 载入Loader类
require __DIR__ . '/library/think/Loader.php';
// 注册自动加载
Loader::register();
...
```
Loader::register() 方法
```
// 注册自动加载机制
public static function register($autoload = '')
{
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
// 变量值:/var/www/wiot.tinywan.com/
$rootPath = self::getRootPath();
self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
// Composer自动加载支持
if (is_dir(self::$composerPath)) {
if (is_file(self::$composerPath . 'autoload_static.php')) {
require self::$composerPath . 'autoload_static.php';
//require $rootPath . 'wiot.tinywan.com/class_autoload/Goods.php'; // 引入自定义类
$declaredClass = get_declared_classes();
// 返回由当前文件中已引入类的名字组成的数组
// $declaredClass数组中的最后一个Composer\Autoload\ComposerStaticInitafad3d1f17f029da67a39c37bf199ba7
$composerClass = array_pop($declaredClass);
foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
if (property_exists($composerClass, $attr)) {
// 该方法用于检测类是否存在指定的属性
self::${$attr} = $composerClass::${$attr};
// 存在则将composer安装的类的私有属性,赋值给本类对应的私有属性
}
}
} else {
self::registerComposerLoader(self::$composerPath);
}
}
// 注册命名空间定义 将think和traits放到 prefixLengthsPsr4 和 prefixDirsPsr4属性中
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
// 使用dirname()方法返回路径的目录部分
'scource' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'scource',
// 实现自定义目录下面的类自动加载
]);
// 加载类库映射文件
// 1、默认情况下在runtime目录下是没有classmap.php的
// 2、可通过命令 php think optimize:autoload 生成 可以提升性能
if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
}
// 自动加载extend目录
self::addAutoLoadDir($rootPath . 'extend');
// 实现自定义目录下面的类自动加载
self::addAutoLoadDir($rootPath . 'scource');
}
```
分析:
1、base.php 中调用`register`方法时传参为空,所以`$autoload = ''`,所以Loader类会调用本类的`autoload()`方法,这里跟上面将的原理是一样的,`autoload()`方法我们后面分析。
```
// 自动加载
public static function autoload($class)
{
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
if ($file = self::findFile($class)) {
// Win环境严格区分大小写
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
}
```
2、composer安装的扩展类库加载,会找到`composer/autoload_static.php`,这里可以引入我们想要自动加载的类,例如代码中引入了我们刚刚自定义的`Good`类,这是引入自定义类的一种方法,后面我们还会介绍其他方法。
3、`$declaredClass = get_declared_classes()` 返回一个数组,数组是当前文件中已引入类的名字,可以自行断点调试,这里我贴上部分结果
```
Array
(
[0] => stdClass
[1] => Exception
[2] => ErrorException
[3] => Error
[74] => ParentIterator
[91] => RecursiveDirectoryIterator
[105] => PDOException
[106] => PDO
[107] => PDOStatement
[133] => Redis
[134] => RedisArray
[135] => RedisCluster
[136] => RedisException
[137] => RedisClusterException
[138] => AMQPConnection
[139] => AMQPChannel
[140] => AMQPQueue
[141] => AMQPExchange
[153] => PharException
[158] => mysqli_driver
[159] => mysqli
[166] => Swoole\Error
[192] => Swoole\Coroutine\Iterator
[248] => Swoole\Redis\Server
[250] => Yaconf
[251] => think\Loader
[252] => Composer\Autoload\ComposerStaticInit0ec10a4d3a3df1a88bd61e927cf732f4
)
```
4、`$composerClass = array_pop($declaredClass); `获取到`$declaredClass`数组的最后一个,然后进行一个循环,循环体中检查`autoload_static.php`类中是否存在指定的属性,存在则`composer`安装的类的私有属性赋值给本类对应的私有属性,这样Loader类的成员变`$prefixLengthsPsr4`和`$prefixDirsPsr4`都有了`autoload_static.php`中对应的值了
autoload_static.php
```
class ComposerStaticInitafad3d1f17f029da67a39c37bf199ba7
{
public static $files = array (
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
);
/**
* @var array
* 说明: 1.数组的键是值的首字母 例如 't' 是 'think\\composer\\'和 'think\\'的首字母
* 2.对应的数字表示 'think\\composer\\'和 'think\\'的长度,注意两个\\中有一个是转义字符 不会被计算到长度中
*/
public static $prefixLengthsPsr4 = array (
't' =>
array (
'think\\composer\\' => 15,
'think\\' => 6,
),
'a' =>
array (
'app\\' => 4,
),
);
/**
* @var array
* 说明:定义$prefixLengthsPsr4中定义的类的目录地址
*/
public static $prefixDirsPsr4 = array (
'think\\composer\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-installer/src',
),
'think\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-helper/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/application',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitafad3d1f17f029da67a39c37bf199ba7::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitafad3d1f17f029da67a39c37bf199ba7::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}
```
4、注册命名空间定义,此时$namespace的值为
```
Array
(
[think] => /var/www/wiot.tinywan.com/thinkphp/library/think
[traits] => /var/www/wiot.tinywan.com/thinkphp/library/traits
[scource] => /var/www/wiot.tinywan.com/thinkphp/library/scource
)
```
说明一下,这里的scource又是一种我们加入自定义类的方法
```
// 注册命名空间
public static function addNamespace($namespace, $path = '')
{
print_r($namespace);die;
if (is_array($namespace)) {
foreach ($namespace as $prefix => $paths) {
self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true);
}
} else {
self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true);
}
}
```
循环`$namespace`调用`addPsr4`方法将`think`和`traits`放到 `prefixLengthsPsr4 `和 `prefixDirsPsr4`属性中
5、加载类库映射文件,默认情况下在`runtime`目录下是没有`classmap.php`的,可通过命令 `php think optimize:autoload `生成, 可以提升性能,当Loader类调用`findFile()`方法时,如果生成了`classmap.php`文件就直接return了,不然就要一次执行`findFile()`方法下面的查看文件的方法。
6、自动加载`extend`目录, 会将扩展目录`extend`目录地址存放在Loader类的$fallbackDirsPsr4 成员属性中,这里就衍生出我们的第三种自定义类实现自动加载的方法,我们在项目根目录中定义`scource`目录,在其中定义我们的自定义类,在定义的时候要注意命名空间。
(1)自定义目录下面的类自动加载(二级)
```
// source/tinywan/Email.php
namespace tinywan;
class Email
{
public static function send()
{
echo 'scource-tinywan-Email-send ' . PHP_EOL;
}
}
```
(2)自定义目录下面的类自动加载(一级)
```
// source/Sms.php
class Sms
{
public static function send()
{
echo 'scource-SMS-send' . PHP_EOL;
}
}
```
(3)自定义目录下面的类自动加载(一级)
```
//thinkphp/library/source/EmailSend.php
namespace source;
class EmailSend
{
public static function send()
{
echo 'nikki-push' . PHP_EOL;
}
}
```
测试类
```
class Test extends Controller
{
/**
* @desc: 类的自动加载测试用例
* 1、Email::send() 和 Sms::send() 是通过 $fallbackDirsPsr4 实现
* 2、EmailSend::send() 是通过 $prefixLengthsPsr4 $prefixDirsPsr4 实现
*/
public function autoLoad()
{
Email::send();
\Sms::send();
EmailSend::send();
}
}
```
- 序言
- 专题一 PHP基础教程
- 1、empty、isset、is_null的用法
- 2、线程安全与非线程
- 3、大文件上传需要修改的配置
- 10、魔术方法
- 4、编译安装PHP7
- 5、编译安装PHP7.4
- 6、PECL 安装 PHP 扩展库
- 专题二 PHP高级教程
- 1、类和对象
- 2、继承
- 3、魔术方法
- 4、抽象类
- 5、接口
- 6、反射机制实现自动依赖注入
- 7、服务容器与依赖注入的思想
- 8、并发解决方案之opcache
- 9、Composer自动加载原理
- 1、安装与使用
- 10、抽象类和接口的区别
- 11、self和static的区别
- 12、PHP7 变量
- 13、PHP8.3 错误 Error 和 异常 Exception 树列表
- 专题三 ThinkPHP6专题
- 1、DI容器
- 2、AUTH权限认证
- 3、Nginx URI重写方式
- 4、并发锁问题
- 5、自定义全局异常
- 6、CLI 模式跨模块查询数据库
- 7、数据库优化方案
- 附录一 常见错误
- 附录二 自定义分页类
- 附录三 cropper.js图片上传和裁剪
- 附录四 数据库和模型源码解读
- 1、Db类
- 2、查询构造器
- 3、模型
- 附录五 权限认证Auth类
- 附录六 ThinkPHP5.1 源码分析
- (一)类的自动加载
- (二)配置文件Config类
- (三)容器Container类
- (四)门面Facade
- (五)框架执行流程
- (六)路由解析
- 附录七 官方扩展
- 1、think-queue 队列
- 附录八 易错整理
- 附录九 问题列表
- 附录十 任务队列异步通过视图导出PDF
- 专题四 Docker教程
- 1、Docker安装
- 2、如何在本地构建镜像
- 3、镜像、容器以及命令操作
- 4、容器进入的4种方式
- 5、Dockerfile常用指令详解
- 6、发布自己的镜像
- 7、数据卷管理
- 8、docker-compose概念
- 9、docker-compose入门
- 10、如何构建docker-compose
- 11、Docker网络
- 12、搭建私有仓库
- 13、Docker部署方式
- 14、推送到Github仓库
- 附录一 PHPStrom 调试XDebug
- 附录二 安装RabbitMQ
- 附录 日常使用笔记
- 附录四 Docker 调试XDebug
- 专题五 Redis教程
- 1、编译安装
- 2、配置文件详解
- 3、Lua 脚本的应用和实践
- 4、Redis实现分布式锁(集群版)
- 5、Redis键空间通知
- 6、Redis5.0 搭建集群
- (1)、创建和使用Redis群集
- (2)、新增节点
- 7、限流器的实现
- 8、Redis5.0 新特性
- 8.1、注意要点
- 8.2 xreadgroup 命令
- 9、延迟任务队列
- 10 Stream 消息队列
- 11、基于 Redis 的 Stream 类型的完美消息队列解决方案
- 12、Streams 实现延迟消息队列
- 13、Stream流三种ACK机制
- 14、read error on connection排查
- 附录一 常见问题
- 附录二 Redis面试大全
- 附录三 有序集合使用场景
- 附录四 Lua脚本调试
- 附录五 高性能、高可扩展关键技术
- 专题六 MySQL教程
- 1-1、二进制安装
- 1-2、安装包安装(推荐)
- 2、索引、锁、事务
- 3、字符集
- 4、导出导入数据
- 5、5.7版本兼容性
- 6、数据库自动备份
- 7、如何重置MySQL 5.7 root密码
- 8、MySQL自动完成和语法突出
- 9、普通索引和唯一索引的区别
- 10、深入了解行锁、表锁、索引
- 11、索引数据结构
- 12、MySQL规范
- 13、开发高频面试题精选(重要)
- 14、锁专题
- 1、可重复读(REPEATABLE_READ)
- 2、事务隔离级别概述
- 15、MySQL外键约束
- 16、left join 查询
- 17、 MySQL设计三范式和反范式
- 18、性能分析-Profiling
- 19、查询好慢,除了索引,还能因为什么?(重要)
- 20、常用日期字段
- MYSQL 5.7 VARCHAR 类型详解
- 专题七 Nginx教程
- 1、什么是Nginx?
- 2、编译安装
- 3、日志配置和模块讲解
- 4、静态资源和缓存服务
- 5、正向和反向服务
- 6、Rewrite规则
- 7、HTTP负载均衡(七层)
- 8、TCP负载均衡(四层)
- 9、如何配置HTTPS服务
- 10、Nginx的负载均衡算法
- 11、如何配置http和https同时访问
- 12、灰度发布
- 13、常见负载均衡算法
- 14、Openresty 专题
- 15、如何改进 NGINX 配置文件节省带宽?
- 16、谈谈基于 OpenResty 的接口网关设计
- 附录一 阿里云负载均衡配置
- 附录二、基础配置文件
- nginx.conf
- 附录三、Nginx+lua+Memcache 实现灰度发布
- 附录四 视频监控RTSP转HLS解决方案
- 附录五 Openresty 编译
- 附录六 Vod模块
- 1、本地模式
- 2、映射模式
- 专题八 Git版本管理
- 1、Git 基础知识
- 2、团队分支模型
- 3、储藏与清理
- 4、如何同步Fork
- 5、多Git账户id_rsa私钥
- 6、高效规范使用Git
- 7、远程分支的创建
- 8、GitFlow工作流
- 9、Git撤销&回滚操作(git reset 和 get revert)
- 10、合并时 --no-ff 的作用
- 11、 删除本地、远程、缓存分支
- 13、Git和Windows的大小写不敏感产生的问题
- 附录一 、一次记录
- 附录二、常用工作流程
- 附录三、每次更新代码都要输入用户名密码
- 附录四、OEM版本控制
- 附录五 常用记录
- 1、查看某一个文件修改的具体内容
- 2、强制推送到远程分支
- 3、生产环境代码回滚
- 附录六 三年 Git 使用心得 & 常见问题整理
- 附录七 Git 忽略文件,不提交文件 清空缓存
- 12/找回历史删除分支
- 专题九 WorkerMan服务
- 3、SocketIO消息推送
- 4、master和worker模型
- 5、GatewayWorker
- 6、使用systemd管理workerman
- 7、TCP长连接应用GatewayWorker心跳检测
- 附录一 运行问题
- 附录二 问题与解决方法
- 专题十 MQ消息中间件
- 1、为什么要使用消息队列
- 2、RabbitMQ
- 『1』AMQP核心概念
- 『2』交换机模式讲解
- 『3』RabbitMQ高级特性
- (1)hello
- 3、NSQ
- 4、RabbitMQ延迟队列
- 附件一 RabbitMQ 注意要点
- 5、RocketMQ PHP 生产端和消费端代码优雅实现
- 专题十一 PHP函数整理
- 1、系统函数
- 2、自定义函数
- 3、回调函数
- 4、匿名函数
- 5、递归函数
- 6、常用函数库
- 7、call_user_func函数
- 8、preg_replace_callback函数
- 专题十二 常用设计模式
- 1、创建型模式
- (1)单例模式
- (2)工厂模式
- (3)抽象工厂模式
- (4)建造者模式(Builder)
- (5)原型模式(Prototype)
- 2、结构型模式
- (1)适配器模式(Adapter)
- (2)桥接模式(Bridge)
- (3)合成模式(Composite)
- (4)装饰器模式(Decorator)
- (5)代理模式(Proxy)
- (6)享元模式(Flyweight)
- 3、行为型模式
- 2、策略模式( Strategy)
- 4、六大原则
- 1、依赖注入
- 5、其他
- 6、Presenter模式
- 4、Service 模式
- 5、Repository模式
- 外观设计模式示例
- 专题十三 实时通信
- 1、pusher 入门教程
- 2、pusher 演示与频道实时通信
- 3、pusher 如何使用私有频道
- 4、pusher 实时图表展示
- 11、webman插件push入门教程
- 12、webman插件push如何使用私有频道
- 13、webman插件push私有频道客户端推送
- 14、webman插件push的webhooks
- 15、webman插件push的实时动态图表
- 专题十四 PHP异常处理
- 1、Exception 类
- 2、如何自定义异常?
- 3、处理PHP重错误
- 4、自定义错误处理器
- 专题十五 Shell脚本案例
- 1、crontab任务脚本无法执行问题
- 专题十六 Jenkins自动化部署
- 1、Jenkins安装
- 2、Pipeline插件
- 3、BlueOcean
- 4、OPENSSH PRIVATE KEY转换为RSA PRIVATE KEY
- 专题十七 常用工具整理
- 1、证书在线打印
- 2、密码生成规则
- 3、vscode插件
- 专题十八 常用功能列表
- 1、frp 内网穿透工具
- (1)如何做成一个服务
- (2)代理Websocket服务
- (3)代理N个Web服务
- (4)设置为系统服务
- (5)Https 配置
- 附录一 常见问题排查
- 2、如何美化文档
- 3、如何提高访问github的速度?
- 4、Vultr搭建SS教程
- 5、PPH编译安装
- 6、Supervisor进程管理工具
- 7、Umeditor 上传文件阿里云和本地
- 8、scp 远程上传或下载 文件/文件夹
- 9、安装和使用守护进程Supervisor
- 10、人脸识别
- 专题十九 流媒体直播实战
- 1、什么是视频直播?
- 2、如何使用推流软件OBS?
- 3、基于Nginx 的RTMP模块搭建系统
- 4、直播流程
- 附录一 阿里云直播
- 5、典型业务场景
- 8、直播回调授权观看
- 9、视频直播源如何加密
- 10、如何实现视频在线云剪辑
- 11、视频点播以及加密技术实现
- 12、FFmpeg 入门教程
- 13、HLS 直播加密播放
- 14、nginx-vod-module 模块
- 15、车辆维修直播系统
- 『16』HLS-m3u8专题
- 附件一 FFmpeg 命令
- 附件二 阿里云点播
- 专题二十 微信
- 附录一 遇到的坑
- 专题二十一 支付专题
- 『1』支付宝支付
- 『2』微信支付
- 『3』支付宝直付通
- 「1」什么是直付通?
- 「2」二级商户进件
- 「3」统一交易收款
- 「4」资金结算
- 「5」分账
- 「6」支付接入流程
- 「?」问题列表
- 附录一 return_url和notify_url的区别
- 附录二 常见错误信息
- 附录三 电脑端支付案例
- 附录四 其他问题
- 1、购买了线上课程后不能退款 这样的现象合法吗?
- 专题二十二 Vue3笔记
- 1、开发环境搭建
- 专题二十三 开放API专题
- 1、错误码
- 2、OAuth2流的简单说明
- 『3』HTTP API 身份验证和授权
- 专题二十四 测试专题
- 1、并发测试
- 专题二十五 DevOps专题
- 『1』PHP代码质量实战
- 专题二十六 前后端分离
- 『1』前后端分离介绍
- 『2』控制权限管理
- 专题二十七 微服务专题
- 1、服务发现 Nacos
- 1.1 服务发现
- 专题二十八 Casbin权限专题
- 1、设置超级管理员的三种方法
- 2、多租户权限和基本设置
- 3、casbin简化策略数据
- 4、多个RBAC
- 5、身份验证和基于角色的RBAC授权
- 6、Casbin在RESTful及中间件使用
- 7、Casbin 中 ABAC 的使用方法
- 8、Model语法和策略存储
- 9、Casbin的Model和Policy
- 10、RBAC的RESTful完全匹配访问模型
- 11、自定义函数使用
- 12、webman中使用
- 13、分布式服务中如何使用Watcher
- 14、Casbin 项目实战ABAC模型策略
- 附录一 源码解读
- 附录二 常见问题
- 专题二十九 PHP 常见错误处理
- 专题三十 ELK日志系统
- 1、docker-elk
- 专题三十一 Swoole专题
- 1、ThinkPHP6中RPC服务
- 专题三十二 Webman框架
- 1、自定义进程执行异步任务
- 2、实现WebRTC信令服务器
- 3、实现一个RPC服务
- 4、对象和资源的持久化
- 5、ThinkORM持久化连接
- 6、ThinkORM悲观锁解决商品超卖问题的实现
- 7、monolog日志神器
- 附录一 为什么?
- 附录二 编码规范
- 附录一 游戏
- 1、初级程序员常犯的错误
- 附录三 设计模式
- 1、单例模式
- 2、工厂方法模式
- 3、抽象工厂模式
- 4、装饰器模式
- 附录四 Docker安装SqlServer
- 专题二十一 Layui
- 我的技术栈【重要】
- 附录四 Linux 日常运维
- 1、sudo 权限
- 2、用户和用户组管理
- 3、grep 多条件查询
- 其他系列
- 1、fnm:基于Rust开发的高效Node版本管理工
- 样式
- Mall商城
- 1、系统架构
- 1.1 mall整合ThinkPHP+ThinkORM搭建基本骨架
- 1.2 mall整合Elasticsearch实现商品搜索
- 1.3 mall整合OSS实现文件上传
- 2、业务篇