## 概述
由于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地址存在路由定義,會自動轉換為路由定義。