ThinkPHP路由源码解析(三)

596 阅读10分钟

本文接着上文继续来解读路由源码,如果你看到本文可以先看一下之前写的路由文章,共计俩篇。

前言

经过前俩篇路由源码的解读,相信大家对路由已经有了一定了了解了。

本文会接着ThinkPHP路由源码解析(二)继续讲解,也是路由这块内容的收尾了。

关于路由,咔咔感觉是整个框架中源码阅读最吃力的一个核心点,也耗费了很多时间。

因为在其中有很多类的嵌套,何不按照常理出牌,例如$this->group的这个点。

虽然就是一个简单的调用关系,但是在源码中执行的功能也是很多很多。

一般源码都会认为这个group就是简单的一个类,其实不然,最终返回的结果令人有点匪夷所思返回的是Domain这个类。

所以说对于框架的一切都需要认真的去理解,阅读源码主要是为了提升自己对框架的认识和框架的设计思想。

还是按照步骤来,先看矿机的执行流程图,然后大家就可以根据流程图进行清晰的阅读文章了。

后期的所有源码阅读都会直接添加到这里,进行补充。

框架执行流程

一、检测路由-合并分组参数、检查分组路由

上一篇的最后是讲的下图的位置,这个位置暂时还是空的,这个空的位置就是接下来要讲的合并分组参数。

参数合并其实就是将路由参数和默认的参数进行合并。

执行位置

为了能给大家清晰的展示出来执行流程,咔咔将执行流程圈了出来。

执行文件:

  1. thinkphp/library/think/App.php -> $dispatch = $this->route->check($path, $must);
  2. thinkphp/library/think/Route.php -> $result = $domain->check($this->request, $url, $completeMatch);
  3. thinkphp/library/think/route/Domain.php -> $result = $this->checkRouteAlias($request, $url); -> return parent::check($request, $url, $completeMatch);
  4. thinkphp/library/think/route/RuleGroup.php -> $this->mergeGroupOptions();

对应执行关系:

  1. 路由检测 返回一个Dispatch对象
  2. 检测域名路由
  3. 检测别名路由 -> 检测分组路由
  4. 合并分组参数

路由检测执行流程

可以看这一小节的标题为检测路由之路由参数、检查分组路由,那么在检测路由这里还是有很多的内容的。

只不过咔咔只是针对于合并分组参数、检查分组路由进行重点的讲解,终于其它的内容是没有贯穿到整条线的,就不去做过深的探讨了。

下一篇文章在控制器中会聊到一部分,但也不是全部都会写的哈!

合并分组参数

接下来先说这块的内容。

合并分组参数

在看这一块内容之前需要对$this->parent这个属性进行查看,看这个值是设置成了什么。

通过debug_backtrace()对其打印可以得知为Domain的实例化类。

打印结果

接下来就进入到mergeGroupOptions方法进行详解。

  • 执行的文件:thinkphp/library/think/route/RuleGroup.php 164行
  • $this->parent:class think\route\Domain
  • 获取路由参数定义,如果不存在路由参数为'merge_rule_regex' => bool(false),反正会在后边追加上路由参数
  • 合并分组参数:$this->mergeOptions : 需要和分组合并的路由参数'after', 'model', 'header', 'response', 'append', 'middleware'
  • 通过array_merge进行合并参数
  • 并将lockOption参数进行锁定
  • 并将合并的结果返回,最终返回结果$this->option

返回结果看下图 合并分组参数

最终返回结果

返回结果

说到底就是将路由参数进行合并,路由参数官方支持的请看下图,注意一下支持版本号。

支持的路由参数

检查分组路由

文件:thinkphp/library/think/route/RuleGroup.php 183行。

在这里首先需要明确一下关于$rules这个变量的值是什么。

检测分组路由

打印出$rules的值可以看出来有俩种情况。

第一种情况为不是资源路由。

第二种情况为资源路由。

$rules的值

这是因为咔咔在路由文件只设置了俩个路由,一个资源路由,一个非资源路由。

路由配置文件

根据上图圈出来的数据就可以知道当执行循环时$item这个值分为俩种情况。

  • 执行think\route\Resource Object中的check方法
  • 执行think\route\RuleItem Object中的check方法

根据神器的打印结果可以看到当为资源路由时 也是执行的thinkphp/library/think/route/RuleGroup.php类的check方法。

