利用路由功能,可以讓你的URL地址更加簡潔和優雅。ThinkPHP支持對模塊的URL地址進行路由操作(路由功能是針對PATHINFO模式或者兼容URL而設計的,暫時不支持普通URL模式)。
ThinkPHP的路由功能包括:
- 正則路由
- 規則路由
- 靜態路由(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',
),
~~~
系統會按定義的順序依次匹配路由規則,一旦匹配到的話,就會定位到路由定義中的控制器和操作方法去執行(可以傳入其他的參數),并且后面的規則不會繼續匹配。
## 路由定義
路由規則的定義格式為: **'路由表達式'=>'路由地址和傳入參數'**
或者:**array('路由表達式','路由地址','傳入參數')**
> 模塊路由和全局路由配置的區別在于,全局路由的路由地址必須包含模塊。
##### 路由表達式
路由表達式包括規則路由和正則路由的定義表達式,只能使用字符串。
| 表達式 | 示例 |
|-----|-----|
| 正則表達式 | /^blog\/(\d+)$/ |
| 規則表達式 | blog/:id |
> 詳細的規則路由和正則路由表達式的定義方法參考后面的章節。
##### 路由地址
路由地址(可以支持傳入額外參數)表示前面的路由表達式需要路由到的地址(包括內部地址和外部地址),并且允許隱式傳入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;} |
如果你定義的是全局路由(在公共模塊的配置文件中定義),那么路由地址的定義格式中需要增加模塊名,例如:
~~~
'blog/:id'=>'Home/blog/read' // 表示路由到Home模塊的blog控制器的read操作方法
~~~
如果路由地址以“/”或者“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`。
默認情況下,外部地址的重定向采用301重定向,如果希望采用其它的,可以使用:
~~~
'blog/:id'=>array('http://blog.thinkphp.cn/read/:1',302);
~~~
在路由跳轉的時候支持額外傳入參數對(額外參數指的是不在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后綴
例如:
~~~
'blog/:id'=>array('blog/read','status=1&app_id=5',array('ext'=>'html')),
~~~
就可以限制html后綴訪問該路由規則才能生效。
##### 限制請求類型
例如:
~~~
'blog/:id'=>array('blog/read','status=1&app_id=5',array('method'=>'get')),
~~~
就限制了只有GET請求該路由規則才能生效。
##### 自定義檢測
支持自定義檢測,例如: 例如:
~~~
'blog/:id'=>array('blog/read','status=1&app_id=5',array('callback'=>'checkFun')),
~~~
就可以自定義checkFun函數來檢測是否生效,如果函數返回false則表示不生效。
# 規則路由
規則路由是一種比較容易理解的路由定義方式,采用ThinkPHP設計的規則表達式來定義。
## 規則表達式
規則表達式通常包含靜態地址和動態地址,或者兩種地址的結合,例如下面都屬于有效的規則表達式:
~~~
'my' => 'Member/myinfo', // 靜態地址路由
'blog/:id' => 'Blog/read', // 靜態地址和動態地址結合
'new/:year/:month/:day'=>'News/read', // 靜態地址和動態地址結合
':user/:blog_id' =>'Blog/read',// 全動態地址
~~~
> 規則表達式的定義始終以“/”為參數分割符,不受`URL_PATHINFO_DEPR`設置的影響
每個參數中以“:”開頭的參數都表示動態參數,并且會自動對應一個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的值傳入了,這種情況屬于路由規則的沖突,解決辦法有兩個:
**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/Home/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/Home/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',
),
~~~
- 序
- 前言
- 內容簡介
- 目錄
- 基礎知識
- 起步
- 控制器
- 模型
- 模板
- 命名空間
- 進階知識
- 路由
- 配置
- 緩存
- 權限
- 擴展
- 國際化
- 安全
- 單元測試
- 拿來主義
- 調試方法
- 調試的步驟
- 調試工具
- 顯示trace信息
- 開啟調試和關閉調試的區別
- netbeans+xdebug
- Socketlog
- PHP常見錯誤
- 小黃鴨調試法,每個程序員都要知道的
- 應用場景
- 第三方登錄
- 圖片處理
- 博客
- SAE
- REST實踐
- Cli
- ajax分頁
- barcode條形碼
- excel
- 發郵件
- 漢字轉全拼和首字母,支持帶聲調
- 中文分詞
- 瀏覽器useragent解析
- freelog項目實戰
- 需求分析
- 數據庫設計
- 編碼實踐
- 前端實現
- rest接口
- 文章發布
- 文件上傳
- 視頻播放
- 音樂播放
- 圖片幻燈片展示
- 注冊和登錄
- 個人資料更新
- 第三方登錄的使用
- 后臺
- 微信的開發
- 首頁及個人主頁
- 列表
- 歸檔
- 搜索
- 分頁
- 總結經驗
- 自我提升
- 進行小項目的鍛煉
- 對現有輪子的重構和移植
- 寫技術博客
- 制作視頻教程
- 學習PHP的知識和新特性
- 和同行直接溝通、交流
- 學好英語,走向國際
- 如何參與
- 瀏覽官網和極思維還有看云
- 回答ThinkPHP新手的問題
- 嘗試發現ThinkPHP的bug,告訴官方人員或者push request
- 開發能提高效率的ThinkPHP工具
- 嘗試翻譯官方文檔
- 幫新手入門
- 創造基于ThinkPHP的產品,進行連帶推廣
- 展望未來
- OneThink
- ThinkPHP4
- 附錄