Thinkphp6源码解析之分析 路由篇-请求流程

0x00 前言:

第一次写这么长的博客,所以可能排版啊,分析啊,什么的可能会比较乱。但是我大致的流程已经觉得是说的够清楚了。几乎是每行源码上都有注释。关于请求流程大概是:

  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中间件。然后执行中间件。最后再记录一下日志。

这里打个小广告:php交流群:159789818。欢迎想学php以及正在学习php的朋友加入一起讨论学习php,thinkphp框架,laravel框架,yii框架,web前端技术,支付平台对接,微信公众号,微信小程序等微信开发,cms二次开发,全栈工程师技术交流,共同学习进步!

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);
//最终输出到页面上我

0x02 Demo

如果觉得本篇文章对您有所帮助,不妨点一下赞再走。

Thinkphp6源码分析之解析,Thinkphp6路由,Thinkphp6路由源码解析,Thinkphp6请求流程解析,Thinkphp6源码的更多相关文章

  1. Spring AOP源码分析(二):AOP的三种配置方式与内部解析实现

    AOP配置 在应用代码中,可以通过在spring的XML配置文件applicationContext.xml或者基于注解方式来配置AOP.AOP配置的核心元素为:pointcut,advisor,as ...

  2. 【源码分析】- 在SpringBoot中你会使用REST风格处理请求吗?

    ​ 目录 前言 1.什么是 REST 风格 1.1  资源(Resources) 1.2  表现层(Representation) 1.3  状态转化(State Transfer) 1.4  综述 ...

  3. Flask源码分析二:路由内部实现原理

    前言 Flask是目前为止我最喜欢的一个Python Web框架了,为了更好的掌握其内部实现机制,这两天准备学习下Flask的源码,将由浅入深跟大家分享下,其中Flask版本为1.1.1. 上次了解了 ...

  4. [源码解析]HashMap和HashTable的区别(源码分析解读)

    前言: 又是一个大好的周末, 可惜今天起来有点晚, 扒开HashMap和HashTable, 看看他们到底有什么区别吧. 先来一段比较拗口的定义: Hashtable 的实例有两个参数影响其性能:初始 ...

  5. 深入理解 path-to-regexp.js 及源码分析

    阅读目录 一:path-to-regexp.js 源码分析如下: 二:pathToRegexp 的方法使用 回到顶部 一:path-to-regexp.js 源码分析如下: 我们在vue-router ...

  6. Docker源码分析(五):Docker Server的创建

    1.Docker Server简介 Docker架构中,Docker Server是Docker Daemon的重要组成部分.Docker Server最主要的功能是:接受用户通过Docker Cli ...

  7. Django中间件部分源码分析

    中间件源码分析 中间件简介 中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围内改变Django的输入和输出.每个中间件组件都负责做一些特定的 ...

  8. 《深入理解Spark:核心思想与源码分析》(第2章)

    <深入理解Spark:核心思想与源码分析>一书前言的内容请看链接<深入理解SPARK:核心思想与源码分析>一书正式出版上市 <深入理解Spark:核心思想与源码分析> ...

  9. android高级---->AsyncTask的源码分析

    在Android中实现异步任务机制有两种方式,Handler和AsyncTask,它在子线程更新UI的例子可以参见我的博客(android基础---->子线程更新UI).今天我们通过一个小的案例 ...

随机推荐

  1. win10安装docker 和 splash

    参考链接1:https://www.cnblogs.com/321lxl/p/9536616.html 参考链接2:https://blog.csdn.net/qq_18831501/article/ ...

  2. Python - 变量的作用域

    变量作用域 Python能够改变变量作用域的代码段是 def . class . lamda. if/elif/else.try/except/finally.for/while 并不能涉及变量作用域 ...

  3. IOS抓包工具Stream——让移动端的抓包变得轻而易举

    有一天下晚班回家,在地铁上的时候,开发发来信息说,能不能把之前创建的bug再抓包看下数据.顿时心里就想,在地铁上,我上哪抓包去.之后百度了下,发现ios有一款非常实用的抓包工具,大家可以上App St ...

  4. [剑指Offer]41.和为S的两个数字 VS 和为S的连续正数序列

    [剑指Offer]41 和为S的两个数字 VS 和为S的连续正数序列 Leetcode T1 Two Sum Given an array of integers, return indices of ...

  5. 记一次Xmrig挖矿木马排查过程

    问题现象 Linux 服务器收到报警信息,主机 CPU 跑满. 自动创建运行 Docker 容器 xmrig, 导致其他运行中容器被迫停止. 问题原因 通过 top 命令可以看到有一个 xmrig 进 ...

  6. MySQL 教程--检视阅读

    MySQL 教程--检视阅读 准备:Windows 上安装 MySQL 教程地址,PHP语言基础 教程地址2 教程地址3,有讲数据库的备份和恢复 教程地址4,w3c.china,php基础,扩展阅读 ...

  7. Hook集合----SSDTHook(x86 Win7)

    最近在学习Ring0层Hook的一些知识点,很久就写完SSDTHook的代码了,但是一直没有整理成笔记,最近有时间也就整理整理. 介绍: SSDTHook 实质是利用Ntoskrnl.exe 中全局导 ...

  8. Ubuntu环境下部署Django+uwsgi+nginx总结

    前言 这是我在搭建Django项目时候的过程,拿来总结记录,以备不时之需. 项目采用nginx+uwsgi的搭配方式. 项目依赖包采用requirements.txt文件管理的方式. 本地准备工作 确 ...

  9. test命令的使用以及判断语法

    test命令 Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值.字符和文件三个方面的测试. 语法:test EXPRESSION 或者 [ EXPRESSION ] 字符串判断( ...

  10. 怎样设计最优的卷积神经网络架构?| NAS原理剖析

    虽然,深度学习在近几年发展迅速.但是,关于如何才能设计出最优的卷积神经网络架构这个问题仍在处于探索阶段. 其中一大部分原因是因为当前那些取得成功的神经网络的架构设计原理仍然是一个黑盒.虽然我们有着关于 ...