ThinkPHP路由源码解析(一)

举报
原来是咔咔 发表于 2022/03/28 01:02:38 2022/03/28
【摘要】 路由是项目开发中比较重要的一个环节,每个项目都会使用路由进行管理接口,接下来本文会从源码方面带大家一起学习路由。 框架路由解析 前言一、路由初识化简单分析二、通过定义路由再谈门面三、路...

路由是项目开发中比较重要的一个环节,每个项目都会使用路由进行管理接口,接下来本文会从源码方面带大家一起学习路由。

前言

使用框架写过项目的肯定都使用过路由,使用路由来进行接口的管理,那么为什么要使用路由呢!

使用路由会保护项目的真实请求路径。

使请求地址更加规范和简洁,在开发过程中方法名有时候会很长,就可以直接使用路由进行简洁处理。

可以统一对请求请求进行拦截并且进行权限检查的操作。

并且在5.1版本支持了注解路由,方便在开发的过程中进行调试。

方便直接对请求进行缓存,并且还支持了路由中间件。

接下来咔咔会对路由的方方面面进行全面的解析,并且会给大家带上脑图方便大家最直观的预览。

执行流程图

一、路由初识化简单分析

在框架执行流程那一篇文章中,都知道路由初始化是在初始化应用那个过程中执行的。

路由初始化位置

然后进入到routeInit这个方法,进行代码解析。

来到这个方法先看代码注释,注释为导入路由定义规则。

这段代码的全部我给复制出来了,接下来就是对这段代码进行解析。

导入路由定义规则

/**
     * 路由初始化 导入路由定义规则
     * @access public
     * @return void
     */
    public function routeInit()
    {
        // 路由检测
        // scandir:返回置顶目录的文件数组形式
        $files = scandir($this->routePath);
        foreach ($files as $file) {
            if (strpos($file, '.php')) {
                $filename = $this->routePath . $file;
                // 导入路由配置
                $rules = include $filename;
                if (is_array($rules)) {
                    $this->route->import($rules);
                }
            }
        }

        if ($this->route->config('route_annotation')) {
            // 自动生成路由定义
            if ($this->appDebug) {
                $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix');
                $this->build->buildRoute($suffix);
            }

            $filename = $this->runtimePath . 'build_route.php';

            if (is_file($filename)) {
                include $filename;
            }
        }
    }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

首先会获取route目录下的文件,函数scandir会返回指定目录的文件并且用数组形式返回。

这里返回结果有三个,第一个为当前目录,第二个为父级目录。这俩个数据不用过多追究。

route目录下的文件

返回结果

接着就会将route目录下存在php结尾的文件给导入进来,也就是route.php文件。

导入路由配置

当把路由文件导入进来之后,进行了一次判断然后进行导入规则。

但是在路由文件可以看到是没有返回任何数据的。

路由文件

路由文件的return演示

那么这里的return是干嘛的呢!在5.1版本之前是没有这一操作的,但是在5.1是存在的,接下来咔咔给大家演示一下这个使用方法。

首先在路由文件的return中配置一条数据。

路由文件配置新的数据

然后在index文件中创建一个新的方法vpn

index文件配置的新方法

此时可以直接访问路由vpn即可。

返回结果

当route文件的return存在的数据的时候,就会执行到$this->route->import($rules);这一步,本节暂时不对这里做出探讨,会在后文中大家详细说明。

下半截源码阅读

路由初始化下半部分

第一行代码知道这里的数据是从哪里来的吗?

在应用初始化的initialize方法中,执行了init方法,在init方法中执行了容器中实例进行配置更新,在哪里进行设置的。如下图

也就说在这里给路由添加的配置,并且可以看到路由的配置文件是来源于app配置文件。

路由配置的由来

想要知道这个配置是干什么的就需要从根源去追溯,所以就需要到app的配置文件中去寻找。

可以看到这个参数指的是注解路由的开关。

app配置文件

在紧接着就是根据注释自动生成路由规则,这一块的代码暂时就说到这里,下文会对这些代码进行详细的说明。

二、通过定义路由再谈门面

这里只是简单提一下,关于路由的一些使用方法之类的就可以直接去文档去看了,咔咔也不用做搬运工在搬过来。

先在路由文件定义一个kaka的路由。

路由文件

但是这个get方法是怎么执行的呢!代码追踪也直接追不过去,那么这个时候应该怎么做。