神器打印

为什么资源路由会执行thinkphp/library/think/route/RuleGroup.php的check

因为在resource类中继承的是RuleGroup类。

并且$item的值是Resource类的实例,所以会进行执行check方法。

所以说拥有神器是多么的重要。

继承关系

经过再一次的执行check方法,最终结果的返回是在下图咔咔圈的地方。

检测路由分组

非资源路由执行check

文件:thinkphp/library/think/route/RuleItem.php 行号231 此处就是非资源路由执行的方法。

非资源路由执行的方法

进到检测路由规则方法后,还是会合并路由参数。

检测路由

关于合并路由参数的方法,在上边已经说过了, 在这里就不多说了。

合并路由参数

直到这里关于检测路由下的合并分组参数和检查分组路由就说完了,思路不清晰的可以看思维导图。

二、检测URL变量和规则路由是否匹配

以下案例使用的正常路由,没有使用资源路由做的案例,走的是文件thinkphp/library/think/route/RuleItem.php

使用神器来打印一下数据。

执行流程

执行流程

以上就是全部的执行过程,接下来就需要解析检测URL和规则路由是否匹配的内容。

检测URL和规则路由是否匹配

在开始这里的内容之前先跟咔咔一起来解决一个问题。

来到上图代码的上层进行打印一下返回结果。

打印内容

打印结果

然后在来看一下路由配置文件route.php。

在这个文件中咔咔只配置了俩个路由地址,只有一个是资源路由,并且设置有变量规则。

路由配置文件

此时在给路由配置文件在加上一条路由地址。

路由配置文件

接着在文章开头的位置进行打印结果。

这里有没有一个疑问,就是为什么会返回false呢!

检测URL和规则路由是否匹配打印结果

解决为什么检测URL和规则路由是否匹配会返回false

那就需要来到源点进行查看了。

这块的内容咔咔在上文已经解释完了,这里的item会存在俩种情况,第一种为执行think\route\Resource Object,第二种为执行think\route\RuleItem Object

都会去执行check的那个方法。

执行流程的源点

很清晰的就是会知道肯定是在执行文件thinkphp/library/think/route/RuleItem.php中的check给予返回的。

检测路由

根据这俩行代码就能明白肯定就是$match这个变量有关系。

这个变量是执行的检测URL和规则路由是否匹配方法,这又回到开始的咱们说的内容了。

所以说源码就是这样一环套一环,慢慢看就行了,看的多了就会融会贯通了。

根据代码找关系

正式开始解析代码

参数说明

  • $url : 访问地址
  • $option : 合并分组参数
  • $completeMatch : 路由是否完全匹配

执行文件:文件:thinkphp/library/think/route/RuleItem.php

具体都执行了些神马让我们细细道来。

解析的源代码

首先会进一次路由是否完全匹配的判断,在解读路由的过程中出现了很多这样的判断,后期咔咔会单独出一篇文章来进行解读为什么需要进行判断。

接着代码会执行到合并路由规则,这个过程是把路由规则进行了合并。

来到方法getPattern,由于路由规则没有设置所以直接返回,路由规则的这个变量的初始值就是空数组,所以合并之后还是一个空数组。

第二行代码不知道大家会不会有疑问,到底是什么执行的。

在这个类的构造函数中进行了依赖注入将// 路由实例:think\Route注入了进来。

根据代码追踪就是从配置信息中获取配置信息,返回结果为string(1) "/"

合并路由规则

在接着对路由规则进行处理,将所有的|改为/

对路由规则进行处理

根据上图给返回的$rule可以明确的一件事就是下边的判断不会执行。

判断不会执行

在这里需要了解一个方法就是preg_quote:函数用于转义正则表达式字符

所以最后这个slash这个变量会返回为string(6) "\/\-\/",其中的反斜杠全是转义符。

preg_replace 函数通过一个正则表达式分隔字符串。

strncasecmp() 函数比较两个字符串(不区分大小写)。

preg_match_all 函数用于执行一个全局正则表达式匹配。

preg_match_all 函数用于执行一个全局正则表达式匹配。

在这个方法中有几个函数对于大部分来说都是很陌生的,只能自己去进行查资料了,咔咔也不会进行解释这几个方法的使用。

三、解析匹配到的规则路由-路由地址动态处理

