Laravel 學習筆記(11) - Route 進階

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');
目前只有一個編輯文章的功能。假如你的網站功能很多,Route 肯定會多到難以維護,其實有幾個方法可以加以調整,本節將專注在 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,因此,原本 route 中的 post 就可以移除或改成斜線。如此在管理上就能很清楚的看到,它們是一夥的。

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

資源控制器(Resource controllers)

這裡要先到終端機(Terminal)看一下我們現在的 route 表。使用 cd 指令進入 blog 網站目錄,輸入:
php artisan routes
會列出目前所建立的 route 表: 現在我們要透過 Route::resource 的方式來換掉前面寫了七行的 route:
Route::resource('post', 'HomeController');
記得把前面的七行 route 刪掉(或註解掉),存檔後再次在終端機執行同樣的指令,你會得到幾乎相同的 routing 表: 多了一行 PATCH,功能和 PUT 相同。Name 的欄位多了 post.index、post.create...等的名稱,這個是 route 的名稱。在 laravel 框架中,你可以像我們範例中指定 連結網址route 名稱 ,最後都會由同一個 Action 也就是 Controller 去執行。

你也可以指定 resource 只產生某幾個 action:
Route::resource('post', 'HomeController', ['only' => ['create','store','destroy']]);

指定 Route Name

之前建立的 route 都沒有指定 route 名稱,所以就只能使用 URI 來請求。這裡示範 Route Name 怎麼寫:
Route::get('post', array('uses' => 'HomeController@index', 'as' => 'post.home'));
原本第二個參數直接指定 Controller@method,把它改為使用陣列,並且使用兩個關鍵字當 key:
  • 'uses' 表示要使用哪個 Controller 的 method
  • 'as' 表示這個 route 的名稱(Name)

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

要怎麼用 route,簡單舉例。在 app/views/home.blade.php 中,原本的
{{ link_to('post/'.$post->id, $post->title) }}
如果使用 route 的方式,要改為:
{{ link_to_route('post.show', $post->title, ['id'=>$post->id]) }}
備註:查了一下文件,link_to 及 link_to_route 是 3.x 的寫法,在 4.x 之後已經改了寫法,改成 HTML::linkRoute。要查看 4.x 的 API 在這裡,可以直接搜尋'link',或在左邊清單中,找到 Html -> HtmlBuilder 就可以看到全部可以使用的方法。
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/posthttp://localhost/blog/public/post/1 都會由這個 route 控制。因為我們的例子中,已經有 "沒有$id" 的 route 了,所以基本上,這個例子沒作用。

