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

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

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

 

posted @ 2020-04-13 21:36  Death-Satan  阅读(2594)  评论(6编辑  收藏  举报