# 路由初體驗
一個典型的`ThinkPHP`應用的URL請求的執行過程通常是:
>[info]### 用戶請求 -> 路由解析 -> 調度請求 -> 執行操作 -> 響應輸出
路由在框架中的作用打個比方的話,路由好比是WEB應用的總調度室,對于訪問的URL地址,路由可以拒絕或者接受某個URL請求,并進行分發調度,而且還有一個副作用是因為路由規則可以隨意定義,因此可以讓你的URL請求地址更優雅,因為不會暴露實際的URL地址,也就意味著更安全,5.0的路由不僅僅只是支持路由到控制器的操作方法,甚至可以路由到任何的類或者閉包。
和`Laravel`等不同,框架本身不強制定義路由,默認情況下的如果沒有定義任何路由規則或者沒有匹配到任何的路由規則,則按照模塊/控制器/操作的URL規范來解析(也就是系統默認的解析規則,這點看過`ThinkPHP5.0`快速入門的用戶已經很清楚了,在此就不再多說)。
> 在本文后面的示例中,為了方便講解,我們統一設置`vhost`訪問,以`apache`為例的話定義如下(其它環境請自行百度):
> ~~~
> <VirtualHost *:80>
> DocumentRoot "/home/www/tp5/public"
> ServerName tp5.com
> </VirtualHost>
> ~~~
> 把DocumentRoot修改為你本機tp5的`public`目錄,并注意修改本機的`hosts`文件把`tp5.com`指向本地`127.0.0.1`。
在沒有定義任何的路由規則之前,你的URL訪問地址可能會是下面這樣:
~~~
http://tp5.com/module/controller/action/name/value
~~~
> 如果你還不了解如何隱藏URL地址中的`index.php`可以參考官方開發手冊的[URL重寫](http://www.hmoore.net/manual/thinkphp5/177576)。
如果需要定義應用的路由規則,直接在`application/route.php`文件中添加路由規則即可(雖然你也可以在應用的公共文件中定義路由,但并不建議,后面會理解為什么)。
> 如果你自定義了`CONF_PATH`常量的話,路由配置文件的位置可能有所區別,請自行調整。
>[danger]### 【5.1須知】
> * * * * *
> `5.1`版本的路由目錄位于根目錄下的`route`目錄,在該目錄下的任何一個路由定義文件都是有效的。通常可以直接在`route/route.php`中定義路由。
本節用一個`Hello,world!`的例子來體驗下路由的使用,定義一個控制器類命名為`application/index/controller/Index.php`:
~~~
<?php
namespace app\index\controller;
class Index
{
public function hello()
{
return 'Hello,World!';
}
}
~~~
要訪問`Index`控制器的`hello`操作方法的話,沒有定義路由規則之前,訪問的URL地址應該是:
~~~
http://tp5.com/index/index/hello
~~~
訪問后瀏覽器輸出結果為:
~~~
Hello,World!
~~~
下面我們來定義一個路由先:
~~~
use think\Route;
Route::rule('hello','index/Index/hello');
~~~
路由定義文件的開頭需要加上下面一句表示引入Route類(后面不再強調)
~~~
use think\Route;
~~~
>[danger]### 【5.1須知】
> * * * * *
> 路由定義文件開頭不需要添加 `use think\Route;`
>
定義后,我們就可以直接訪問下面的地址:
~~~
http://tp5.com/hello
~~~
訪問后瀏覽器輸出結果和之前是一樣的。
> 如果你夠細心的話,會發現原來的URL地址`http://tp5.com/index/index/hello`已經禁止訪問,這是由于5.0遵循唯一的URL地址設計。
很多時候操作方法都會有變量傳入需要,例如給`hello`方法添加一個`name`變量,方法代碼修改如下:
~~~
<?php
namespace app\index\controller;
class Index
{
public function hello($name)
{
return 'Hello,' . $name . '!';
}
}
~~~
原來的URL地址變成是(記得先刪除前面定義的路由規則,否則會禁止訪問下面的URL地址):
~~~
http://tp5.com/index/index/hello/name/thinkphp
~~~
訪問后瀏覽器輸出結果為:
~~~
Hello,thinkphp!
~~~
現在給該URL地址定義一個新的路由規則如下:
~~~
Route::rule('hello/:name','index/Index/hello');
~~~
現在我們來分析下`rule`方法的參數,第一個參數稱為路由規則(通過路由訪問的地址),第二個參數為該規則對應的路由地址(也就是原來定義路由之前訪問的URL地址)。
路由規則通常可以包含變量(例如其中的`:name`就是一個路由變量),路由規則中包含變量(包括可選變量)的就稱該條路由規則為動態路由,沒有包含任何變量的路由我們稱之為靜態路由。
前面我們定義過兩條路由規則就分別是靜態路由和動態路由:
~~~
// 靜態路由規則
Route::rule('hello','index/Index/hello');
// 動態路由規則
Route::rule('hello/:name','index/Index/hello');
~~~
> 靜態路由和動態路由的解析過程是完全不同的,簡單點說的話,動態路由需要遍歷所有規則依次匹配(除非第一條就匹配成功),是一個多次匹配的過程,而靜態路由是一次快速匹配(當然特殊情況下也會有靜態路由無效的情況)。
定義好路由規則后,URL地址就可以簡化為:
~~~
http://tp5.com/hello/thinkphp
~~~
路由規則中的靜態規則部分不區分大小寫,所以
~~~
Route::rule('Hello/:name','index/index/hello');
~~~
和前面定義的路由是相同的,雖然是兩條不同的路由規則。
我們知道一個URL請求類型有很多,常用的包括`GET`/`POST`/`PUT`/`DELETE`等,我們使用`rule`方法注冊的路由,默認是支持任意請求類型訪問的,不過你可以通過第三個參數來限定請求類型:
~~~
Route::rule('hello/:name','index/index/hello','GET');
~~~
則表示,只有通過`GET`請求的訪問,該路由才會生效。
為了方便定義,系統給每個請求類型的路由規則增加了獨立的方法,所以你可以使用下面的方法注冊一條相同的路由:
~~~
Route::get('hello/:name','index/index/hello');
~~~
如果你希望路由可以支持所有的請求類型,也可以使用:
~~~
Route::any('hello/:name','index/index/hello');
~~~
`any`方法其實和`rule`方法是一樣的,區別在于不用寫第三個參數。
ThinkPHP5支持的請求類型包括
~~~
['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
~~~
不過只支持了 `get`/`post`/`put`/`delete`/`patch` 常用請求類型的路由注冊方法,其它兩個類型由于不常用,需要自己使用`rule`方法注冊。
一個路由規則可以設置匹配的條件參數(其實請求類型也是一個匹配條件,單獨出來一個參數是從性能考慮),匹配條件是一個數組,可以包含多個匹配條件,例如:
~~~
Route::any('hello/:name', 'index/index/hello', [
'ext' => 'html',
'method' => 'get',
]);
~~~
上面的路由規則表示,必須是`GET`請求訪問,并且URL后綴為`html`的地址。
>[danger]### 【5.1須知】
> * * * * *
> 上面的路由可以改成更清晰的方式
> ~~~
> Route::any('hello/:name', 'index/index/hello')
> ->ext('html')
> ->method('get');
> ~~~
因此,訪問下面的URL地址就會拋出異常
~~~
http://tp5.com/hello/thinkphp
~~~
正確的URL地址應該是:
~~~
http://tp5.com/hello/thinkphp.html
~~~
> 如果沒有定義路由的后綴參數,表示路由規則本身不限制URL后綴訪問(但仍然受應用配置參數`url_html_suffix`影響)。
更多的路由參數后續還會提到,除了路由參數外,我們還可以對路由變量設置規則,這個時候就需要用到第四個參數。
~~~
Route::any('hello/:name', 'index/index/hello', [
'ext' => 'html',
'method' => 'get',
], ['name' => '[A-Za-z0-9]+']);
~~~
>[danger]### 【5.1須知】
> * * * * *
> 可以更直觀的改為
> ~~~
> Route::any('hello/:name', 'index/index/hello')
> ->ext('html')
> ->method('get')
> ->pattern(['name' => '[A-Za-z0-9]+']);
> ~~~
>
這里定義了路由變量`name`的變量規則(正則表達式),在實際正則表達式檢測的時候,最終的正式表達式其實是 `/^[A-Za-z0-9]+$/`,系統會自動加上起始和結束符號。當URL里面的變量不符合正則表達式匹配要求的時候,路由規則就不會生效,因此下面的URL地址訪問就會出錯:
~~~
http://tp5.com/hello/think-php
~~~
對于`any`方法定義的路由規則,系統支持直接在路由配置文件中直接返回數組的方式定義,例如:
~~~
return [
'hello/:name' => ['index/index/hello', ['ext' => 'html', 'method' => 'get'], ['name' => '[A-Za-z0-9]+']],
];
~~~
如果沒有路由參數和變量規則定義的話,可以直接使用:
~~~
return [
'hello/:name' => 'index/index/hello',
];
~~~
>[danger]### 【5.1須知】
> * * * * *
> 5.1不推薦使用返回數組定義路由的方式,請盡量使用方法注冊路由
> 通常,我們把這種方式的路由注冊稱之為靜態注冊,反之,使用Route類的方法注冊的我們稱之為動態注冊。
雖然靜態注冊也能滿足需求,但因為靜態注冊路由存在一定的局限性(不夠靈活,而且會影響路由檢測的性能),本指南大多數情況下都使用動態注冊路由的方式講解,也是5.0推薦的方式。
當然,路由方法注冊和配置定義可以同時使用:
~~~
<?php
use think\Route;
Route::get('blog/:name', 'index/blog/read');
return [
'hello/:name' => 'index/index/hello',
];
~~~
路由配置文件定義的路由規則,會由`Route`類的`import`方法完成注冊過程(換句話說,你也可以調用`import`方法批量注冊路由規則)。
現在你已經了解如何定義一個路由規則了,你已經開啟了路由這扇大門,但是路由之旅才剛剛開始^_^
## 小結
回想下,你現在是否已經明白了靜態路由和動態路由的區別,以及什么是靜態注冊和動態注冊路由。
再復雜的路由也是由最基本的路由規則、路由地址、路由參數和變量規則幾個部分組成,所謂萬變不離其宗,復雜的路由只是用了一些更多的技巧來完成注冊或者更方便的使用,我們來復習下路由的幾個概念:
>[info]### Route::rule('路由規則','路由地址','請求類型','路由參數(數組)','變量規則(數組)');
**路由規則**:URL訪問規則(包括靜態規則和動態規則),只有符合規則的路由才能正確訪問;
**路由地址**:實際訪問的地址(可以是控制器操作、類的方法或者閉包);
**請求類型**:表示當前路由生效使用的請求類型,包括`GET`/`POST`/`PUT`/`DELETE`等,如果希望任何請求都能訪問使用`*`號(默認值)。
**路由參數**:路由匹配的條件約束或設置參數(用于檢測或者解析);
**路由變量**:路由規則里面的動態變量以及`PATH_INFO`里面的參數都稱之為路由變量;
**變量規則**:路由規則中的變量的匹配規則(正則表達式);
下一節我會給大家講解路由的執行流程。