第一种办法,在框架中所有的基础类都是注册了门面模式的,所以在路由里边引入facade就可以进行代码追踪了。

但是既然都把门面模式的源码都看了怎么还会去使用第一种办法,肯定要把其中的一些小细节给扒出来。

在base文件中已经注册了门面的别名了,所以说在引入文件时可以使用use Config,也可以使用use think\facade\Config;

注册类库别名

这个时候就会来到thinkphp/library/think/facade/Config.php这个文件。

可以看到是继承了门面类的。

config的门面文件

来到Facade这个类中,拉到文件最后可以看到一个方法__callStatic,这个方法是当调用不存在的静态的方法时就会执行这个方法。

门面类方法

最后就会去执行创建门面类的方法,最终会通过容器返回返回对应的实例,比如使用的是Config,最终就会返回Config的这类的实例。

创建门面类方法

最后就会回到开始,使用call_user_func_array进行执行对应实例的方法。

static::createFacade()会返回Config的对应实例。

最终执行方法

这一块内容就说到这里了,虽然说之前在门面中也提到了但是在这里在回顾一下也是很有必要的,为故而知新嘛!

三、路由定义rule方法中的$this->group到底执行了什么

执行流程图

通过定义路由再谈门面这一节中,就可以知道下图中的rule方法会执行那个类的那个方法。

路由文件

根据之前咔咔给大家介绍的方法就可以知道最终会执行到thinkphp/library/think/Route.php这个文件中的rule方法。

注册路由规则

这里就会出现一个问题就是这个group是什么,是怎么执行的,其实在上边的图已经描述的很明白了。

咔咔在用文字给大家在说一遍,可以对照着进行查看源码。

先看一下这个方法熟悉不熟悉。

没错这个方法在容器那一节中进行了特别的讲解,如果不明白的去前言查看之前的文章进行查看。

那么这个方法在哪里执行的,咔咔就直接说了,就不说的那么细了。

route类中的make方法

当使用Route::rule();时就会执行到门面的__callStatic方法。

这个方法已经说了好多次了,当执行的静态方法不存在时就会执行。

门面的魔术方法

然后就会去执行createFacade这个方法。

在这个方法需要特别注意最后一行,也就是圈出来的哪一行代码。

这里需要注意一下static::class,它返回的就是Route类,至于是怎么返回的之前也说过,如果不会的可以评论区见哈!

所以make方法的第一个参数就是Route。

进入容器方法

接着就会进入到容器类的make方法,在这个方法中,注意咔咔圈的俩处。

在容器类中一定要注意的四个属性,咔咔给大家画了出来。

容器类的四大属性

第一次进来会存入到容器标识名中。

并且第二次执行make方法时传的是标识名的值。

直到这里就会执行到咔咔圈的第二处,执行invokeClass这个方法。

make方法

来到invokeClass方法中。

在这个方法中主要使用的是反射的知识。

首先会把传过来的think\Route,进行反射类的实例化。

然后会去判断think\Route中是否存在__make方法。

invokeClass方法

最后就会去执行Route类的__make方法。

并且进行依赖注入App类和Config类。

同时进行了实例化了本类就是代码$route = new static($app, $config);

那么就会去执行构造函数。

route类中的make方法

接着来到构造函数的方法中查看。

这里一定要注意第二步的注释,这个已经说了很多次了。

如果有疑问就是为什么使用了ArrayAccess。

咔咔就给大家捋一下,首先App类是继承的Container类。

Container类又继承着ArrayAccess类,所以就可以使用像之前的说,像数组一样访问对象。

  • 注入的App类
  • 此处使用了ArrayAccess像数组一样访问对象,但是$app中不存在request属性,所以就会去执行容器类中的__get魔术方法,在__get方法中调用的是容器中的make方法,第一个参数为request,最终会返回request的实例。
  • 注入的config类
  • 此属性返回域名,
  • 初始化默认域名

Route文件的构造函数

接着就会来到方法setDefaultDomain

这里需要注意一下咔咔圈的地方,在这一节的开头就使用的这个属性来执行的rule中的addRule方法。

这个group就是think\Route\Domain这个类。

又因为think\Route\Domain继承着think\Route\Group

继承

所以会执行到think\Route\Group到这个类里边。

这下所有的流程就都已经理顺了。

初始化默认域名

通过上面的这一顿分析和刨铣相信大家对这一块的内容就十分了解了。

四、路由规则预处理

接下来的内容就是对路由规则的处理方式进行解析。

