TP的路由

这里是用 thinkphp 3.2.0 版本的框架来做分析的

TP 的路由支持的还是蛮多的,这里将解析 TP 路由的源码。

一些准备

在准备开始处理 PATH_INFO 的时候,先对其做了一些过滤。

去除了后面的 URL_PATHINFO_DEPR,去除了结尾的 .php (当然结尾是看定义的)。

为了保证分隔符的统一,之后又进行了一次替换。

静态路由

首先系统会先解析静态路由,而静态路由是用户自己配置的。

关于路由的定义,可以看一下官方文档 路由定义。

其实通过这段代码很容易分析出,所谓的静态路由其实就是一些路由的映射,不包含动态参数。适用的场景很少。

经历了 parseUrl 之后,获取到了相关参数,并合并到 $_GET 中。

这里值得注意的就是,$_GET 是第二个参数,也就是说,$var 存在被覆盖的可能性。(这是一个隐性的坑,也表明,这个静态配置可以动态修改)

到这里,静态路由解析已经结束。

动态路由

如果没有配置,直接结束。

如果配置了,则会对路由规则进行遍历。

两种配置方式

可以说,TP 的兼容性真的很强,支持多种写法,虽然我不建议。

上面的代码,为下面两种写法做了铺垫:

 ['address', 'options']
]

// 第二种写法
[
    ['rule', 'address', 'options']
]

一些过滤

如果 $route 是数组,并且有第二个参数,那么会进行一些列过滤。

  • 后缀过滤
  • 请求方式过滤(很棒的一个东西)
  • 回调函数的过滤

正则匹配

如果 $rule 是以 / 开头,并且正则匹配路由能够通过,那么,就按照正则路由处理。

如果正则是以 # 开头的就不行了,这点需要注意。

如果 $route 是一个函数,则执行函数。(关于 invokeRegx,要手动在 匿名函数 中给 $_GET 赋值我们匹配到的路由参数)

如果 $route 不是函数,则直接当做正则来解析。详细的 parseRegex 解析看最后的 parseRegex 部分。

规则路由

=$len2 || strpos($rule,'[')) {
    ...
}

这里的其实也是正则解析,但是有一点不同的是,这里只有出现可选路由的时候或者请求路由的层级大于等于规则的层级才能够被匹配。

全匹配

如果 $rule 是以 $ 结尾的,那么表示 请求路由 的层级需要和 规则 的层级保持一致才可。

接下来会解析规则,使用的函数是 checkUrlMatch

获得匹配的数据之后,和之前的处理方式差不多,如果 $rule 是函数,则执行,不是则匹配。

部分源码解析

parseUrl 解析

这里的 $url 参数支持三种格式:

  • path + query (/news/index?type=top)
  • path (news/index)
  • query (m=news&a=top)

解析的顺序,从上到下。

path + query

格式:/news/index?type=top

通过判断是否有 ? 辨别是否属于第一种情况。

通过 parse_url 解析 path 得到如下结构:

Array
(
    [path] => /news/index
    [query] => type=top
)

接下来可以分别解析 pathquery,并通过 parse_str 将解析的 query 存入 $var,从而向后流转。

path

格式:news/index

这个就简单了,如果存在 / 就直接解析 path

query

格式:m=news&a=top

直接解析 query 就简单多了,直接放入 $var 向后流转。

path 的最后解析

第一种模式和第二种模式都会解析出 path。这里对 path 又进行了一次操作。

这里有段逻辑,会从 path 解析的数组最后开始弹出,第一个弹出的是 action,然后 controllermodule,如果某个环节发现 path 数组被弹空之后,后续的就无法再赋值了。

parseRegex 解析

通过一个例子来看一下整个流程。

假设规则如下:

// 规则
'URL_ROUTE_RULES' => [
    '/^blog\/(\w+)$/' => 'Blog/read/id/:1'
]

请求链接是 http://demo.com/blog/123/page/1

在执行 parseRegex 之前会根据正则 /^blog\/(\w+)$/ 匹配出如下结果:

array(2) {
  [0]=>
  string(8) "blog/123"
  [1]=>
  string(3) "123"
}

接下来执行 parseRegex

