继续学习TP5框架的启动流程,也就是在request请求后是如何response的。
Thinkphp5.1对底层架构做了进一步的改进,减少依赖,主要就是新增了容器(Container)、门面(Facade)、中间件(Middleware)三大块功能。
容器(Container)的功能,一句话概括就是管理对象实例,缓存已创建的实例。
加载基础文件
index.php分为两部分,一部分是加载基础文件,一部分是执行应用并响应。
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// [ 应用入口文件 ]
namespace think;
// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';
// 支持事先使用静态方法设置Request对象和Config对象
// 执行应用并响应
Container::get('app')->run()->send();
跟进加载基础文件,接着加载/library/think/Loader.php,最后调用_require_file包含文件并return。
接着跟进,分别调用了Loader类和Error类下的register()注册函数。
// 注册自动加载
Loader::register();
// 注册错误和异常处理机制
Error::register();
首先跟进Loader::register,先获得了网站目录和composer目录
判断在composerPath目录下是否存在autoload_static.php文件,如果存在则进行包含操作。
接着进行了命名空间的定义,简化了命名空间引用的繁杂度。
self::addNamespace([
'think' => __DIR__,
'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
]);
最后加载插件目录
self::addAutoLoadDir($rootPath . 'extend');
接着注册错误和异常处理机制,先是进行了一个文件包含的操作。
调用该类下的register()函数
最后实现日志接口,先判断是否存在 Psr\Log\LoggerInterface的extends,如果没有添加过该插件会注册一个孔的日志接口。
// 实现日志接口
if (interface_exists('Psr\Log\LoggerInterface')) {
interface LoggerInterface extends \Psr\Log\LoggerInterface
{}
} else {
interface LoggerInterface
{}
}
最后注册了类的别名,最后进入了Container的调用流程。
Container调用过程
这里先包含了Container.php文件,然后进入Container.php的get()函数
这里调用了Container获取实例的的函数,并且返回实例。这里的is_null返回true,内存中只有一个$instance存在。
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
return static::$instance;
}
可以查看该$instance,绑定了之前注册的别名。
然后调用make方法,这里的$abstract赋值为app,concrete为think\App,也就是app类的命名空间。由于这里进入了else空间,所以return了调用的make,接着跟进make。
第二次进入make,$abstract值为think/App,由于bind数组没有存在该键名,所以进入了$this->invokeClass()。
这里比较关键的是调用反射实例化了App类,并且调用了App的构造,最后返回App。
回到原来的调用
Container::get('app')->run()->send();
通过上面的分析可知,Container::get('app')
这里返回了App类,所以这里调用的run是App类中的run,接着跟进run函数。
路由分发
开始调用了初始化函数,跟进可知该函数对$app中的部分属性进行了赋值处理,并进行了一些初始化操作,例如设置环境变量、加载配置文件、注册命名空间、初始化应用、开启类名后缀、是否要开启debug模式判断、注册异常处理、注册根命名空间、数据库初始化、系统时区设置、路由初始化等等。而路由初始化进行了一个目录扫描并包含文件的操作。
$this->initialize();
这里的$dispatch为空,所以进入了路由检测。
routeCheck()分别进行检测路由缓存、获取应用调度信息、判断是否开启强制路由模式,最后返回一个Dispath对象,调用该对象下的init,之后调用parseUrl,通过TP的方式解析路由并分装参数。
实例化Module,调用Init进行初始化,通过tp解析控制器和获取操作名的方法获得需要获得的变量,设置需要调用的控制器名和操作名,最后返回$this。
到这里的初始化就完成了,这里已经实现了路由分发、控制器和操作名设置等等,剩下的就是将需要将调度信息发送到应有的控制器进行执行,也就是调用send()方法。
执行调度
Thinkphp将对路由的处理放在了中间件中,这部分我也不是很熟悉,只能跟着其他人的分析流程走。
这部分传入我们包装好的路由信息,跟进到中间件的add函数
这里将传入的闭包添加到队列中
关注这里的函数,进入buildMiddleware
跟进后进入第二个return语句,如果为闭包的话就返回一个数组,数组中的值为$middleware
和$param
。返回think\App跟进$this->mdiddleware->dispatch()
。
跟进$this->resolve,这里传入的值是’route’,在这里返回闭包。
跟进,上面分析了,$dispatch
为UrlDispatch实例,这个实例中存储的dispatch属性是一个Moudle对象。进入Module,也就是MVC中的M,调用需要的控制器和方法。
跟进exec,跟初始化过程类似,实例化需要的控制器,如果不存在则返回报错信息
跟踪,会获取操作名和方法还有传入参数,最后进入invokeReflectMethod
这里绑定了参数,通过反射调用响应的类的方法。
兜兜转转进入控制器
将html源码作为返回值进行返回,最后进入日志。
到这里调用流程就结束了!
总结
启动流程分析起来总还是很复杂的,其实无非也是文件包含、环境变量配置、变量创建、分发路由和访问控制器,总体还是从request到response的传递。