路由规则处理方法

这几个参数就对前俩个做一个简单的打印,然后看一下这俩个参数分别都是什么。

参数rule

参数rule

参数$route

参数route

可以看到这俩个参数几个就是 路由的前半部分,一个是路由的后半部分。

路由文件

从而就可以得知代码会执行到下图第一处圈起来的地方。

addRule方法

此时需要注意一点就是在上图中圈起来的第二处地方,这个地方会在什么时候执行呢!

就是当下图路由这样设置的时候才会执行那段代码。

这里的路由设置只是为了做演示,在实际工作中不能这样设置路由啊!第二个路由地址会把第一个路由地址给覆盖的。

路由文件

接着代码就会执行到创建路由规则实例,也就是下图圈出来的地方。

创建路由规则实例

关于创建路由规则实例的几个参数需要进行简单的介绍一下

第一个参数:$this->router

参数设置

第二个参数:$this,就是指的think\route\Domain

接下来的几个地址就是路由规则和地址,就没有必要看了,主要就是第一个参数。

接下来就需要进入到创建路由规则实例的方法中

代码就会追踪到thinkphp/library/think/route/RuleItem.php这个类中。

在这个类中做的事情就是设置规则,也就是下图咔咔圈出来的地方。

创建路由规则实例的本类

路由规则预处理setRule方法解读。

上图中最后执行的流程就会来到setRule这个方法。

这段代码没有我们眼看这那么容器阅读,接下来就跟着咔咔一步一步的去阅读这块代码。

路由规则预处理

1.首先代码就会执行这部分

路由预处理第一部分代码

从一开始就进行了一个简单的判断$,那么这个是从哪里过来的呢!

在文件thinkphp/library/think/route/RuleGroup.php这里就对$进行了处理。

thinkphp/library/think/route/RuleGroup.php

所以说在什么情况下会执行预处理的第一部分呢!

我们来看一下简单的案例。

设置路由做测试

然后在路由预处理中进行打印,就会发现打印结果后边会多一个$符号。

在路由规则预处理中进行打印

打印结果

substr介绍

这里对substr进行简单的说明一下,碰到每一个知识点都简单的回顾一下。

这个方法用来字符串截取。

参数一:需要截取的字符串

参数二:从什么位置开始截取,正数从左往右,负数结尾开始,0 - 在字符串中的第一个字符处开始

参数三:长度,正数从参数二进行返回,负数从末端进行返回。

所以说最终这块代码的执行会把$这个符号取消掉,只留为/,打印结果如下图。

打印结果

接着就是对路由预处理的下半部分进行解析。

先进行一次移除左侧所有的斜杠的处理。

路由预处理下半部分

在接下来的$this->parent大家有有没有一点点疑问,这里是指的哪里。

参数是从这里开始进行传递的。

经过上文的分析,我们知道$this->group就是实例化的think\route\Domain

Route类

紧接着会跳转到下图,这里的$this就是就是think\route\Domain,要不就没办法解释的。

这块有问题的可以去看第三节文章。

this的解释

所以说这里的$parent就是think\route\Domain这个类。

thinkphp/library/think/route/RuleItem.php

所以说这块代码就迎刃而解了。

所以说第三节哪里没看明白的一定要自己拉着代码好好的看,否则越往后越不明白。

这块代码具体做了什么事情后边在深入详解,这里就主要针对$this->parent它是怎么执行的就可以了。

类thinkphp/library/think/route/RuleItem.php

在接着就是这块代码,这块就是对理由参数做了俩中处理。

把带有符号的参数转化。

例如::name 转化为 <name>

放在代码中就是$this->rule = <name>

类thinkphp/library/think/route/RuleItem.php

五、解析生成路由标识的快捷访问

从路由处理规则之后还有一步操作就是生成路由标识的快捷访问

路由规则中的变量

但是在前边我们需要对路由规则中的变量做一点简单的说明。

下图圈出来的地方就是将要解析的内容。

路由规则变量

来到parseVar这个方法,这个方法是在thinkphp/library/think/route/Rule.php这个类里,因为本类继承的rule类

那么接下来就是要对这一整块的内容作出详细的说明了。

路由规则中的变量

首先要先明白preg_match_all的三个参数是什么。

参数一:需要搜索的字符串

参数二:输入的字符串

参数三:多维数组,作为输出参数输出所有匹配结果, 数组排序通过flags指定。

