Route 進階

在前一個迷你部落格專案中,我們的 routes.php 最終內容是這樣:

Route::get('post', '[email protected]');
Route::get('post/create', '[email protected]');
Route::post('post', '[email protected]');
Route::get('post/{id}', '[email protected]');
Route::get('post/{id}/edit', '[email protected]');
Route::put('post/{id}', '[email protected]');
Route::delete('post/{id}', '[email protected]');

你可以思考一件事,目前我們只開發了一個簡單的文章 CRUD 功能,但是我們的 Route 卻寫了 7 行,當你的網站功能越來越多,Route 的數量肯定會多到難以維護。Laravel 提供了一些方法,可以讓你更好管理 Route。

群組化 (group)

你會注意到,這些功能全都在 post 這個網址之下,因此我們可以將它往上提,改成這樣:

Route::group(['prefix'=>'post'], function(){
Route::get('/', '[email protected]');
Route::get('create', '[email protected]');
Route::post('/', '[email protected]');
Route::get('{id}', '[email protected]');
Route::get('{id}/edit', '[email protected]');
Route::put('{id}', '[email protected]');
Route::delete('{id}', '[email protected]');
});

使用 Route::group() 方法可以將位於同一名稱底下的網址群組化。

第一個參數是陣列,['prefix'=>'post'] 的 prefix 是關鍵字,用來指定前置字,因為我們的網址都是以 post 開頭,所以將 post 指定為 prefix。

現在,原本 route 中的 post 就可以移除或改成斜線,這裡的斜線表示為 post 本身。如此在管理上就能很清楚的看到,它們是一夥的。

除了 prefix 關鍵字外,還有 before 及 after 等可以使用。

資源控制器 (Resource controllers)

要使用資源控制器,就要先瞭解 Route 表。首先,開啟終端機,使用 cd 指令進入 blog 網站根目錄,輸入:

php artisan routes

會列出目前建立的所有 Route,如下:

因為這 7 個 Route 是一個基本的 CRUD 流程,經常使用,所以 Route 提供了一個方法,可以幫你自動產生這 7 個 Route。

現在,將 routes.php 中,之前寫的 Route 全部註解掉,改成使用 resourece() 方法:

Route::resource('post', 'HomeController');

存檔,再回到終端機執行同樣的指令,你會得到幾乎相同的 Route 表,如下:

僅多了一行 PATCH。Name 的欄位多了 post.index、post.create...等的名稱,這個是 Route 的名稱。在 Laravel 框架中,你可以使用 URI(前面的範例都是使用這個)或 Name 的方式指定 Route,最後都會由對應的 Action 也就是 Controller 去執行。

所以,現在你可以瞭解,為什麼在 Controller 中,我們要使用這些特定名稱來為方法命名了,因為這樣才能使用資源控制器來簡化 Route 的撰寫。

有時候你並沒有使用到那麼多 Route ,但又想使用資源控制器,你也可以要求 resource 只產生某幾個 Route,使用第三個參數:

Route::resource('post', 'HomeController', ['only' => ['create','store','destroy']]);

第三個參數是陣列型態,可以使用許多條件。使用 'only' 關鍵字,接著給一個陣列的值,其值可以是 index、create、store、show、edit、update、destroy 等值,使用 only 的話,就是有指定的才會建立,其餘的則忽略。

使用 Route Name

之前建立的 Route 都沒有設定 Route 名稱,所以就只能使用 URI 來接受請求。這裡示範 Route Name 怎麼寫:

Route::get('post', array('uses' => '[email protected]', 'as' => 'post.home'));

原本第二個參數直接指定 [email protected] 這個 Action。現在把它改為使用陣列,並且使用兩個關鍵字當 key:

!注意,如果把這行寫在 Route::resource 這行之後,由 resource 產生的相同 URI 的 Route 就會被後面這個 Route 取代(表示不會有重覆的 Route 出現),所以最後的 Route Name 就會是 post.home 而不是 post.index,你可以在終端機下指令觀看 Route 表的變化。

那要怎麼用 Route Name 呢?簡單舉例,在 app/views/home.blade.php 中,原本的

{{ link_to('post/'.$post->id, $post->title) }}

如果使用 Route Name 的方式,要改為:

{{ link_to_route('post.show', $post->title, ['id'=>$post->id]) }}

註:link_to 及 link_to_route 是 3.x 的寫法,在 4.x 之後已經改了寫法,改成 HTML::linkRoute[1]。要查看 4.x 的 API 在這裡[2],可以直接搜尋'link',或在左邊清單中,找到 Html -> HtmlBuilder 就可以看到全部可以使用的方法。

所以,在 4.x 之後的版本:

link_to 改為 HTML::link
link_to_route 改為 HTML::linkRoute

參數 (Parameters)

選擇性參數

之前我們寫的

Route::get('post/{id}', '[email protected]');

{id} 就是參數。如果這個參數是選擇性的,可以加上問號:

Route::get('post/{id?}', '[email protected]');

後面加個問號,當網址是

http://localhost/blog/public/post

http://localhost/blog/public/post/1

都會由這個 Route 處理。因為在我們的例子中,已經有 "沒有$id" 的 Route 了,所以要小心衝突的發生。

在我們的例子中,如果要使用選擇性參數,可以先將

Route::get('post', '[email protected]');

這行註解掉,就不能作用了,現在連到這個網址就會出錯

http://localhost/blog/public/post

現在,把 [email protected] 的 Route 的 {id?} 加上問號。接著修改 HomeController 的 show() 為:

public function show($id=null)
{
if (is_null($id)) {
return $this->index();
}
//下略...

這樣當 $id 為 null 時,就會轉給 index() 去處理。這裡只是舉例,請在必要的時候才這麼做。

參數的條件限制

串接 where() 方法可做條件限制:

Route::get('post/{id}', '[email protected]')->where('id', '[0-9]+');

條件限制是以正規表示式來描述。但是因為為我們的 [email protected] 本來就無法處理 id 非數字的情況,所以無論有沒有加條件限制都會發生錯誤,在於錯誤會不同。

當輸入的 id 不是數字,在加了條件限制的 URI 中,這個請求不會被送入 Controller,而是出現找不到對應的 Route 的錯誤。在加條件限制之前,非數字的 id 仍會送進 Controller,但在 Model 查詢資料庫時,會因為資料型態不符合(id必須為數值)而出現錯誤,這時候會是資料庫查詢錯誤。

你也可以把模式寫成全域的

Route::pattern('id', '[0-9]+');

記得要寫在最上面,這樣會影響之後所有參數為 id 的 Route。

過濾器(filter)

Route 還可以設定過濾器,在執行某個網址之前,先做一些檢查。這些過濾器另外寫在 app/filters.php 檔案裡。之後我們會用到 auth 這個內建的過濾器來做登入的驗證。

Model 綁定(Model Binding)

Route 提供 Model 及資料表的綁定,

Route::model('pp', 'Post');
Route::get('p/{pp}', function(Post $post)
{
return $post->title;
});

我們把 pp 和 Post Model 綁定,這時候只要使用參數 {pp} 就會得到一個 Post 物件。也就是

http://localhost/blog/public/p/1

中的 1 會送入 {pp} 所綁定的 Post 物件,並以 id 查詢後,傳回給 $post 變數,然後就可以使用 $post->title 取得標題。