# Route 進階

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

```
Route::get('post', 'HomeController@index');
Route::get('post/create', 'HomeController@create');
Route::post('post', 'HomeController@store');
Route::get('post/{id}', 'HomeController@show');
Route::get('post/{id}/edit', 'HomeController@edit');
Route::put('post/{id}', 'HomeController@update');
Route::delete('post/{id}', 'HomeController@destroy');
```

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

## 群組化 (group)

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

```
Route::group(['prefix'=>'post'], function(){
    Route::get('/', 'HomeController@index');
    Route::get('create', 'HomeController@create');
    Route::post('/', 'HomeController@store');
    Route::get('{id}', 'HomeController@show');
    Route::get('{id}/edit', 'HomeController@edit');
    Route::put('{id}', 'HomeController@update');
    Route::delete('{id}', 'HomeController@destroy');
});
```

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

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

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

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

## 資源控制器 (Resource controllers)

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

```
php artisan routes
```

會列出目前建立的所有 Route，如下：

![](https://2143920249-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M48rwYB4_KhIVG8EBhp%2F-M49JjoezbujiTyFIL6l%2F-M49UlHjAzx9g4Ci4cZG%2Flaravel-blog-example-routing.png?alt=media\&token=5309517e-4b5d-49fd-beb4-e2410ae2cc77)

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

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

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

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

![](https://2143920249-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M48rwYB4_KhIVG8EBhp%2F-M49JjoezbujiTyFIL6l%2F-M49Uxa65s5KT2nEwn2P%2Flaravel-blog-example-routing-2.png?alt=media\&token=76e22231-a436-41b7-85b5-ca7f8e1a007f)

僅多了一行 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' => 'HomeController@index', 'as' => 'post.home'));
```

原本第二個參數直接指定 Controller\@method 這個 Action。現在把它改為使用陣列，並且使用兩個關鍵字當 key：

* 'uses' 表示要使用哪個 Controller\@method
* 'as' 表示這個 Route 的 Route Name。

！注意，如果把這行寫在 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](http://laravel.com/api/4.2/Illuminate/Html/HtmlBuilder.html#method_linkRoute)\[1]。要查看 [4.x 的 API 在這裡](http://laravel.com/api/4.2/index.html)\[2]，可以直接搜尋'link'，或在左邊清單中，找到 Html -> HtmlBuilder 就可以看到全部可以使用的方法。

所以，在 4.x 之後的版本：

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

## 參數 (Parameters)

### 選擇性參數

之前我們寫的

```
Route::get('post/{id}', 'HomeController@show');
```

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

```
Route::get('post/{id?}', 'HomeController@show');
```

後面加個問號，當網址是

```
http://localhost/blog/public/post
```

或

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

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

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

```
Route::get('post', 'HomeController@index');
```

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

```
http://localhost/blog/public/post
```

現在，把 HomeController\@show 的 Route 的 {id?} 加上問號。接著修改 HomeController 的 show() 為：

```
public function show($id=null)
{
    if (is_null($id)) {
        return $this->index();
    }

    //下略...
```

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

### 參數的條件限制

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

```
Route::get('post/{id}', 'HomeController@show')->where('id', '[0-9]+');
```

條件限制是以正規表示式來描述。但是因為為我們的 HomeController\@show 本來就無法處理 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 取得標題。

* \[1] <http://laravel.com/api/4.2/Illuminate/Html/HtmlBuilder.html#method_linkRoute>
* \[2] <http://laravel.com/api/4.2/index.html>
