概述
因为工作的需要,深入研究了一下thinkphp的源码,也算是对php知识的一个回归,工作这么多年,我一直坚信php是最好的Web编程语言,它可以做到成本和效率的一个平衡,知其然,更要知其所以然才是高手修炼之道
类的自动加载
不管是tp,yaf 还是yii ,所有的php框架都是从自动加载类库文件开始的,如果你不知道如何下手,就打开入口文件,从分析类的自动加载开始。
thinkphp6使用了composer去加载类库,整个composer的实现原理是:首先将各个使用了不同psr规范的类或映射类,以某种形式存储,然后当类找不到的时候,通过与存储的数据匹配,找到类所在的路径,然后去加载。
实际上composer总共有四种规范的文件需要加载,分别是:psr0、psr4、类映射、公共函数文件。
1.使用了单例模式,原理:简化后,psr0,psr4,classmap每个分类对应一个数组,类名在这三个数组进行检索,检索完成后,include
php
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
}
2.此处先注册自动加载未定义类,紧跟着注销,是因为只加载并实例化classLoader类,其他类的加载,使用composer提供的方法,而不是自定义的。
php
spl_autoload_register(array('ComposerAutoloaderInit1283bda52466502421173f3a3bffb31b', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit1283bda52466502421173f3a3bffb31b', 'loadClassLoader'));
3.php版本大于5.6且未使用hhvm且没有启用zenGaurd加密扩展,即可使用静态加载,composer install 后,从各个vendor库的composer.json中读取autoload属性。
scss
$useStaticLoader = PHP_VERSION_ID >= 50600 &&
!defined('HHVM_VERSION') &&
(!function_exists('zend_loader_file_encoded') ||
!zend_loader_file_encoded());
这里使用到了一个技巧,若对象类的成员属性是private,同时已经实现了set方法,现在需要实现同样的功能,直接复制给private成员属性。若是常用方法是将private属性变成public,或者修改set方法,或添加新的方法。但这里使用了系统类Closure的属性,可以通过bind方法,使用到了目标对象的private属性。
php
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$prefixDirsPsr4;
$loader->fallbackDirsPsr0 = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$fallbackDirsPsr0;
$loader->classMap = ComposerStaticInit1283bda52466502421173f3a3bffb31b::$classMap;
}, null, ClassLoader::class);
}
框架初始化执行流程
thinkphp6.0 应用的初始化做了大量的操作,其主要的操作有:加载环境变量、加载配置文件,加载语言包、监听 AppInit、initializers 数组包含的类的初始化。
php
public function run(Request $request = null): Response
{
//初始化
$this->initialize();
//自动创建request对象
$request = $request ?? $this->app->make('request', [], true);
$this->app->instance('request', $request);
try {
$response = $this->runWithRequest($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
return $response;
}
1.加载环境变量
重点强调一下在初始化加载initialize中,和底下的$this->lang->load
、$this->config->load
都是一样的,都是加载对应文件中的数组。
scss
// 加载环境变量
if (is_file($this->rootPath . '.env')) {
$this->env->load($this->rootPath . '.env');
}
object(think\Env)#8 (1) {
["data":protected]=>
array(14) {
["APP_DEBUG"]=>
string(4) "true"
["DATABASE_CHARSET"]=>
string(7) "utf8mb4"
["PROJECT_WS_DOMAIN"]=>
string(15) "wss://127.0.0.1"
}
}
2.调试模式设置
<math xmlns="http://www.w3.org/1998/Math/MathML"> t h i s − > d e b u g M o d e I n i t ( ) 运行原理详见注释 , 需要注意的是,这里不知道是不是源码中的 B u g , ! this->debugModeInit() 运行原理详见注释,需要注意的是,这里不知道是不是源码中的Bug,! </math>this−>debugModeInit()运行原理详见注释,需要注意的是,这里不知道是不是源码中的Bug,!this->appDebug 恒为true (有时间在做ob缓存和依赖注入的知识点)。
php
protected function debugModeInit(): void
{
// 应用调试模式
if (!$this->appDebug) {
$this->appDebug = $this->env->get('app_debug') ? true : false;
// 关闭错误显示
ini_set('display_errors', 'Off');
}
// 如果不是命令行模式
if (!$this->runningInConsole()) {
// 重新申请一块比较大的buffer
if (ob_get_level() > 0) {
// 相当于ob_get_contents() 和 ob_clean()
// 获取缓冲区内容并删除缓冲区内容
$output = ob_get_clean();
}
// 开启新的缓冲区控制
ob_start();
if (!empty($output)) {
// 由于开启了新的缓冲区控制,
// 这里不会直接输出$output
// 而是等到依次执行了ob_flush()和flash()之后才将内容输出到浏览器
echo $output;
}
}
}
- 加载应用文件和配置等操作
在加载全局初始化文件的时候,加载是有顺序的,首先加载app目录下的common.php文件和系统下的helper.php文件,然后加载config目录下的所有php文件,最后加载event事件和service服务文件。
php
protected function load(): void
{
$appPath = $this->getAppPath();
# 首先加载app目录下的common.php文件和系统下的helper.php文件
if (is_file($appPath . 'common.php')) {
include_once $appPath . 'common.php';
}
include_once $this->thinkPath . 'helper.php';
$configPath = $this->getConfigPath();
$files = [];
if (is_dir($configPath)) {
$files = glob($configPath . '*' . $this->configExt);
}
foreach ($files as $file) {
$this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
}
# 然后加载config目录下的所有php文件
# 最后加载event事件和service服务文件
if (is_file($appPath . 'event.php')) {
$this->loadEvent(include $appPath . 'event.php');
}
if (is_file($appPath . 'service.php')) {
$services = include $appPath . 'service.php';
foreach ($services as $service) {
$this->register($service);
}
}
}
4.初始化错误和异常处理、注册系统服务和初始化系统服务
最后,初始化错误和异常处理、注册系统服务和初始化系统服务,这几行代码做了比较多的操作:分别实例化包含在里面的类,然后调用其init方法。initializers 数组的值如下:
kotlin
// 初始化
foreach ($this->initializers as $initializer) {
$this->make($initializer)->init($this);
}
php
protected $initializers = [
Error::class,
RegisterService::class,
BootService::class,
];
结尾
thinkphp6.0的类的自动加载和初始化就介绍到这里了,知其然,更要知其所以然才是高手修炼之道。