# 快速入門(六):路由
ThinkPHP框架對URL有一定的規范,所以如果你希望定制你的URL格式的話,就需要好好了解下內置的路由功能了,它能讓你的URL變得更簡潔和有內涵。
## 路由定義
路由定義一般包括三個配置參數:
|參數|描述|
|------|-----------|
|URL_ROUTER_ON|開啟路由,設置為true后路由規則定義生效|
|URL_ROUTE_RULES|路由規則定義|
|URL_MAP_RULES|靜態路由(URL映射)定義|
要使用路由功能,前提是你的URL支持PATH_INFO(或者兼容URL模式也可以,采用普通URL模式的情況下不支持路由功能),并且在應用(或者模塊)配置文件中開啟路由:
~~~
'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',
),
~~~
系統會按定義的順序依次匹配路由規則,一旦匹配到的話,就會定位到路由定義中的模塊、控制器和操作方法去執行(可以傳入其他的參數),并且后面的規則不會繼續匹配。
路由規則一般是配置在模塊的配置文件中(`模塊名/Conf/config.php`),這樣的話路由規則僅針對當前模塊,并且URL中匹配的路由不能省略當前模塊名(除了默認模塊外)。如果需要定義全局的路由規則,就需要把路由配置定義在公共模塊的配置文件中(`Common/Conf/config.php`),但全局路由定義的路由規則中必須包含明確的模塊。
路由規則的定義方式如下:
~~~
'路由表達式'=>'路由地址和額外參數'
~~~
## 路由表達式
路由表達式包括規則路由和正則路由的定義表達式,只能使用字符串。
|表達式 |示例|
|-------|-------|
|正則表達式 |/^blog\/(\d+)$/|
|規則表達式 |blog/:id|
**正則表達式**
路由表達式支持的正則定義必須以“/”開頭,否則就視為規則表達式。也就是說如果采用
~~~
'#^blog\/(\d+)$#'
~~~
方式定義的正則表達式不會被支持,而會被認為是規則表達式進行解析,從而無法正確匹配。
~~~
'/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2',
~~~
對于正則表達式中的每個變量(即正則規則中的子模式)部分,如果需要在后面的路由地址中引用,可以采用:1、:2這樣的方式,序號就是子模式的序號。
更多的關于如何定義正則表達式就不在本文的描述范疇了。
**規則表達式**
規則路由比正則路由更方便定義和容易理解,規則表達式通常包含靜態地址和動態地址,或者兩種地址的結合,例如下面都屬于有效的規則表達式:
~~~
'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里面沒有的一些參數,這里允許使用字符串或者數組方式定義,支持下面6種方式定義:
|定義方式 |定義格式|
|-----------|------------|
|方式1:路由到內部地址(字符串) |'[分組/模塊/操作]?參數1=值1&參數2=值2...'|
|方式2:路由到內部地址(數組)參數采用字符串方式 |array('[分組/模塊/操作]','參數1=值1&參數2=值2...')|
|方式3:路由到內部地址(數組)參數采用數組方式| array('[分組/模塊/操作]',array('參數1'=>'值1','參數2'=>'值2'...))|
|方式4:路由到外部地址(字符串)301重定向| '外部地址'|
|方式5:路由到外部地址(數組)可以指定重定向代碼| array('外部地址','重定向代碼')|
|方式6:閉包函數 |function($name){ echo 'Hello,'.$name;}|
如果路由地址以“/”或者“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里面不存在的,屬于隱式傳值,當然并不一定需要用到,只是在需要的時候可以使用。
## 閉包支持
我們可以使用閉包的方式定義一些特殊需求的路由,而不需要執行控制器的操作方法了,例如:
~~~
'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`
## 靜態路由
靜態路由其實屬于規則路由的靜態簡化版(又稱為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
## 總結
通過本篇的學習,我們大概掌握了如何定義路由,后面我們將會學習如何定義視圖和模板賦值。