获取路由 Blog/read/id/:1,通过 preg_replace_callback:1 替换成 123。最终获得 $urlBlog/read/id/123

如果匹配出来的 $url/ 或者 http 开头,则会执行跳转...(很神奇的操作,也很坑,万一没注意加了一个 /,还真不一定能够很快定位问题在哪里)。

如果不跳转,就开始解析 $url 获得参数。

此时的 $var

array(4) {
  ["a"] => string(3) "123"
  ["m"] => string(2) "id"
  ["g"] => string(4) "read"
}
$val){
    if(strpos($val,'|')){
        list($val,$fun) = explode('|',$val);
        $var[$key]    =   $fun($val);
    }
}

参数获得之后,会去解析每个参数,解析执行 函数过滤处理

解析剩余的URL参数 /page/1,并将参数给 $var

此时的 $var

array(4) {
  ["a"] => string(3) "123"
  ["m"] => string(2) "id"
  ["g"] => string(4) "read"
  ["page"] => string(1) "1"
}

解析路由剩下的部分,并且与 $var 合并。最后赋值给 $_GET

invokeRegx 解析

// 执行正则匹配下的闭包方法 支持参数调用
static private function invokeRegx($closure, $var = array()) {
    $reflect = new \ReflectionFunction($closure);
    $params  = $reflect->getParameters();
    $args    = array();
    array_shift($var);
    foreach ($params as $param){
        if(!empty($var)) {
            $args[] = array_shift($var);
        }elseif($param->isDefaultValueAvailable()){
            $args[] = $param->getDefaultValue();
        }
    }
    return $reflect->invokeArgs($args);
}
getParameters();

通过反射获取到函数的实体和参数。

因为 $var 是正则匹配后的结果,值如下:

array(2) {
  [0] => string(8) "blog/123"
  [1] => string(3) "123"
}

而下标为0的没有作用,这里直接将其从数组里弹出。

isDefaultValueAvailable()){
        $args[] = $param->getDefaultValue();
    }
}

遍历参数,并拼凑参数。

如果 $var 里有值则按照顺序弹出。如果没有,则获取参数的默认值。

invokeArgs($args);

最后执行函数。

checkUrlMatch 解析

假设规则如下:

'URL_ROUTE_RULES' => [
    'blog/:id/[:page\d]'   => 'Blog/read/id/:1'
]

请求链接为 blog/123/1

将路由和规则都解析成数组,然后依据规则的数组开始逐个解析。

解析后的结果如下:

// m1
array(3) {
  [0] => string(4) "blog"
  [1] => string(3) "123"
  [2] => string(1) "1"
}
// m2
array(3) {
  [0] => string(4) "blog"
  [1] => string(3) ":id"
  [2] => string(13) "[:page\d]"
}

如果是可选的,那么会移除掉两边的东西 [],保留剩余的部分。例如:[:page\d] -> :page\d

if(':' == substr($val,0,1)) {
} else if (0 !== strcasecmp($val,$m1[$key])){
    return false;
}

如果不是以 : 开头,并且在 $m1 相同的位置的值一致(忽略大小写),则表明正常,否则返回错误。例如:blog

如果是以 : 开头的,则会检测是否存在 |,如果存在则截取。例如::page^1|2|3 -> :page^1。但是这个和后面的匹配规则冲突,所以在 3.2.2 中,将 | 改为了 -。(这个版本中,不建议使用,因为没用)

如果检测到字符串里存在 \,并且不在首位,则会获取类型,这里只支持一位(很重要)。

如果还是数字的话 d,会从 $m1 中获取相应的值,并匹配类型。

3.2.0 版本中,这个可以忽略,和上面说道的地方冲突了,不好用。

如果不存在 \^,则直接截取。例如::id -> id

最后返回 $var

invokeRule 解析

invokeRegx 差不多,不同的地方在于 $var 的结构不同。

小伙伴们可以参照上面的解析来看这段源码。

parseRule 解析

这里和上面的 parseRegex 有很多类似的地方,可以调试查看,这里就不重复太多了。

最后

TP 的路由写的比较复杂,适合的情况也有很多,但是限制也不少,需要了解的情况太多,而且还存在不少坑。

你可能感兴趣的:(TP的路由)