所以说最终的匹配结果会存放在变量$matches中。

那么先来打印一下$matches这个变量。

这里需要先说明一个关于PHP版本的问题

此次使用的路由案例为下图

路由案例

在打印这个值的时候,咔咔遇到了一点点问题,在PHP7.3.4中使用dump是无法显示数据的,但是在dubug调试下会显示。

打印结果

在PHP版本7.2.9下dump是显示的。

打印结果

关于这个问题,咔咔就先绕过了,如果有大佬知道情况的可以给咔咔简单的那么唠一唠,这里咔咔就使用PHP7.2.9来进行讲解了。

后期咔咔在针对这个问题进行简单的说明一下,遇到问题就需要去解决嘛!

那么你知道在上图的打印结果中<name?>这个是怎么来的吗?不知道的赶紧看下图,在上文已经说过了。

在到这里说一次哈!

就是下图为路由规则预处理的地方进行正则处理的,看到这里关于上面的那个问题,咔咔初步认为在不同版本中要木preg_replace这个方法进行了改动,要木就是关于正则方面进行了改动。

thinkphp/library/think/route/RuleItem.php

所以说把这块搞定后,其余的代码就是毛毛雨啦!

这里就是在根据?第一次出现的问题进行判断。

thinkphp/library/think/route/Rule.php

在上文中咔咔给出的路由案例中相信大家也看到了另外一个跟案例很相似的路由。

这俩组路由参数分别为参数必有,和可选参数。

在打印截图中咔咔只是说明了一种情况,另一种情况就需要大家去进行测试了。

最终会把结果返回给$var,接下来我们就光打印一下俩种情况的返回值就可以了。

情况一:Route::get(‘hello/[:name]’, ‘index/index/hello’);

Route::get('hello/[:name]', 'index/index/hello');

情况二:Route::get(‘hello/:name’, ‘index/index/hello’);

Route::get('hello/:name', 'index/index/hello');

从而可以得出结论就是,当参数可选时为2,必选时为1。

截止到这里关于路由规则中的变量一些处理就简单说到这里。

这块的代码没什么难的,只要会preg_match_allstrpossubstr就可以了。

解析生成路由标识的快捷访问

接下来就继续来看这块内容。

咔咔下图圈出来的就暂时先不用看,在下文中会单独提出来来说这一块。

路由参数

那么接着就需要看一个$value这个值是什么了。

下图就是打印结果,可以看到都包含了路由规则,参数是否可选,请求域名,请求方式。

打印结果

最后又来到我们最熟悉的容器模块了Container::get('rule_name')->set($name, $value, $first);,也就是这行代码。

参数就是rule_name

容器的get方法

然后执行make方法,因为在容器类里边已经把rule_name进行了绑定。

绑定

最终通过反射将object(think\route\RuleName)返回回来,并且存放在容器里边。

此时就相当于是$this->instances['think\route\RuleName'] = object(think\route\RuleName);

存放容器

最后一步就是设置路由规则。

设置路由规则

截止到这里就把路由标识的快捷访问就解析完了。

由于路由这块的内容还有将近一半的内容,一文写完篇幅有点太长

本文就到这里,下一文接着路由在来谈其它内容。

六、总结

在这一文中主要针对路由的执行流程、通过定义路由的方式在一次对门面进行深究。

同时对rule方法中的group进行了特别详细的讲解,这块的内容必须要好好的研究一下,类与类之间的继承一定要注意。

在就是关于路由规则的预处理,就是对于路由的前半部分进行处理。

主要就是以下的三句话。

把带有:符号的参数转化。

例如::name 转化为 。

放在代码中就是$this->rule = <name>

紧接着就是对生生路由标识的快捷访问进行了源码的阅读,在这一节最重要的就是对变量的处理。

判断参数是可选的还是不可选的,可选为2,不可选为1,作为标识。

以下是咔咔画出来的脑图。

这个脑图还没有写完整,后边还有内容,在下文会进行详细的补充。

route类的执行流程

最后就是在给大家把这个图给大家放出来,这个图是最详细的一个执行流程。

关于类与类之间的执行关系,都在图中有详细的描述。

执行流程

坚持学习、坚持写博、坚持分享是咔咔从业以来一直所秉持的信念。希望在偌大互联网中咔咔的文章能带给你一丝丝帮助。我是咔咔,下期见。

文章来源: blog.csdn.net,作者:咔咔-,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/fangkang7/article/details/109275988

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200