硬舉例的話,先將
Route::get('post', 'HomeController@show');
這行註解掉,就不能作用了。現在連到這個網址 http://localhost/blog/public/post
就會出錯。把 {id} 加上問號 {id?}。接著修改 HomeController 的 show() 為:
public function show($id=null)
{
    if (is_null($id)) {
        return $this->index();
    }

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

參數的正規表示式

Route::get('post/{id}', 'HomeController@show')->where('id', '[0-9]+');
加上 where 去做參數的正規表示式限定。但其實只要 id 不是數字,都會發生錯誤,在於錯誤會不同。

非數字的 id 在加了 pattern 的 URI 中,不會被送入 Controller,而是出現找不到對應的網址錯誤。在加 pattern 之前,非數字的 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 綁定,這時候只要使用參數 {pp} 就會得到一個 Post 物件。也就是 http://localhost/blog/public/p/1 中的 1 會送入 {pp} 所綁定的 Post 物件,並以 id 查詢後,傳回給 $post 變數,然後就可以 $post->title 去取得標題。
本文網址:http://blog.tonycube.com/2015/01/laravel-11-route.html
Tony Blog 撰寫,轉載時請註明出處及文章連結,謝謝 😀

14 則留言

  1. 你好,
    我想問下關於put和delete的用法.
    我見你的例子Route::put('post/{id}', 'HomeController@update');
    請問post是否model/table呢? 而ID是否post的model/table內的pri key呢? 如果兩者都不是, 請問能否訴告這是什麼呢?
    我打算用一張form來update table的record. 我現時是没有使用model的, 請問是否需要加回呢?
    感謝

    回覆刪除
  2. 你寫在 Route 裡的都是網址的一部份,
    例如 'post/{id}' 的網址大概會像這樣 http://www.mysite.com/post/1
    所以 post 不是 model 或 table 的名稱,它是網址的一部份,

    {id} 是 PK,會在 Controller 裡用來處理資料庫相關的動作,
    因為更新和刪除都必須知道要對哪個 PK 做動作,所以它們的 URI 是一樣的,
    差別在於你是指定哪個狀態,例如:PUT 或 DELETE 來區別。

    回覆刪除
    回覆
    1. 請問在form的寫法是否如下?
      <form action={{action('EditController@update')}} method="put">

      我在route加下面這句後
      Route::put('edit/{no}', 'EditController@update');

      就出現以下的error msg
      Missing required parameters for [Route: ] [URI: edit/{no}]. (View: C:\xampp\htdocs\atwd2\resources\views\edit.blade.php)
      感謝

      刪除
    2. form 的 method 只有兩種,GET 或 POST,
      PUT 及 DELETE 都必須另外附加一個隱藏欄位來送出,
      大概像這樣:
      <input type="hidden" name="_method" value="PUT">
      Laravel 有提供快捷方法
      {{ method_field('PUT') }}

      你的 form 應該寫成
      <form action="edit/123">
      123 是你的 no 也就是要編輯的 PK。

      你用 Laravel 提供的快捷方法也是可以,但要寫成
      <form action="{{ action('EditController@update', ['no' => 123]); }}">

      刪除
    3. 非常感謝.
      另外, 我剛啱遇到另一個問題. 我用laravel建立了get的API, 想在同一個環境下用作測試.
      例如, 我的API叫://127.0.0.1/?no=1. 這個API是會出XML format.
      咁我在另一個view內想使用這條API.
      $url = 'http://127.0.0.1/api?no=1';
      $xml = simplexml_load_strig($url);
      但就不成功. 没有error msg. 只show等左60秒, server冇反應. 但我用postman測過API是没有問題的.
      請問有冇這方面的經驗呢? 感謝

      刪除
    4. 看一下說明書(http://php.net/manual/en/function.simplexml-load-string.php)
      「simplexml_load_string — Interprets a string of XML into an object」
      這個方法是讀進 XML 字串然後轉成物件,你送網址進去它當然無法解讀,
      你要先將 XML 內容下載下來,才放進去,例如:

      //////////////////////
      <?php
      $url = 'https://tw.news.yahoo.com/rss';
      $xml = file_get_contents($url);
      if ($xml) {
        $xmlObj = simplexml_load_string($xml);
        if (xmlObj) {
          print_r($xmlObj);
        } else {
          echo "XML 解析錯誤";
        }
      } else {
        echo "下載發生錯誤";
      }
      //////////////////////

      file_get_contents 適合用於檔案,雖然它也可以讀網址,
      建議去研究一下 cURL (http://php.net/manual/en/book.curl.php)

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

    請教一下 這行如果要讓他產生如版大說的各自的route name,應該要怎麼寫才行呢?感謝~

    回覆刪除
    回覆
    1. 你用 Route::resource 它就會幫你建立了,
      用指令 php artisan routes 查詢,看 Name 那一欄

      刪除
  4. 另外,route name 跟uri 的差異是什麼啊~ 有點搞不太清楚說, 還請版大開示,感恩~

    回覆刪除
    回覆
    1. 它們的用途都是用來指向 Controller,

      假設網址是 http://www.abc.com/post
      uri 就是 post

      route name 是你自訂的(或 resources 產生),
      差別只在用 uri 和網址直接相關,route name 你可以自訂名稱,
      選一個用就可以

      刪除
    2. 感謝您熱心地對每個問題一一解答:~
      再請教一下,有個函式Redirect::intended 的用法 ,他通常會被放在認證通過後 執行, 不曉得跟Redirect::to 有什麼差別呢?

      刪除
    3. Redirect::to 是導向你指定的頁面,
      Redirect::intended 是使用者意圖進入某個頁面但被強制先登入時,
      在登入後自動導向該頁,如果該頁無法進入,就導向你指定的頁面。

      例如:
      使用者在網址輸入 http://www.xxx.com/show <- 可是這個 show 必須先登入才能看,
      因為使用者還未登入,於是就跳到 /login 頁面,輸入完資料按下登入後,
      會自動導向 /show 這個使用者意圖進入的頁面,
      在找不到該頁的情況下,才導向你指定的頁面,這裡和 Redirect::to 差不多。

      刪除
  5. 請問如果 Resource controllers 要進一步設定參數要怎麼設定呢??

    例如 Route::get('content', 'Admin\ContentController@index')

    我還要加上 modelid跟lang這兩個參數~ 且lang是可有可無


    回覆刪除
    回覆
    1. Route::get('content/{modelid}/{lang?}', 'Admin\ContentController@index')
      對應的可能結果:
      /content/1
      /content/1/zh-TW

      刪除

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。