ThinkPHP3.2路由模式

Published on 2016 - 09 - 26

利用路由功能,可以让你的URL地址更加简洁和优雅。ThinkPHP支持对模块的URL地址进行路由操作(路由功能是针对PATHINFO模式或者兼容URL而设计的,暂时不支持普通URL模式)。

ThinkPHP的路由功能包括:

  • 正则路由
  • 规则路由
  • 静态路由(URL映射)
  • 闭包支持

规则路由

规则表达式

规则表达式通常包含静态地址和动态地址,或者两种地址的结合,例如下面都属于有效的规则表达式:

'my'         => 'Member/myinfo', // 静态地址路由
'blog/:id'   => 'Blog/read', // 静态地址和动态地址结合
'new/:year/:month/:day'=>'News/read', // 静态地址和动态地址结合
':user/:blog_id' =>'Blog/read',// 全动态地址

规则表达式的定义始终以“/”为参数分割符,不受URL_PATHINFO_DEPR设置的影响,该字段做URL分隔符的用途。

每个参数中以“:”开头的参数都表示动态参数,并且会自动对应一个GET参数,例如:id表示该处匹配到的参数可以使用$_GET['id']方式获取,:year、 :month 、:day 则分别对应$_GET['year']$_GET['month']$_GET['day']

数字约束

支持对变量的类型检测,但仅仅支持数字类型的约束定义,例如

blog/:id\d'=>'Blog/read',

表示只会匹配数字参数,如果你需要更加多的变量类型检测,请使用正则表达式定义来解决。

函数支持

可以支持对路由变量的函数过滤,例如:

'blog/:id\d|md5'=>'Blog/read',

表示对匹配到的id变量进行md5处理,也就是说,实际传入read操作方法的$_GET['id'] 其实是 md5($_GET['id'])。

注意:不支持对变量使用多次函数处理和函数额外参数传入。

可选定义

支持对路由参数的可选定义,例如:

'blog/:year\d/[:month\d]'=>'Blog/archive',

[:month\d]变量用[ ]包含起来后就表示该变量是路由匹配的可选变量。

以上定义路由规则后,下面的URL访问地址都可以被正确的路由匹配:

http://serverName/index.php/Home/blog/2013
http://serverName/index.php/Home/blog/2013/12

采用可选变量定义后,之前需要定义两个或者多个路由规则才能处理的情况可以合并为一个路由规则。

可选参数只能放到路由规则的最后,如果在中间使用了可选参数的话,后面的变量都会变成可选参数。

规则排除

非数字变量支持简单的排除功能,主要是起到避免解析混淆的作用,例如:

'news/:cate^add-edit-delete'=>'News/category'

因为规则定义的局限性,恰巧我们的路由规则里面的news和实际的news模块是相同的命名,而:cate并不能自动区分当前URL里面的动态参数是实际的操作名还是路由变量,所以为了避免混淆,我们需要对路由变量cate进行一些排除以帮助我们进行更精确的路由匹配,格式^add-edit-delete 表示,匹配除了add edit 和delete之外的所有字符串,我们建议更好的方式还是改进你的路由规则,避免路由规则和模块同名的情况存在,例如

'new/:cate'=>'News/category'

就可以更简单的定义路由规则了。

完全匹配

规则匹配检测的时候只是对URL从头开始匹配,只要URL地址包含了定义的路由规则就会匹配成功,如果希望完全匹配,可以使用$符号,例如:

'new/:cate$'=> 'News/category'

http://serverName/index.php/Home/new/info会匹配成功,而http://serverName/index.php/Home/new/info/2则不会匹配成功。

如果是采用

'new/:cate'=> 'News/category'

方式定义的话,则两种方式的URL访问都可以匹配成功。

完全匹配的路由规则中如果使用可选参数的话将会无效。

正则路由

正则路由也就是采用正则表达式定义路由的一种方式,依靠强大的正则表达式,能够定义更灵活的路由规则。

