1. 入口文件先实例化容器,然后再通过容器去获取到Http对象 (Web管理类),然后执行Http对象中的run方法。
2. 方法内会创建一个Request对象,然后将对象绑定到容器内。然后再到runWithRequest方法,执行应用程序
3. runWithRequest方法内会初始化当前应用,简单来说就比如加载一下语言包,加载一下应用文件。common.php公共函数文件。helper.php助手函数文件、.env环境变量、运行开始的时间、设置时区、加载中间件等等。然后到dispatchToRoute方法,传入当前的Request请求对象。
4. dispatchToRoute方法,这里我姑且称为路由初始化方法。这里主要就是检测配置文件,是否开启了路由。如果开启了路由。就加载路由文件。并且设置一个匿名函数。只有在调用的时候才会加载设置的路由。接着会通过容器获取route类的实例。并且传入当前Request对象和路由配置的匿名函数并执行里面的dispatch方法
5. dispatch方法,主要就是路由初始化。判断路由是否配置。如果没有配置就直接执行默认控制器和默认方法。如果有的话。就加载一下路由配置,再执行check方法,通过check方法。去检测是什么路由。然后调用url方法传入当前的url地址
6. url方法执行并且会返回一个Url基础类
7. 这里我称之为url且切割类吧。他主要的作用就是将传入的Request对象,rule路由规则对象,以及当前的url地址。把url地址解析。这里又去调用了一个parseUrl方法。
8. 这个方法我就不多介绍了。它主要的作用就是分割出来要执行的control和function。
9. 到这里就结束了url的部分。又回到了第五部。去调用url类中的init方法(路由后置操作。中间件).然后通过middleware中的then。到最后执行一个上一步返回的Url类中的run方法
10. run这里主要就是最通过exec再去获取控制器(controller)和对应的方法(function)的结果。然后创建一个Response对象。最后返回
11. 最后回到了入口文件run方法下面还有一个send方法。源码就不贴了。他的作用就是输出
12. 入口文件最后一行。调用了一下Http类的end方法。简单说就是挂个HttpEnd中间件。然后执行中间件。最后再记录一下日志。
## 0x01 源码解析
### 1、public/index.php
~~~
// [ 应用入口文件 ]
//定义根命名空间
namespace think;
//引入composer
require __DIR__ . '/../vendor/autoload.php';
//通过Ioc容器将HTTP类实例出来
// 执行HTTP应用并响应
$http = (new App())->http;
//执行HTTP类中的run类方法 并返回一个response对象
$response = $http->run();
//执行response对象中的send类方法 该方法是处理并输出http状态码以及页面内容
$response->send();
//执行response对象中的send方法
$http->end($response);
~~~
### 2、通过\\Think\\App容器获取到Http对象,然后再执行Http对象中的run方法
~~~
/**
* 执行应用程序
* @access public
* @param Request|null $request
* @return Response
*/
public function run(Request $request = null): Response
{
//判断是否传入Request对象,如果没有则创建
$request = $request ?? $this->app->make('request', [], true);
//将Request绑定到App容器内
$this->app->instance('request', $request);
try {
//runWithRequest方法 作用是执行应用程序
$response = $this->runWithRequest($request);
} catch (Throwable $e) {
//如果捕捉到Throwable异常 则执行reportException方法
//调用Handle::class实例中的report方法。收集异常信息
$this->reportException($e);
//通过调用Handle::class实例中的render方法
//获取异常信息输出流
$response = $this->renderException($request, $e);
}
//return 内容
return $response;
}
~~~
### 3、\\Think\\Http->runWithRequest() 初始化应用程序,并执行
~~~
protected function runWithRequest(Request $request)
{
//初始化应用程序
$this->initialize();
// 加载全局中间件
$this->loadMiddleware();
// 设置开启事件机制
$this->app->event->withEvent($this->app->config->get('app.with_event', true));
// 监听HttpRun
$this->app->event->trigger(HttpRun::class);
//这里重点
return $this->app->middleware->pipeline()
->send($request)
->then(function ($request) {
//通过dispatchToRoute方法加载路由
return $this->dispatchToRoute($request);
});
}
~~~
### 4、\\Think\\Http->dispatchToRoute()
~~~
protected function dispatchToRoute($request)
{
//通过容器控制反转快速获取config类实例
//并获取配置文件中的with_route 判断是否加载路由
//如果加载则返回一个匿名函数。里面是路由文件内的设置
$withRoute = $this->app->config->get('app.with_route', true) ? function () {
$this->loadRoutes();
} : null;
//执行\Think\Route类中的dispatch方法,并且将获取到的路由
//文件以及当前的Request实例传入到路由中,然后进行路由调度
return $this->app->route->dispatch($request, $withRoute);
}
~~~
### 5、\\Think\\Route->dispatch() 路由
~~~
public function dispatch(Request $request, $withRoute = null)
{
//设置传入的Request对象到当前对象的属性上
$this->request = $request;
//同上 这个是设置host
$this->host = $this->request->host(true);
//执行Route init 方法 初始化
$this->init();
//判断的withRoute是否为真
if ($withRoute) {
//执行传入过来的匿名函数,加载路由
$withRoute();
//官方注释的是检测url路由,我这里姑且认为是路由分发吧
//check返回的是think\route\Dispatch 路由调度基础类对象
$dispatch = $this->check();
} else {
//调用think\route\dispatch\Url类
//将当前的url地址传入进去,进行默认的url解析
$dispatch = $this->url($this->path());
}
//执行Dispatch对象中的init方法
//这里用于绑定控制器和方法以及路由后置操作,例如:中间件、绑定模型数据
$dispatch->init($this->app);
//执行路由调度。并返回一个Response对象
return $this->app->middleware->pipeline('route')
->send($request)
->then(function () use ($dispatch) {
return $dispatch->run();
});
}
~~~
### 6、\\Think\\Route->init() 初始化
~~~
protected function init()
{
//合并默认配置以及读取到的route.php配置
$this->config = array_merge($this->config, $this->app->config->get('route'));
//判断路由中间件是否存储,如果存在则调用middleware类中的import方法
//注册route中间件
if (!empty($this->config['middleware'])) {
$this->app->middleware->import($this->config['middleware'], 'route');
}
//是否延迟解析
$this->lazy($this->config['url_lazy_route']);
//读取到的 是否合并路由配置项赋值到类变量mergeRuleRegex中
$this->mergeRuleRegex = $this->config['route_rule_merge'];
//获取配置:是否删除url最后的斜线
$this->removeSlash = $this->config['remove_slash'];
//是否去除url最后的斜线
$this->group->removeSlash($this->removeSlash);
}
~~~
### 7、Think\\Route\\Dispatch->init()
```
`public``function``init(App``$app``)`
`{`
`$this``->app =``$app``;`
`// 执行路由后置操作`
`$this``->doRouteAfter();`
`}`
```
### 8、Think\\Route\\Dispatch->run()
```
`public``function``run(): Response`
`{`
`//判断$this->rule路由规则是否为RuleItem类的实例`
`//判断当前请求方法,是不是OPTIONS以及`
`//判断当前路由规则是否为自动注册的OPTIONS路由`
`if``(``$this``->rule``instanceof``RuleItem &&``$this``->request->method() ==``'OPTIONS'``&&``$this``->rule->isAutoOptions()) {`
`//获取当前的路由列表`
`$rules``=``$this``->rule->getRouter()->getRule(``$this``->rule->getRule());`
`$allow``= [];`
`foreach``(``$rules``as``$item``) {`
`//这里是循环把所有路由全部转成大写`
`$allow``[] =``strtoupper``(``$item``->getMethod());`
`}`
`//创建并返回一个Response对象,调用create静态方法`
`return``Response::create(``''``,``'html'``, 204)->header([``'Allow'``=> implode(``', '``,``$allow``)]);`
`}`
`//如果上面的不匹配则调用当前Dispatch类中的exec方法`
`//实例化控制器以及方法`
`$data``=``$this``->``exec``();`
`//最后动态的返回一个Response对象。xml、json等等`
`return``$this``->autoResponse(``$data``);`
`}`
```
### 9、\\Think\\Route->check()
```
`public``function``check(): Dispatch`
`{`
`//转换PATH_INFO分隔符,拼接url`
`$url``=``str_replace``(``$this``->config[``'pathinfo_depr'``],``'|'``,``$this``->path());`
`//获取是否完全匹配 配置项`
`$completeMatch``=``$this``->config[``'route_complete_match'``];`
`//调用checkDomain检测是否为域名路由如果不是则返回false`
`//如果是域名路由,则返回一个Domain对象。并且执行对象中的check方法`
`//并把当前的Request请求对象以及url地址和是否完全匹配路由项传入进去`
`$result``=``$this``->checkDomain()->check(``$this``->request,``$url``,``$completeMatch``);`
`//判断result是否为false 也就是不是域名路由`
`//再判断是否为跨域路由`
`if``(false ===``$result``&& !``empty``(``$this``->cross)) {`
`// 如果是跨域路由,就将当前的Request请求对象以及url地址和是否完全匹配路由项传入进去`
`$result``=``$this``->cross->check(``$this``->request,``$url``,``$completeMatch``);`
`}`
`//如果是域名路由`
`if``(false !==``$result``) {`
`//直接返回 $result变量 变量内存储着 RuleGroup对象实例`
`//路由规则`
`return``$result``;`
`}``elseif``(``$this``->config[``'url_route_must'``]) {`
`//判断是否启用了强制路由,如果启用了强制路由`
`//然后域名路由也匹配不上。就触发一个路由`
`//找不到的异常类`
`throw``new``RouteNotFoundException();`
`}`
`//以上都不匹配,则调用url函数,传入当前的url地址`
`//返回一个Url类实例`
`return``$this``->url(``$url``);`
`}`
```
### 10、\\Think\\Route->url()
```
`public``function``url(string``$url``): UrlDispatch`
`{`
`return``new``UrlDispatch(``$this``->request,``$this``->group,``$url``);`
`}`
```
### 11、\\Think\\Route\\dispatch\\Url url切割类(自己给的称呼)
```
`//构造函数`
`public``function``__construct(Request``$request``, Rule``$rule``,``$dispatch``,``array``$param``= [], int``$code``= null)`
`{`
`//获取传入来的Request对象,存储到类成员变量内`
`$this``->request =``$request``;`
`//获取到路由列表,也放到类成员变量内`
`$this``->rule =``$rule``;`
`// 调用类中的parseUrl方法 解析URL规则`
`$dispatch``=``$this``->parseUrl(``$dispatch``);`
`//调用父类构造函数`
`parent::__construct(``$request``,``$rule``,``$dispatch``,``$this``->param,``$code``);`
`}`
`protected``function``parseUrl(string``$url``):``array`
`{`
`//获取到分隔符`
`$depr``=``$this``->rule->config(``'pathinfo_depr'``);`
`//获取当前域名`
`$bind``=``$this``->rule->getRouter()->getDomainBind();`
`//如果域名不为空,并且正则匹配的到`
`if``(``$bind``&& preg_match(``'/^[a-z]/is'``,``$bind``)) {`
`//切割url,换成配置项中的PATH_INFO分隔符`
`$bind``=``str_replace``(``'/'``,``$depr``,``$bind``);`
`// 如果有域名绑定`
`$url``=``$bind``. (``'.'``!=``substr``(``$bind``, -1) ?``$depr``:``''``) . ltrim(``$url``,``$depr``);`
`}`
`//调用rule类中的parseUrlPath方法,切割pathinfo参数`
`//如果url中有参数 返回一个demo吧 ['Index','Demo']`
`//第一个为控制器、第二个为方法`
`$path``=``$this``->rule->parseUrlPath(``$url``);`
`//如果切割的pathinfo为空,则直接返回一个[null,null] 这样的一个空数组`
`if``(``empty``(``$path``)) {`
`return``[null, null];`
`}`
`//获取到第一个下标 控制器`
`$controller``= !``empty``(``$path``) ?``array_shift``(``$path``) : null;`
`//正则匹配,如果匹配不到。就弹出一个HttpException异常`
`if``(``$controller``&& !preg_match(``'/^[A-Za-z0-9][\w|\.]*$/'``,``$controller``)) {`
`throw``new``HttpException(404,``'controller not exists:'``.``$controller``);`
`}`
`//获取到第二个下标 方法 function`
`// 解析操作`
`$action``= !``empty``(``$path``) ?``array_shift``(``$path``) : null;`
`$var` `= [];`
`// 解析额外参数`
`//类似于 /index.php/Index/Users/Pascc`
`//这样就会返回一个 三个下标的数组`
`if``(``$path``) {`
`//这里将多余的下标,放到var变量内`
`preg_replace_callback(``'/(\w+)\|([^\|]+)/'``,``function``(``$match``)``use``(&``$var``) {`
`$var``[``$match``[1]] =``strip_tags``(``$match``[2]);`
`}, implode(``'|'``,``$path``));`
`}`
`//获取到泛域名 再判断其中是否有*符号`
`$panDomain``=``$this``->request->panDomain();`
`if``(``$panDomain``&&``$key``=``array_search``(``'*'``,``$var``)) {`
`// 泛域名赋值`
`$var``[``$key``] =``$panDomain``;`
`}`
`// 设置当前请求的参数`
`$this``->param =``$var``;`
`// 封装路由`
`$route``= [``$controller``,``$action``];`
`//判断路由,是否存在 不存在则弹出未找到路由`
`if``(``$this``->hasDefinedRoute(``$route``)) {`
`throw``new``HttpException(404,``'invalid request:'``.``str_replace``(``'|'``,``$depr``,``$url``));`
`}`
`//返回路由`
`return``$route``;`
```
### 12、\\Think\\Route\\dispatch\\controller->init() 绑定控制器和方法
~~~
public function init(App $app)
{
parent::init($app);
$result = $this->dispatch;
if (is_string($result)) {
$result = explode('/', $result);
}
// 获取控制器名
$controller = strip_tags($result[0] ?: $this->rule->config('default_controller'));
if (strpos($controller, '.')) {
$pos = strrpos($controller, '.');
$this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1));
} else {
$this->controller = Str::studly($controller);
}
// 获取操作名
$this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action'));
// 设置当前请求的控制器、操作
$this->request
->setController($this->controller)
->setAction($this->actionName);
}
~~~
### 13、接下来流程就是回到第5步
#### 最终会返回一个Response对象。流程又回到了第2步过程
### 14、然后会在入口文件中
```
`namespace``think;`
`//引入composer`
`require``__DIR__ .``'/../vendor/autoload.php'``;`
`$http``= (``new``App())->http;`
`$response``=``$http``->run();`
`//执行返回的Response对象中的send方法`
`//执行response对象中的send类方法 该方法是处理并输出http状态码以及页面内容`
`$response``->send();`
`//执行Http对象中的send方法`
`$http``->``end``(``$response``);`
`//最终输出到页面上我`
```
- thinkphp6执行流程(一)
- php中use关键字用法详解
- Thinkphp6使用腾讯云发送短信步骤
- 路由配置
- Thinkphp6,static静态资源访问路径问题
- ThinkPHP6.0+ 使用Redis 原始用法
- smarty在thinkphp6.0中的最佳实践
- Thinkphp6.0 搜索器使用方法
- 从已有安装包(vendor)恢复 composer.json
- tp6with的用法,表间关联查询
- thinkphp6.x多对多如何添加中间表限制条件
- thinkphp6 安装JWT
- 缓存类型
- 请求信息和HTTP头信息
- 模型事件用法
- 助手函数汇总
- tp6集成Alipay 手机和电脑端支付的方法
- thinkphp6使用jwt
- 6.0session cookie cache
- tp6笔记
- TP6(thinkphp6)队列与延时队列
- thinkphp6 command(自定义指令)
- command(自定义指令)
- 本地文件上传
- 缓存
- 响应
- 公共函数配置
- 七牛云+文件上传
- thinkphp6:访问多个redis数据源(thinkphp6.0.5 / php 7.4.9)
- 富文本编辑器wangEditor3
- IP黑名单
- 增删改查 +文件上传
- workerman 定时器操作控制器的方法
- 上传文件到阿里云oss
- 短信或者邮箱验证码防刷代码
- thinkphp6:访问redis6(thinkphp 6.0.9/php 8.0.14)
- 实现关联多个id以逗号分开查询数据
- thinkphp6实现邮箱注册功能的细节和代码(点击链接激活方式)
- 用mpdf生成pdf文件(php 8.1.1 / thinkphp v6.0.10LTS )
- 生成带logo的二维码(php 8.1.1 / thinkphp v6.0.10LTS )
- mysql数据库使用事务(php 8.1.1 / thinkphp v6.0.10LTS)
- 一,创建过滤IP的中间件
- 源码解析请求流程
- 验证码生成
- 权限管理
- 自定义异常类
- 事件监听event-listene
- 安装与使用think-addons
- 事件与多应用
- Workerman 基本使用
- 查询用户列表按拼音字母排序
- 扩展包合集
- 查询用户数据,但是可以通过输入用户昵称来搜索用户同时还要统计用户的文章和粉丝数
- 根据图片的minetype类型获取文件真实拓展名思路
- 到处excel
- 用imagemagick库生成缩略图
- 生成zip压缩包并下载
- API 多版本控制
- 用redis+lua做限流(php 8.1.1 / thinkphp v6.0.10LTS )
- 【thinkphp6源码分析三】 APP类之父, 容器Container类
- thinkphp6表单重复提交解决办法
- 小程序授权
- 最简单的thinkphp6导出Excel
- 根据访问设备不同访问不同模块
- 服务系统
- 前置/后置中间件
- 给接口api做签名验证(php 8.1.1 / thinkphp v6.0.10LTS )
- 6实现邮箱注册功能的细节和代码(点击链接激活方式)
- 使用前后端分离的验证码(thinkphp 6.0.9/php 8.0.14/vue 3.2.26)
- 前后端分离:用jwt+middleware做用户登录验证(php 8.1.1 / thinkphp v6.0.10LTS )
- vue前后端分离多图上传
- thinkphp 分组、页面跳转与ajax
- thinkphp6 常用方法文档
- 手册里没有的一些用法
- Swagger 3 API 注释
- PHP 秒级定时任务
- thinkphp6集成gatewayWorker(workerman)实现实时监听
- thinkphp6按月新增数据表
- 使用redis 实现消息队列
- api接口 统一结果返回处理类
- 使用swoole+thinkphp6.0+redis 结合开发的登录模块
- 给接口api做签名验证
- ThinkPHP6.0 + UniApp 实现小程序的 微信登录
- ThinkPHP6.0 + Vue + ElementUI + axios 的环境安装到实现 CURD 操作!
- 异常$e
- 参数请求验证自定义和异常错误自定义