# 路由
在理解SF的路由之前,我們先了解一下什么是pretty URI。
現在的Web頁面,基本都會用形如“`http://www.rsywx.net/books/01805.html`”的URI來表示一個資源。這個名為`01805.html`的頁面并不物理存在于服務器上。在以前的編寫實踐中,它很可能是要這樣來表達的:“`http://www.rsywx.net/listbook.php?bookid=01805`”。
現代PHP框架(其實更恰當地說是所有現代框架)都拋棄了第二種很丑陋也極不靈活極不安全、也極不SEO友好的做法。而采用類似第一種這樣的pretty URI方式。
和幾乎所有現代Web框架一樣,SF也是單入口的。所謂單入口,是說整個Web應用都以一個文件作為入口,作為調用其它控制器的總調度。在SF中,這個文件是`app.php`(生產環境)或者`app_dev.php`(調試環境)。
那么問題來了,如果我們只有一個PHP文件,我們怎么來定義一個URI的路徑呢?比如說:`http://www.rsywx.net/books/01805.html`需要將我們“帶到”一個控制器,這個控制器能識別出參數(`01805`),然后進行相關的后續工作。
換句話說,在V->C的過程中(我們訪問一個URI是V層次的動作,而對這個動作進行相應是C層次的操作),我們如何建立起這個映射?
我們需要的是所謂的路由。
我們先看一個典型的路由:
~~~
homepage:
pattern: /
defaults: { _controller: AppBundle:Default:index }
~~~
這個最簡單的路由由三個部分組成:
* 名稱:`homepage`。這是一個描述性的名稱,可以隨意起名,但是最好和其內容有點關聯并有指示作用。
* 模式:`pattern: /`。這個模式定義了訪問應用的入口。這個入口可以是對外的,也可以是內部的。即以本例來說,它定義的入口就是“`http://somedomain.com/`”,也就是常規意義上的首頁。
* 選項:`defaults: { ... }`。這部分進一步定義了該入口的參數。其中,最重要的就是`_controller`參數。
* `_controller`:指明處理該應用入口請求的控制器是哪一個。除了極個別情況,這是必須有的參數。控制器的指定形式是:Bundle_Name:Controller_Name?(or?class name):Action_Name。在本例中個,處理`/`這個URI請求的控制器動作是`index`,它在`Default`控制器中,并位于`AppBundle`這個包里。
從理論上說,一個應用可以開放的入口并沒有上限。不過在實際應用中,有那么幾十個也就差不多了。
## 參數
路由可以帶參數。而參數有兩種:一種是沒有缺省值而必須提供的,一種是有缺省值而可以省略的。
帶有參數的路由舉例如下:
~~~
book_list:
pattern: /books/list/{type}/{page}/{key}
defaults:
page: 1
key: all
type: title
_controller: trrsywxBundle:Book:list
~~~
路由中的參數用形如`{param_name}`的形式定義。一般建議將各個參數用`/`分割以避免參數之間的混淆和最終URI的清晰。
在上面這個路由中,我們定義了三個參數:`type`,`page`,`key`。這些參數具體派什么用途我們會稍后討論。
在`defaults`中,對這三個參數設置了缺省值。因此,如果我們只是簡單地訪問:`http://mydomain.com/books/list`,那么由于三個參數都沒有提供值,就等同于訪問:`http://mydomain.com/books/list/title/all/1`。
如果一個路由中的參數沒有缺省值,那么必須在訪問時提供。否則SF會報錯。
### 路由參數的限定
對類似“`http://www.rsywx.net/books/01805.html`”這樣的一個URL,我們可以這樣來設置其路由:
~~~
book_detail:
pattern: /books/{id}.html
defaults: { _controller: AppBundle:Book:detail }
~~~
如果一個用戶不小心使用了類似:`/books/abc.html`(一本書的ID不可能是字符)或者`/books/123.html`(一本書的ID必須是5位數字組成)這樣的URI,會發生什么?
這個路由中指定的控制器還是會被執行,根據傳遞進來的參數(abc或者123)進行書籍的選擇——當然就找不到了。如果我們能在請求進入控制器之前就對參數加以限定以避免這樣的低級錯誤,不是更好嗎?
此時,我們可以對“合法”的參數該是怎樣做出限定。
~~~
book_detail:
pattern: /books/{id}.html
defaults: { _controller: AppBundle:Book:detail }
requirements:
id: \d{5}
~~~
這里的`\d{5}`是一個正則表達式,匹配5個數字。通過這樣的限制,我們可以保證通過該路由傳遞過來的參數必然是5位數字。當然,這個數字是不是有對應的書籍是另外一個問題。
### 訪問URI的方法
通常,我們輸入一個URI或者通過點擊一個鏈接訪問一個URI時,都是進行的GET請求。一般來說,GET方法是最常見的,也是足夠用的。但是,在處理表單的提交時,我們一般更會偏向于使用POST方法。也就是說,一個URI顯示表單,然后提交的數據進入另一個URI進行處理。
因此,通常情形下,我們要設置兩個路由:一個用來顯示表單(比如`register`),一個用來處理表單(比如`do_register`)。我們當然不希望用戶在瀏覽器中直接訪問`do_register`所對應的URI,因此有必要對訪問路由的方法進行限制:
~~~
do_register:
pattern: /create_user
defaults: { _controller: AppBundle:User:create }
methods: [POST]
~~~
通過指定`do_register`只能通過POST方法訪問,就阻止了用戶簡單地在瀏覽器中輸入“`/create_user`”來訪問這個URI。
## 創建路由時的常見陷阱
* SF對路由的解析是由上到下的。也就是說,如果有一個路由的模式得到匹配,SF將不再匹配后續的路由。因此,我們必須注意一點,就是路徑模式應該遵循?“越精確、越特殊的模式越在前定義”?的原則。
* 在定義路徑模式時,一定要注意會不會出現可能的重復。
比如這兩個路由:
~~~
books_with_tag:
pattern: /tag/{tag}
add_tag:
pattern: /tag/add
methods: [POST]
~~~
第一個路由可以用來顯示那些有著標記為`tag`的書籍,第二個路由用來為一本書籍增加一個tag。這兩個路由以這樣的順序出現的問題在于,如果我們通過一個表單提交了一些新的tag準備加到一本書籍上,那么我們期望的動作是`/tag/add`這個路由定義的動作,但是由于`books_with_tag`這個路由定義的路徑模式在前,也匹配形如`/tag/add`這樣的調用(此時這個路由的`tag`參數變成`add`),且該路由沒有說明不可接受POST方法,于是這個路由將被第一個匹配。于是我們的表單遞交動作將不會被執行。
解決方法之一,是調換這兩個路由的定義次序;其二,可以限制`books_with_tag`的方法為只接受GET;其三,當然也可以修改其中一個路由的路徑模式,使其不會產生誤解。在實際操作中,我們可以根據需要選擇一種方法來避免出現問題。
路由定義往往是應用開發的第一步——因為至少你必須創建一個主頁吧?
- 引言
- 1 LAMP
- 1.1 安裝虛擬機
- 1.2 安裝Vagrant
- 1.3 安裝Ubuntu
- 1.4 安裝Apache 2
- 1.5 安裝PHP
- 1.6 安裝MySQL服務器
- 1.7 最后的微調
- 1.8 設置一個虛擬主機
- 1.9 一個趁手的IDE
- 2 Symfony 3和重要構件
- 2.1 Symfony 3
- 2.2 Doctrine
- 2.3 Twig
- 2.4 Composer
- 3 Symfony重要概念
- 3.1 MVC
- 3.2 Bundle/包
- 3.3 Route/路由
- 3.4 Controller/控制器
- 3.5 Entity/實體
- 3.6 Repository/倉庫
- 3.7 Template/模板
- 3.8 Test/測試
- 4 藏書管理程序的結構
- 5 創建應用
- 5.1 建立版本管理
- 5.2 建立數據庫
- 5.3 應用結構
- 5.4 建立數據庫實體
- 5.5 樣本數據
- 5.6 路由
- 5.7 模板
- 5.8 開始編寫首頁
- 5.9 書籍詳情頁面
- 5.10 書籍列表頁面
- 5.11 書籍搜索
- 6 用戶和后臺
- 7 結語