## 概述
由于ThinkPHP5.0默认采用的URL规则是:
>[info] #### http://server/module/controller/action/param/value/...
路由解析的最终结果通常是把URL地址解析到模块的某个控制器下的操作方法,在特殊的情况下,也可以跳转到外部地址或者执行闭包函数。
新版的路由功能做了大量的增强,包括:
* 支持路由到模块(模块/控制器/操作)、控制器(控制器类/操作)、类(任何类库);
* 闭包路由的增强;
* 规则路由支持全局和局部变量规则定义(正则);
* 支持路由到任意层次的控制器;
* 子域名路由功能改进;
* 路由分组并支持分组参数定义;
* 通过函数自定义路由检测规则;
ThinkPHP5.0的路由比较灵活,系统支持三种方式的URL解析规则:
#### 一、普通模式
关闭路由,完全使用默认的pathinfo方式URL:
~~~
'url_route_on' => false,
~~~
路由关闭后,不会解析任何路由规则,采用默认的PATH_INFO 模式访问URL:
~~~
module/controller/action/param/value/...
~~~
> 但仍然可以通过Action参数绑定、空控制器和空操作等特性实现URL地址的简化。
#### 二、混合模式
开启路由,并使用路由+默认PATH_INFO方式的混合:
~~~
'url_route_on' => true,
~~~
该方式下面,只需要对需要定义路由规则的访问地址定义路由规则,其它的仍然按照默认的PATH_INFO模式访问URL。
#### 三、强制模式
开启路由,并设置必须定义路由才能访问:
~~~
'url_route_on' => true,
'url_route_must'=> true,
~~~
这种方式下面必须严格给每一个访问地址定义路由规则,否则将抛出异常。
首页的路由规则是 `/`。
## 注册路由规则
路由功能由`think\Route`类实现,包括路由注册和检测。
路由注册可以采用方法动态单个和批量注册,也可以直接定义路由定义文件的方式进行集中注册。
### 动态注册
使用Route类的register方法注册路由规则(通常可以在应用的公共文件中注册,或者定义配置文件后在公共文件中批量导入的方式注册),例如注册如下路由规则后:
~~~
\think\Route::register('new/:id','index/New/read');
~~~
我们访问:
~~~
http://serverName/new/5
~~~
> ThinkPHP5.0的路由规则定义是从根目录开始,而不是基于模块名的。
其实是访问的:
~~~
http://serverName/index/new/read/id/5
~~~
可以在register方法中指定请求类型,不指定的话默认为任何请求类型,例如:
~~~
\think\Route::register('new/:id','New/update','POST');
~~~
表示定义的路由规则在POST请求下才有效。
系统提供了为不同的请求类型定义路由规则的简化方法,例如:
~~~
\think\Route::get('new/:id','New/read'); // 定义GET请求路由规则
\think\Route::post('new/:id','New/update'); // 定义POST请求路由规则
\think\Route::put('new/:id','New/update'); // 定义PUT请求路由规则
\think\Route::delete('new/:id','New/delete'); // 定义DELETE请求路由规则
\think\Route::any('new/:id','New/read'); // 所有请求都支持的路由规则
~~~
如果要定义get和post请求支持的路由规则,也可以用:
~~~
\think\Route::register('new/:id','New/read','GET|POST');
~~~
我们也可以批量注册路由规则,例如:
~~~
\think\Route::register(['new/:id'=>'New/read','blog/:name'=>'Blog/detail']);
\think\Route::get(['new/:id'=>'New/read','blog/:name'=>'Blog/detail']);
\think\Route::post(['new/:id'=>'New/update','blog/:name'=>'Blog/detail']);
~~~
注册多个路由规则后,系统会依次遍历注册过的满足请求类型的路由规则,一旦匹配到正确的路由规则后则开始调用控制器的操作方法,后续规则就不再检测。
### 定义路由配置文件
如果不希望这么麻烦注册路由规则,可以直接在应用目录下面的`route.php` 直接定义路由规则,内容示例如下:
~~~
return [
'__pattern__' => [
'name' => '\w+',
],
'new/:id' => 'New/read',
'[blog]' => [
':id' => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
':name' => ['Blog/read', ['method' => 'post']],
],
];
~~~
`__pattern__`表示定义路由变量的全局规则。更详细的使用我们会在后面描述。
## 路由规则定义
路由定义由三个部分组成:路由表达式、路由地址和参数、路由响应的请求类型。
以register方法为例的话就是:
#### Think\Route::register('路由表达式','路由地址和参数','请求类型(默认为GET)','匹配参数(数组)','变量规则');
批量注册的时候规则是:
~~~
\think\Route::register(['路由表达式'=>'路由地址和参数',...],'','请求类型(默认为GET)','匹配参数(数组)','变量规则');
~~~
或者
~~~
\think\Route::get(['路由表达式'=>'路由地址和参数',...],'','匹配参数(数组)','变量规则'); // GET响应路由
\think\Route::post(['路由表达式'=>'路由地址和参数',...],'','匹配参数(数组)','变量规则'); // POST响应路由
\think\Route::put(['路由表达式'=>'路由地址和参数',...],'','匹配参数(数组)','变量规则'); // PUT响应路由
~~~
为了便于描述路由表达式和路由地址的对应关系,下面关于路由规则的描述定义均以批量定义的形式来表达。
## 路由表达式
路由表达式统一使用规则路由表达式定义,只能使用字符串。
>[danger] 正则路由定义功能已经废除,改由变量规则定义完成。
### 规则表达式
规则表达式通常包含静态地址和动态地址,或者两种地址的结合,例如下面都属于有效的规则表达式:
~~~
'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']`。
### 完全匹配
规则匹配检测的时候只是对URL从头开始匹配,只要URL地址包含了定义的路由规则就会匹配成功,如果希望完全匹配,可以使用$符号,例如:
~~~
'new/:cate$'=> 'News/category',
~~~
~~~
http://serverName/index.php/new/info
~~~
会匹配成功,而
~~~
http://serverName/index.php/new/info/2
~~~
则不会匹配成功
如果是采用
~~~
'new/:cate'=> 'News/category',
~~~
方式定义的话,则两种方式的URL访问都可以匹配成功。
## 路由地址及参数
路由地址和额外参数表示前面的路由表达式最终需要路由到的地址以及一些需要的参数,支持下面4种方式定义:
| 定义方式 | 定义格式 |
|---|---|
| 方式1:路由到模块/控制器 | '[模块/控制器/操作]?额外参数1=值1&额外参数2=值2...' |
| 方式2:路由到重定向地址 | '外部地址'(默认301重定向) 或者 ['外部地址','重定向代码'] |
| 方式3:路由到控制器的方法 | '@控制器/操作' |
| 方式4:路由到类的方法 | '\\完整的命名空间类'(静态方法) 或者 ['\\完整的命名空间类','方法名']|
### 路由到模块/控制器/操作
这是最常用的一种路由地址定义,路由地址中的 **[模块/控制器/操作]** 的解析规则是先从后面的操作开始解析,然后依次往前解析控制器和模块,如果没有则用默认控制器及默认模块。
> 如果关闭路由功能,那么解析规则其实就是采用该方式。
路由地址中支持多级控制器,使用下面的方式进行设置:
~~~
'blog/:id'=>'index/group.blog/read'
~~~
表示路由到下面的控制器类,
~~~
index/controller/group/Blog
~~~
支持可以动态改变路由地址,例如:
~~~
'blog/:action'=>'index/blog/:action'
~~~
### 路由到重定向地址
重定向的外部地址必须以“/”或者http开头的地址。
如果路由地址以“/”或者“http”开头则会认为是一个重定向地址或者外部地址,例如:
~~~
'blog/:id'=>'/blog/read/id/:id'
~~~
和
~~~
'blog/:id'=>'blog/read'
~~~
虽然都是路由到同一个地址,但是前者采用的是301重定向的方式路由跳转,这种方式的好处是URL可以比较随意(包括可以在URL里面传入更多的非标准格式的参数),而后者只是支持模块和操作地址。举个例子,如果我们希望avatar/123重定向到
/member/avatar/id/123_small的话,只能使用:
~~~
'avatar/:id'=>'/member/avatar/id/:id_small'
~~~
路由地址采用重定向地址的话,如果要引用动态变量,直接使用动态变量即可。
采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用,例如:
~~~
'blog/:id'=>'http://blog.thinkphp.cn/read/:id'
~~~
表示当前网站(可能是http://thinkphp.cn )的 blog/123地址会直接重定向到 http://blog.thinkphp.cn/read/123。
### 路由到控制器的方法
这种方式看起来似乎和第一种是一样的,本质的区别是直接执行某个控制器类的方法,而不需要去解析 模块/控制器/操作 这些。
例如,定义如下路由后:
~~~
'blog/:id'=>'@index/blog/read',
~~~
系统会直接执行
~~~
\think\Loader::action('index/blog/read');
~~~
相当于直接调用 \app\index\controller\blog类的read方法。
### 路由到类的方法
这种方式更进一步,可以支持执行任何类的方法,而不仅仅是执行控制器的操作方法,例如:
~~~
'blog/:id'=>['\app\index\service\Blog','read'],
~~~
执行的是 \app\index\service\Blog类的read方法。
也支持执行某个静态方法,例如:
~~~
'blog/:id'=>'\app\index\service\Blog::read',
~~~
### 额外参数
在路由跳转的时候支持额外传入参数对(额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用,后面我们会提到)。例如:
~~~
'blog/:id'=>'blog/read?status=1&app_id=5',
~~~
上面的路由规则定义中额外参数的传值方式都是等效的。status和app_id参数都是URL里面不存在的,属于隐式传值,当然并不一定需要用到,只是在需要的时候可以使用。
### 匹配参数
匹配参数用于设置当前的路由规则详细的匹配条件,匹配参数是优先于路由表达式检测的,目前支持设置的匹配参数包括:
| 参数名 | 参数说明 |
|---|---|
|method | 请求类型 |
| ext | 伪静态后缀 |
| domain | 域名检测 |
| https | 是否https访问 |
| callback | 自定义检测,可以支持所有is_callable的定义,包括闭包 |
| before_behavior | 前置行为(检测) |
|after_behavior|后置行为(执行)|
例如我们可以设置当前的路由规则只有当伪静态后缀为html的时候才会匹配:
~~~
\think\Route::register('new/:id','New/read','GET',['ext'=>'html']);
~~~
或者
~~~
\think\Route::get('new/:id','New/read',['ext'=>'html']);
~~~
设置后,URL访问地址:
~~~
http://serverName/new/6
~~~
将无法正确匹配,所以会报错。只有访问:
~~~
http://serverName/new/6.html
~~~
的时候才能正常访问。
可以支持设置多个后缀,例如:
~~~
\think\Route::get('new/:id','New/read',['ext'=>'html|shtml']);
~~~
如果设置如下:
~~~
\think\Route::get('new/:id','New/read',['https'=>true]);
~~~
那么访问
~~~
https://serverName/new/6
~~~
才是有效匹配的。
如果定义了
~~~
\think\Route::get('new/:id','New/read',['callback'=>'checkRoute']);
~~~
则调用自定义的checkRoute方法进行检测是否匹配当前路由。
### 变量规则
ThinkPHP5.0支持在规则路由中为变量用正则的方式指定变量规则,弥补了之前变量规则太局限的问题,并且支持全局规则设置。使用方式如下:
设置全局变量规则,全局路由有效:
~~~
// 设置name变量规则(采用正则定义)
\think\Route::pattern('name','\w+');
// 支持批量添加
\think\Route::pattern(['name'=>'\w+','id'=>'\d+']);
~~~
局部变量规则,仅在当前路由有效:
~~~
// 定义GET请求路由规则 并设置name变量规则
\Think\Route::get('new/:id','New/read',[],['name'=>'\w+']);
~~~
如果一个变量同时定义了全局规则和局部规则,局部规则会覆盖全局变量的定义。
### 完整URL规则
如果要对整个URL进行变量规则检查,可以进行`__url__` 变量规则(效果相当于之前版本的使用正则路由规则检测),例如:
~~~
// 定义GET请求路由规则 并设置完整URL变量规则
\Think\Route::get('new/:id','New/read',[],['__url__'=>'new\/\w+']);
~~~
## URL映射
URL映射其实属于URL路由的静态简化版,由于不进行路由规则的遍历操作而是直接定位,因此效率较高,但也因为是类似于静态路由的概念,从而作用有限。
注册URL映射的方法如下:
~~~
\think\Route::map('new/top','news/index?type=top');
~~~
定义之后,如果我们访问:
~~~
http://serverName/new/top
~~~
其实是访问:
~~~
http://serverName/index/news/index/type/top
~~~
和路由匹配不同的是,URL映射匹配是完整匹配,所以如果访问:
~~~
http://serverName/new/top/var/test
~~~
尽管前面也有new/top,但并不会被匹配到index/news/index/type/top。
URL映射定义也支持批量方式:
~~~
\think\Route::map(['new/top'=>'news/index?type=top','blog/new'=>'Blog/index?type=new']);
~~~
因为URL映射中的映射规则是静态定义,所以不能含有动态变量,而且在匹配的时候必须是完全匹配,所以下面的定义是不支持的或者无法生效的:
~~~
\think\Route::map('new/:id','news/read');
~~~
URL映射无法支持匹配请求类型。
> 如果你绑定了模块那么,URL映射会自动加上模块名。
## 闭包定义支持
我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作方法了,例如:
~~~
\think\Route::get('test',function(){ echo 'just test';});
\think\Route::get('hello/:name',function($name){ echo 'Hello,'.$name;});
~~~
### 参数传递
闭包定义的时候支持参数传递,例如:
~~~
\think\Route::get('hello/:name',function($name){ echo 'Hello,'.$name;});
~~~
规则路由中定义的动态变量的名称 就是闭包函数中的参数名称,不分次序。
因此,如果我们访问的URL地址是:
~~~
http://serverName/hello/thinkphp
~~~
则浏览器输出的结果是:
~~~
Hello,thinkphp
~~~
## 资源路由
5.0支持设置RESTFul请求的资源路由,方式如下:
~~~
\think\Route::resource('blog','index/blog');
~~~
或者在路由配置文件中使用`__rest__`添加资源路由定义:
~~~
return [
// 定义资源路由
'__rest__'=>[
'blog'=>'index/blog',
],
// 定义普通路由
'hello/:id'=>'index/hello',
]
~~~
设置后会自动注册7个路由规则如下
| 请求类型 | 生成路由规则 | 对应操作方法 |
| --- | --- | --- |
| GET | `blog` | index |
| GET | `blog/create` | create |
| POST | `blog` | save |
| GET | `blog/:id` | read |
| GET | `blog/:id/edit` | edit |
| PUT | `blog/:id` | update |
| DELETE | `blog/:id` | delete |
具体指向的控制器由路由地址决定,例如上面的设置,会对应index模块的blog控制器,你只需要为Blog控制器创建以上对应的操作方法就可以支持下面的URL访问:
~~~
http://serverName/blog/
http://serverName/blog/128
http://serverName/blog/28/edit
~~~
方法如下:
~~~
namespace app\index\controller;
class Blog {
public function index(){
}
public function read($id){
}
public function edit($id){
}
}
~~~
可以改变默认的id参数名,例如:
~~~
\think\Route::resource('blog','index/blog',['var'=>['blog'=>'blog_id']]);
~~~
控制器的方法定义调整如下:
~~~
namespace app\index\controller;
class Blog {
public function index(){
}
public function read($blog_id){
}
public function edit($blog_id){
}
}
~~~
也可以在定义资源路由的时候限定执行的方法,例如:
~~~
// 只允许index read edit update 四个操作
\think\Route::resource('blog','index/blog',['only'=>['index','read','edit','update']]);
// 排除index和delete操作
\think\Route::resource('blog','index/blog',['except'=>['index','delete']]);
~~~
如果需要更改某个资源路由的对应操作,可以使用下面方法:
~~~
\think\Route::rest('create',['GET', '/add','add']);
~~~
设置之后,URL访问变为:
~~~
http://serverName/blog/create
变成
http://serverName/blog/add
~~~
创建blog页面的对应的操作方法也变成了add。
支持批量更改,如下:
~~~
\think\Route::rest([
'save' => ['POST', '', 'store'],
'update' => ['PUT', '/:id', 'save'],
'delete' => ['DELETE', '/:id', 'destory'],
]);
~~~
> 注意,rest数据定义的索引 `save/update/delete` 是不能更改的。
### 资源嵌套
支持资源路由的嵌套,例如:
~~~
\think\Route::resource('blog.comment','index/comment');
~~~
就可以访问如下地址:
~~~
http://serverName/blog/128/comment/32
http://serverName/blog/128/comment/32/edit
~~~
生成的路由规则分别是:
~~~
blog/:blog_id/comment/:id
blog/:blog_id/comment/:id/edit
~~~
Comment控制器对应的操作方法如下:
~~~
namespace app\index\controller;
class Comment{
public function edit($id,$blog_id){
}
}
~~~
如果需要改变其中的变量名,可以使用:
~~~
\think\Route::resource('blog.comment','index/comment',['var'=>['blog'=>'blogId']]);
~~~
Comment控制器对应的操作方法改变为:
~~~
namespace app\index\controller;
class Comment{
public function edit($id,$blogId){
}
}
~~~
## 路由分组
路由分组功能允许把相同前缀的路由定义合并分组,这样可以提高路由匹配的效率,不必每次都去遍历完整的路由规则。
例如,我们有定义如下两个路由规则的话
~~~
'blog/:id' => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
'blog/:name' => ['Blog/read', ['method' => 'post']],
~~~
可以合并到一个blog分组
~~~
'[blog]' => [
':id' => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
':name' => ['Blog/read', ['method' => 'post']],
],
~~~
可以使用Route类的group方法进行注册,如下:
~~~
\think\Route::group('blog',[
':id' => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
':name' => ['Blog/read', ['method' => 'post']],
]);
~~~
可以给分组路由定义一些公用的路由设置参数,例如:
~~~
\think\Route::group('blog',[
':id' => ['Blog/read', [], ['id' => '\d+']],
':name' => ['Blog/read', [],
],['method'=>'get','ext'=>'html']);
~~~
## URL生成
可以统一使用 \think\Url类生成URL地址,例如:
~~~
\think\Url::build('hello');
\think\Url::build('index/hello');
// 或者使用帮助函数
U('hello');
U('index/hello');
~~~
> 如果传入的url地址存在路由定义,会自动转换为路由定义。