ThinkPHP框架对URL有一定的规范,所以如果你希望定制你的URL格式的话,就需要好好了解下内置的路由功能了,它能让你的URL变得更简洁和有文化。
启用路由
要使用路由功能,前提是你的URL支持PATH_INFO,并且在项目配置文件中开启路由:
'URL_ROUTER_ON' => true, //开启路由
然后就是配置路由规则了,使用
URL_ROUTE_RULES参数进行配置,配置格式是一个数组,每个元素都代表一个路由规则,例如:
'URL_ROUTE_RULES'=>array(
'news/:year/:month/:day' => array('News/archive', 'status=1'),
'news/:id' => 'News/read',
'news/read/:id' => '/news/:1',
),
系统会按定义的顺序依次匹配路由规则,一旦匹配到的话,就会定位到路由定义中的模块(支持分组)和操作方法去执行,并且后面的规则不会继续匹配。
路由定义
路由规则的定义方式如下:
'路由表达式'=>'路由地址和额外参数'
路由表达式
路由表达式包括规则路由和正则路由的定义表达式,只能使用字符串。
表达式 |
示例 |
正则表达式 |
/^blog\/(\d+)$/ |
规则表达式 |
blog/:id |
正则表达式
路由表达式支持的正则定义必须以“/”开头,否则就视为规则表达式。也就是说如果采用
'#^blog\/(\d+)$#'
方式定义的正则表达式不会被支持,而会被认为是规则表达式进行解析,从而无法正确匹配。
'/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2',
对于正则表达式中的每个变量(即正则规则中的子模式)部分,如果需要在后面的路由地址中引用,可以采用:1、:2这样的方式,序号就是子模式的序号。
更多的关于如何定义正则表达式就不在本文的描述范畴了。
规则表达式
3.0的规则路由是从2.1的简单路由进化而来,虽然无法完美实现正则路由的功能,但是相比简单路由确实增强了不少,而且比正则路由更方便定义和容易理解。
规则表达式通常包含静态地址和动态地址,或者两种地址的结合,例如下面都属于有效的规则表达式:
'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',
表示只会匹配数字参数,如果你需要更加多的变量类型检测,请使用正则表达式定义来解决。
规则排除
非数字变量支持简单的排除功能,主要是起到避免解析混淆的作用,例如:
'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/new/info
会匹配成功
而
http://serverName/index.php/new/info/2
则不会匹配成功
如果是采用
'new/:cate'=> 'News/category',
方式定义的话,则两种方式的URL访问都可以匹配成功。
路由地址和额外参数
路由地址和额外参数表示前面的路由表达式最终需要路由到的地址并且允许隐式传入URL里面没有的一些参数,这里允许使用字符串或者数组方式定义,支持下面5种方式定义:
定义方式 |
定义格式 |
方式1:路由到内部地址(字符串) |
'[分组/模块/操作]?额外参数1=值1&额外参数2=值2...' |
方式2:路由到内部地址(数组)参数采用字符串方式 |
array('[分组/模块/操作]','额外参数1=值1&额外参数2=值2...') |
方式3:路由到内部地址(数组)参数采用数组方式 |
array('[分组/模块/操作]',array('额外参数1'=>'值1','额外参数2'=>'值2'...)) |
方式4:路由到外部地址(字符串)301重定向 |
'外部地址' |
方式5:路由到外部地址(数组)可以指定重定向代码 |
array('外部地址','重定向代码') |
如果路由地址以“/”或者“http”开头则会认为是一个重定向地址或者外部地址,例如:
'blog/:id'=>'/blog/read/id/:1'
和
'blog/:id'=>'blog/read/'
虽然都是路由到同一个地址,但是前者采用的是301重定向的方式路由跳转,这种方式的好处是URL可以比较随意(包括可以在URL里面传入更多的非标准格式的参数),而后者只是支持模块和操作地址。举个例子,如果我们希望avatar/123重定向到
/member/avatar/id/123_small的话,只能使用:
'avatar/:id'=>'/member/avatar/id/:1_small'
路由地址采用重定向地址的话,如果要引用动态变量,也是采用:1、:2 的方式。
采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用,例如:
'blog/:id'=>'http://blog.thinkphp.cn/read/:1'
表示当前网站(可能是http://thinkphp.cn)的 blog/123地址会直接重定向到 http://blog.thinkphp.cn/read/123。
在路由跳转的时候支持额外传入参数对(额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用,后面我们会提到),支持“额外参数1=值1&额外参数2=值2”或者array('额外参数1'=>'值1','额外参数2'=>'值2'...)这样的写法,可以参考不同的定义方式选择。例如:
'blog/:id'=>'blog/read/?status=1&app_id=5',
'blog/:id'=>array('blog/read/?status=1&app_id=5'),
'blog/:id'=>array('blog/read/','status=1&app_id=5'),
'blog/:id'=>array('blog/read/',array('status'=>1,'app_id'=>5)),
上面的路由规则定义中额外参数的传值方式都是等效的。status和app_id参数都是URL里面不存在的,属于隐式传值,当然并不一定需要用到,只是在需要的时候可以使用。
实例说明
通过上面的讲解,我们了解了如何定义路由规则,下面我们来举个例子加深印象。
假设我们定义了News控制器如下(代码实现仅供参考):
class NewsAction extends Action{
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_ROUTE_RULES' => array( //定义路由规则
'new/:id\d' => 'News/read',
'new/:name' => 'News/read',
'new/:year\d/:month\d' => 'News/archive',
),
然后,我们访问:
http://serverName/index.php/new/8
会匹配到第一个路由规则,实际执行的效果等效于访问:
http://serverName/index.php/News/read/id/8
当访问:
http://serverName/index.php/new/hello
会匹配到第二个路由规则,实际执行的效果等效于访问:
http://serverName/index.php/News/read/name/hello
那么如果访问:
http://serverName/index.php/new/2012/03
是否会匹配第三个路由规则呢?我们期望的实际执行的效果能够等效于访问:
http://serverName/index.php/News/archive/year/2012/month/03
事实上却没有,因为http://serverName/index.php/new/2012/这个URL在进行路由匹配过程中已经优先匹配到了第一个路由规则了,把2012当成id的值传入了,这种情况属于路由规则的冲突,解决办法有两个:
1、调整定义顺序
路由定义改成:
'URL_ROUTE_RULES' => array( //定义路由规则
'new/:year\d/:month\d' => 'News/archive',
'new/:id\d' => 'News/read',
'new/:name' => 'News/read',
),
接下来,当我们再次访问:
http://serverName/index.php/new/2012/03
的时候,达到了预期的访问效果。所以如果存在可能规则冲突的情况,尽量把规则复杂的规则定义放到前面,确保最复杂的规则可以优先匹配到。但是如果路由规则定义多了之后,仍然很容易混淆,所以需要寻找更好的解决办法。
2、利用完全匹配功能
现在我们来利用路由的完全匹配定义功能,把路由定义改成:
'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/new/2012/03/01
的话,是不会匹配成功任何一条路由的。
3、利用正则路由
当然,解决问题的办法总是不止一种,对于复杂的情况,我们不要忘了使用正则路由规则定义,在你找不到解决方案的时候,正则路由总能帮到你。
要实现上面的同样路由功能的话,还可以用下面的规则定义:
'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',
),