在上文执行完的过程中会发生几种情况,咔咔这里给大家整理一下,由于写的比较多怕有时候看的时候就混淆了。

第一种情况

没有任何路由参数和路由规则,就单纯的路由地址。

单纯的路由地址

在这里打印一下检测URL和规则路由是否匹配的返回结果,以下的几种情况都使用的同一个地方打印,就不在写第二次了。

也就是当路由地址为纯路由地址时返回的是一个空数组。

返回检测URL和规则路由是否匹配结果

打印结果

第二种情况

设置有路由参数并且是必选参数,会返回参数和值并且是数组形式。

设置有路由参数并且是必选参数

返回检测URL和规则路由是否匹配结果

第三种情况

设置有资源路由参数并且是必选参数,会返回参数和值并且是数组形式,如果没有参数则返回false。

资源路由情况

返回检测URL和规则路由是否匹配结果

接下来在进入到本节的正题,为什么要做对以上的三种情况做出声明,请看下图咔咔圈出来的地方。

以上三种情况的打印结果就是$match这个的值,所以当$match值为false时整个流程会直接返回false。

不会在去执行解析匹配到的规则路由这一步骤。

检测路由

在这里会执行几个动作,分别为

  • 检查OPTIONS请求
  • 检查前置行为
  • 解析匹配到的规则路由

而且我们要学习的就是解析匹配到的规则路由这个里边的内容。

使用以下路由作为案例进行执行流程。

使用的路由做演示案例

解析匹配到的规则路由

参数说明

  • $request : object(think\Request) 请求类
  • $this->rule : string(13) "hello/<name?>" 路由规则
  • $this->route : string(17) "index/index/hello" 路由地址
  • $url : string(5) "hello" 请求地址
  • $option : array(1) { ["merge_rule_regex"] => bool(false)} 路由参数
  • $match : array(1) { ["name"] => string(1) "1"} 就是上边三种情况返回的值

执行文件:thinkphp/library/think/route/RuleItem.php,行号202.

解析匹配到的规则路由

接着就会进入到方法parseRule这个里边,这个方法的功能就是解析匹配到的规则路由,参数就是咔咔上边给写出来的六个参数。

由于路由参数是没有设置的所以不会去执行上边的那部分,接下来会对圈起来的地方进行深入的解析。

在这里主要就是替换路由地址中的变量。

解析匹配到的规则路由

这里的route和match这俩个值在上边的参数就已经做出说明了,可以翻上去看一下。

可以肯定的是肯定这个判断肯定会进行执行。

这里注意一下中间的部分,可以看到$replace$search执行了俩次。其实就是针对路由规则后边的地址进行了俩种方案。

路由规则后边的参数是可选的,所以说这俩个值分别为以下,请看图。

参数一

参数二

然后通过str_replace() 函数替换字符串中的一些字符(区分大小写)。

最后返回的route就是string(17) "index/index/hello"

接下来就是咔咔圈出来的地方,这块主要就是运用PHP函数来对请求地址进行的处理。

这里有一个方法作用是解析URL地址中的参数Request对象,但是因为URL在这里是个空值所以不会去执行。

在将三个值进行赋值,然后就开始执行了路由调度。

解析额外参数

本节就简单说到这里,在本节主要就是解析匹配到的规则路由,接下来就是进行路由调度。

路由调度是路由这块的最后一个流程,也是连接控制器的一个点,接下来咔咔会对路由调度进行详细解析。

续集

解析匹配到的规则路由中存在这行代码,在上文中没有做出对应情况说明,接下来对这种情况做出说明。

解析代码

路由地址设置为如下。

路由案例设置

此时在来打印一下刚刚那块的代码查看一下变化,到底有什么作用。

分别为俩个值$search和replace

最后就是通过str_replace进行替换的,所以并将参数换成hello。

请求地址

这里有一个非常重要的一个点就是关于str_replace这个函数的高级使用,平时都是用一个字符串替换另一个字符串,但是在本案例中是数组替换数组的方式。

如果你之前没有使用过这种方式,请自行搜索或者持续关注咔咔的发文,你会看到你想要的。

四、路由调度

在路由的这一节中这是咔咔最后讲解的一个重点,路由调度。

路由调度完就是控制器的执行了,不是说路由设置完就可以直接访问的。

解析匹配到的规则路由的最后一步就会执行路由调度。

执行文件 : thinkphp/library/think/route/Rule.php 763行.