路由表达式支持的正则定义必须以“/”开头,否则就视为规则表达式。也就是说如果采用:

'#^blog\/(\d+)$#' => 'Blog/read/id/:1'

方式定义的正则表达式不会被支持,而会被认为是规则表达式进行解析,从而无法正确匹配。

下面是一种正确的正则路由定义:

'/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2',

对于正则表达式中的每个变量(即正则规则中的子模式)部分,如果需要在后面的路由地址中引用,可以采用:1、:2这样的方式,序号就是子模式的序号。

正则定义也支持函数过滤处理,例如:

'/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1|format_year&month=:2',

其中 year=:1|format_year 就表示对匹配到的变量进行format_year函数处理(假设format_year是一个用户自定义函数)。

静态路由

静态路由其实属于规则路由的静态简化版(又称为URL映射),路由定义中不包含动态参数,静态路由不需要遍历路由规则而是直接定位,因此效率较高,但作用也有限。

如果我们定义了下面的静态路由

'URL_ROUTER_ON'   => true, 
'URL_MAP_RULES'=>array(
    'new/top' => 'news/index?type=top'
)

注意:为了不影响动态路由的遍历效率,静态路由采用URL_MAP_RULES定义和动态路由区分开来

定义之后,如果我们访问: http://serverName/Home/new/top,其实是访问: http://serverName/Home/news/index/type/top

静态路由是完整匹配,所以如果访问: http://serverName/Home/new/top/var/test,尽管前面也有new/top,但并不会被匹配到news/index/type/top。

静态路由定义不受URL后缀影响,例如: http://serverName/Home/new/top.html 也可以正常访问。

静态路由的路由地址 只支持字符串,格式:[控制器/操作?]参数1=值1&参数2=值2

闭包支持

闭包定义

我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如:

'URL_ROUTE_RULES'=>array(
    'test'        => 
        function(){ 
            echo 'just test';
        },
    'hello/:name' => 
        function($name){ 
            echo 'Hello,'.$name;
        }

参数传递

闭包定义的参数传递在规则路由和正则路由的两种情况下有所区别。

规则路由

规则路由的参数传递比较简单:

'hello/:name' => 
    function($name){ 
        echo 'Hello,'.$name;
    }

规则路由中定义的动态变量的名称 就是闭包函数中的参数名称,不分次序。 因此,如果我们访问的URL地址是: http://serverName/Home/hello/thinkphp
则浏览器输出的结果是: Hello,thinkphp

如果多个参数可以使用:

'blog/:year/:month' => 
    function($year,$month){ 
        echo 'year='.$year.'&month='.$month;
    }

正则路由

如果是正则路由的话,闭包函数中的参数就以正则中出现的参数次序来传递,例如:

'/^new\/(\d{4})\/(\d{2})$/' => 
    function($year,$month){ 
        echo 'year='.$year.'&month='.$month;
    }

如果我们访问: http://serverName/Home/new/2013/03 浏览器输出结果是: year=2013&month=03

继续执行

默认的情况下,使用闭包定义路由的话,一旦匹配到路由规则,执行完闭包方法之后,就会中止后续执行。如果希望闭包函数执行后,后续的程序继续执行,可以在闭包函数中使用布尔类型的返回值,例如:

'hello/:name' => 
    function($name){ 
        echo 'Hello,'.$name.'<br/>';
        $_SERVER['PATH_INFO'] = 'blog/read/name/'.$name;
        return false;
    }

该路由定义中的闭包函数首先执行了一段输出代码,然后重新设置了$_SERVER['PATH_INFO']变量,交给后续的程序继续执行,因为返回值是false,所以会继续执行控制器和操作的检测,从而会执行Blog控制器的read操作方法。

假设blog控制器中的read操作方法代码如下:

public function read($name){
    echo 'read,'.$name.'!<br/>';
}

如果我们访问的URL地址是: http://serverName/Home/hello/thinkphp,则浏览器输出的结果是:

Hello,thinkphp
read,thinkphp!

路由应用实例

我们已经了解了如何定义路由规则,下面我们来举个例子加深印象。假设我们定义了News控制器如下(代码实现仅供参考):

namespace Home\Controller;
use Think\Controller;
class NewsController extends Controller{
    public function read(){
        $New = M('New');
        if(isset($_GET['id'])) {
            // 根据id查询结果
            $data = $New->find($_GET['id']);
        }elseif(isset($_GET['name'])){
            // 根据name查询结果
            $data = $New->getByName($_GET['name']);
        }
        $this->data = $data;
        $this->display();
    }

    public function archive(){
        $New = M('New');
        $year   =   $_GET['year'];
        $month  =   $_GET['month'];
        $begin_time = strtotime($year . $month . "01");
        $end_time = strtotime("+1 month", $begin_time);
        $map['create_time'] =  array(array('gt',$begin_time),array('lt',$end_time));
        $map['status']  =   1;
        $list = $New->where($map)->select();
        $this->list =   $list;
        $this->display();
    }
}

定义路由规则如下:

'URL_ROUTER_ON'   => true, //开启路由
'URL_ROUTE_RULES' => array( //定义路由规则 
    'new/:id\d'    => 'News/read',
    'new/:name'    => 'News/read',
    'new/:year\d/:month\d'  => 'News/archive',
),

然后,我们访问: http://serverName/index.php/Home/new/8,会匹配到第一个路由规则,实际执行的效果等效于访问: http://serverName/index.php/Home/News/read/id/8

当访问: http://serverName/index.php/Home/new/hello,会匹配到第二个路由规则,实际执行的效果等效于访问: http://serverName/index.php/Home/News/read/name/hello

那么如果访问: http://serverName/index.php/Home/new/2012/03,是否会匹配第三个路由规则呢?我们期望的实际执行的效果能够等效于访问: http://serverName/index.php/Home/News/archive/year/2012/month/03

事实上却没有,因为http://serverName/index.php/Home/new/2012/这个URL在进行路由匹配过程中已经优先匹配到了第一个路由规则了,把2012当成id的值传入了,这种情况属于路由规则的冲突,解决办法有两个:

调整定义顺序

路由定义改成:

'URL_ROUTE_RULES' => array( //定义路由规则
    'new/:year\d/:month\d'  => 'News/archive',
    'new/:id\d'             => 'News/read',
    'new/:name'    => 'News/read',
),

接下来,当我们再次访问: http://serverName/index.php/Home/new/2012/03,的时候,达到了预期的访问效果。所以如果存在可能规则冲突的情况,尽量把规则复杂的规则定义放到前面,确保最复杂的规则可以优先匹配到。但是如果路由规则定义多了之后,仍然很容易混淆,所以需要寻找更好的解决办法。

利用完全匹配功能

现在我们来利用路由的完全匹配定义功能,把路由定义改成:

'URL_ROUTE_RULES' => array( //定义路由规则
    'new/:id\d$'   => 'News/read',
    'new/:name$'    => 'News/read',
    'new/:year\d/:month\d$'  => 'News/archive',
),

在规则最后加上$符号之后,表示完整匹配当前的路由规则,就可以避免规则定义的冲突了。对于规则路由来说,简单的理解就是URL里面的参数数量或者类型约束要完全一致。 所以,如果我们访问 http://serverName/index.php/Home/new/2012/03/01的话,是不会匹配成功任何一条路由的。

利用正则路由

当然,解决问题的办法总是不止一种,对于复杂的情况,我们不要忘了使用正则路由规则定义,在你找不到解决方案的时候,正则路由总能帮到你。 要实现上面的同样路由功能的话,还可以用下面的规则定义:

'URL_ROUTE_RULES' => array( //定义路由规则
    '/^new\/(\d+)$/'        => 'News/read?id=:1',
    '/^new\/(\w+)$/'        => 'News/read?name=:1',
    '/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2',
),

参考文献