参数说明

  • $request : object(think\Request) 请求类
  • $route : string(17) "index/index/hello"
  • $option : array(1) { ["merge_rule_regex"] => bool(false)}

发起路由调度

那么就来到发起路由调度的方法来进行深度解析。

发起路由调度

在这个方法中咔咔只对最常用的方式进行解析了,其它的方式根据自己阅读源码的方式进行阅读即可。

路由到模块控制器操作

来到解析URL地址为 模块/控制器/操作中。

解析URL地址为 模块/控制器/操作

关于这个list的使用在源码中也出现了很多次了,咔咔将打印结果给大家展示出来。、

打印的数据为$this->parseUrlPath($route);

打印结果

接下来在一张图中进行打印$path$var的值,你们就知道这个list是怎么使用的。

从下图中就可以看到list其实就是将数组的索引值赋值给list中的俩个变量而已。

打印的俩个变量

接下来就是获取操作,控制器,模块。

函数array_pop就是返回数组的最后一组数据然后返回,在这里也就是返回的操作。

控制器也是用同样的操作。

模块是会在配置文件中获取,最终也是通过array_pop来进行获取的。

至于使用请求类来获取请求方式,这个在后期会进行解析,这里只需知道返回的是请求方式即可。

最终的返回结果如下图。

路由到模块/控制器/操作的返回结果

在这里有一个问题,就是关于类的调用问题,跟这咔咔的节奏一点一点追踪。

解决最后一步的调用关系

路由到模块/控制器/操作类的调用关系

追踪这个函数第一步是进入到了类thinkphp/library/think/route/Rule.php这里

追踪第一步

然后在进行追踪Modul这个类。

又因为这个类继承着Dispatch这个类,所以还需要在继续追踪。

追踪第二步

来到thinkphp/library/think/route/Dispatch.php就可以看到这个构造函数,到这里也就是最后一步了。

thinkphp/library/think/route/Dispatch.php

然后在通过本类thinkphp/library/think/route/Dispatch.php的init方法,返回本类,也就是上层打印出来的结果。

返回本类

在这里可以对这个返回结果做一个调试看一下结果。

通过结果就知道最终就是从这里返回过去的。

打印返回返结果

返回结果

五、路由调度的最终结果返回给了谁?

路由调度的解析在这里就结束了,但是经过了一层一层的深扒已经进入到了框架的最底层位置了。

这个时候突然给返回了一个值,是不是有点懵,这是返回到哪里去了。

根据咔咔画的思维导图先来捋一遍。

  • 路由到模块/控制器/操作$this->dispatchModule
  • 发起路由调度$this->dispatch
  • 解析匹配到的规则路由return $this->parseRule
  • 执行 $this->checkRule检测路由
  • 执行think\route\RuleItem Object 中的check
  • 检查分组路由
  • 检测分组路由 : parent::check
  • 检测域名路由
  • 路由检测:dispatch=dispatch = this->route->check(path,path, must);
  • 路由检测 : $this->routeCheck()->init()

所以说根据咔咔的导图最开始的位置就是$this->routeCheck()->init()

如果图不清晰请及时在评论区见。

路由执行流程

也就是返回到了下图位置。

app类

然胡使用咔咔提供的神器来打印一下执行流程。

这里跟上边那个无序列表时反向的,跟这个流程执行也是一一致的。

第一流程

第二流程

截止到这里关于路由的所有内容就结束了,在路由这节有很多的内容,也不会去全面的解读,抓住要点进行解析即可。

总结

路由执行全流程

在这一节中主要针对解析匹配到的规则路由-路由地址动态处理,主要就是针对不同的路由规则参数,返回不同俩种结果。

主要就是针对上文中的一种情况就是路由规则和路由地址都带有参数时对参数进行的处理。

然后使用array_pop进行获取模块控制器方法,最终执行到路由调度。

在路由调度后一定要清晰的知道关于返回值是返回给了哪里,对执行的流程一定明确,可以根据咔咔给出的导图,或者使用神器来打印也可以。

路由源码解析到这里就大结局了,从应用初始化开始到路由调度返回值,这一个过程中有很多的过程,咔咔也仅仅是针对重要的进行了解析。

后期如有时间也会对其它情况进行详细的说明,路由这块也是框架最复杂的一部分,希望可以好好进行理解。