HTTP 路由

基本路由

你會在 app/Http/routes.php 中定義應用程式大多數的路由,該檔案將會被 App\Providers\RouteServiceProvider 類別載入。而最基本的 Laravel 路由僅接受 URI 加上一個閉包

Route::get('/', function () {
    return 'Hello World';
});

Route::post('foo/bar', function () {
    return 'Hello World';
});

Route::put('foo/bar', function () {
    //
});

Route::delete('foo/bar', function () {
    //
});

為多重的動作註冊路由

有時候你可能需要註冊一個路由來回應多個 HTTP 的動作。你可以透過 Route facadematch 方法來使用:

Route::match(['get', 'post'], '/', function () {
    return 'Hello World';
});

或者,你甚至可以透過 any 方法來使用註冊路由並回應所有的 HTTP 動作:

Route::any('foo', function () {
    return 'Hello World';
});

產生 URLs 路由

你可以透過 url 輔助函式產生應用程式路由:

$url = url('foo');

路由參數

基礎路由參數

有時候你可能需要在你的 URI 路由中,取得一些參數。例如,你可能需要從 URL 取得使用者的 ID。你可以透過定義路由參數來取得:

Route::get('user/{id}', function ($id) {
    return 'User '.$id;
});

你可以依照路由需要,定義任何數量的路由參數:

Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

路由的參數都會被放在「大括號」內。當執行路由時,參數會透過路由閉包來傳遞。

注意:路由參數不能包含 - 字元。用底線 (_) 來取代。

選擇性路由參數

有時候你可能需要指定路由參數,但是讓路由參數的存在是可選的。你可以藉由在參數名稱後面加上 ? 達成:

Route::get('user/{name?}', function ($name = null) {
    return $name;
});

Route::get('user/{name?}', function ($name = 'John') {
    return $name;
});

正規表達式限制參數

你可以在路由實例上使用 where 方法限制你的路由參數格式。where 方法接受參數的名稱和定義參數應該如何被限制的正規表達式:

Route::get('user/{name}', function ($name) {
    //
})
->where('name', '[A-Za-z]+');

Route::get('user/{id}', function ($id) {
    //
})
->where('id', '[0-9]+');

Route::get('user/{id}/{name}', function ($id, $name) {
    //
})
->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

全域限制

如果你希望路由參數可以總是遵循正規表達式,你可以使用 pattern 方法。你應該在 RouteServiceProviderboot 方法裡定義這些模式:

/**
 * Define your route model bindings, pattern filters, etc.
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 */
public function boot(Router $router)
{
    $router->pattern('id', '[0-9]+');

    parent::boot($router);
}

一旦定義了模式,會自動的應用到所有使用該參數名稱的路由:

Route::get('user/{id}', function ($id) {
    // Only called if {id} is numeric.
});

命名路由

命名路由讓你更方便為特定路由產生 URL 或進行重導。你可以使用 as 陣列鍵指定名稱到你的路由:

Route::get('user/profile', ['as' => 'profile', function () {
    //
}]);

你還可以指定路由名稱到你的控制器操作:

Route::get('user/profile', [
    'as' => 'profile', 'uses' => 'UserController@showProfile'
]);

除了在路由的陣列定義中指定路由名稱外,你也可以在路由定義後方鏈結 name 方法:

Route::get('user/profile', 'UserController@showProfile')->name('profile');

路由群組和命名路由

假設你使用路由群組,你可以指定一個 as 關鍵字在你的路由群組的屬性陣列,也允許你設定所有路由群組中共同的路由名稱前綴:

Route::group(['as' => 'admin::'], function () {
    Route::get('dashboard', ['as' => 'dashboard', function () {
        // 路由名稱為「admin::dashboard」
    }]);
});

對命名路由產生 URLs

一旦你在給定的路由中分配了名稱,你可以透過 route 函式,使用路由名稱產生 URLs 或是重新導向:

$url = route('profile');

$redirect = redirect()->route('profile');

如果在路由定義參數,你可以把參數作為第二個參數傳遞給 route 方法。給定的參數將自動加入到 URL:

Route::get('user/{id}/profile', ['as' => 'profile', function ($id) {
    //
}]);

$url = route('profile', ['id' => 1]);

路由群組

路由群組允許你共用路由屬性,例如:中介層、命名空間,你可以利用路由群組套用這些屬性到多個路由,而不需在每個路由都設定一次。共用屬性被指定為陣列格式,當作 Route::group 方法的第一個參數:

為了了解更多路由群組相關內容,我們會看過去幾個常見的使用範例和功能。

中介層

要指定中介層到所有群組內的路由,你可以在群組屬性陣列裡使用 middleware 參數。中介層將會依照你在列表內指定的順序執行:

Route::group(['middleware' => 'auth'], function () {
    Route::get('/', function ()    {
        // 使用 Auth 中介層
    });

    Route::get('user/profile', function () {
        // 使用 Auth 中介層
    });
});

命名空間

另一個常見的範例是,指派相同的 PHP 命名空間中給控制器群組。你可以使用 namespace 參數指定群組內所有控制器的命名空間:

Route::group(['namespace' => 'Admin'], function()
{
    // 控制器在「App\Http\Controllers\Admin」命名空間

    Route::group(['namespace' => 'User'], function()
    {
        // 控制器在「App\Http\Controllers\Admin\User」命名空間
    });
});

記得,預設 RouteServiceProvider 會在命名空間群組內導入你的 routes.php 檔案,讓你不用指定完整的 App\Http\Controllers 命名空間前綴就能註冊控制器路由。所以,我們只需要指定在基底 App\Http\Controllers 根命名空間之後的命名空間部分。

子網域路由

路由群組也可以用來處理萬用字元的子網域。子網域可以像路由 URIs 分配路由參數,讓你在你的路由或控制器取得子網域參數。可以使用路由群組屬性陣列上的 domain 指定子網域變數名稱:

Route::group(['domain' => '{account}.myapp.com'], function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});

路由前綴

透過路由群組陣列屬性中的 prefix,在路由群組內為每個路由給定的 URI 加上前綴。例如,你可能想要在路由群組中將所有的路由 URIs 加上前綴 admin

Route::group(['prefix' => 'admin'], function () {
    Route::get('users', function ()    {
        // 符合「/admin/users」URL
    });
});

你也可以使用 prefix 參數去指定你的路由群組中共用的參數:

Route::group(['prefix' => 'accounts/{account_id}'], function () {
    Route::get('detail', function ($account_id)    {
        // 符合 accounts/{account_id}/detail URL
    });
});

CSRF 保護

介紹

Laravel 提供簡單的方法保護你的應用程式不受到 跨網站請求偽造 攻擊。跨網站請求偽造是一種惡意的攻擊,藉以透過經過身份驗證的使用者身份執行未經授權的命令。

Laravel 會自動產生了一個 CSRF token 給每個活動使用者受應用程式管理的 Session。該 token 用來驗證使用者為實際發出請求至應用程式的使用者。要產生一個隱藏的輸入欄位 _token 包含 CSRF token,你可以使用 csrf_field 輔助函式:

<?php echo csrf_field(); ?>

csrf_field 輔助函式產生以下的 HTML:

<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

當然,也可以在 Blade 模板引擎 中使用:

{{ csrf_field() }}

你不需要手動驗證 POST、PUT 或 DELETE 請求的 CSRF token。在 VerifyCsrfToken HTTP 中介層 將自動驗證請求與 session 中的 token 是否相符。

不受 CSRF 保護的 URIs

有時候你可能會希望一組 URIs 不要被 CSRF 保護。例如,你如果使用 Stripe 處理付款,並且利用他們的 webhook 系統,你需要從 Laravel CSRF 保護中,排除 webhook 的處理路由。

你可以在 VerifyCsrfToken 中介層中增加 $except 屬性來排除 URIs:

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;

class VerifyCsrfToken extends BaseVerifier
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'stripe/*',
    ];
}

X-CSRF-TOKEN

除了檢查 CSRF token 當作 POST 參數之外,在 Laravel VerifyCsrfToken 中介層也會確認請求標頭中的 X-CSRF-TOKEN。例如,你可以將其儲存在 meta 標籤中:

<meta name="csrf-token" content="{{ csrf_token() }}">

一旦你建立了 meta 標籤,你可以使用 jQuery 之類的函式庫將 token 加入到所有的請求標頭。基於 AJAX 的應用,提供了簡單、方便的 CSRF 保護:

$.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
});

X-XSRF-TOKEN

Laravel 也會在 XSRF-TOKEN cookie 中儲存 CSRF token。你也可以使用 cookie 的值來設定 X-XSRF-TOKEN 請求標頭。一些 JavaScript 框架會自動幫你處理,例如:Angular。你不太可能會需要手動去設定這個值。

路由模型綁定

Laravel 路由模型綁定提供了一個方便的方式來注入類別實例至你的路由中。例如,除了注入一個使用者的 ID,你可以注入與給定 ID 相符的完整 User 類別實例。

首先,使用路由的 model 方法為給定參數指定類別。你必須在 RouteServiceProvider::boot 方法中定義你的模型綁定:

綁定參數至模型

public function boot(Router $router)
{
    parent::boot($router);

    $router->model('user', 'App\User');
}

接著,定義包含 {user} 參數的路由:

$router->get('profile/{user}', function(App\User $user) {
    //
});

因為我們已經綁定 {user} 參數至 App\User 模型,所以 User 實例會被注入至該路由。所以,舉個例子,一個至 profile/1 的請求會注入 ID 為 1 的 User 實例。

注意:如果符合的模型不存在於資料庫中,就會自動拋出一個 404 例外。

如果你希望指定你自己的「不存在」行為,只要傳遞一個閉包作為 model 方法的第三個參數:

$router->model('user', 'App\User', function() {
    throw new NotFoundHttpException;
});

如果你希望使用你自己的解析邏輯,那麼你必須使用 Route::bind 方法。你傳遞至 bind 方法的閉包會取得 URI 的部分值,且必須回傳你想注入至路由的類別實例:

$router->bind('user', function($value) {
    return App\User::where('name', $value)->first();
});

表單方法欺騙

HTML 表單沒有支援 PUTPATCHDELETE 動作。所以在定義 PUTPATCHDELETE 路由,並在 HTML 表單中被呼叫的時候,你將需要在表單中增加隱藏的 _method 欄位。隨著 _method 欄位送出的值將被視為 HTTP 請求方法使用:

<form action="/foo/bar" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>

要產生隱藏的輸入欄位 _method,你也可以使用 methid_field 輔助函式:

<?php echo method_field('PUT'); ?>

當然,可以使用 Blade 模板引擎

{{ method_field('PUT') }}

拋出 404 錯誤

這裡有兩種方法從路由手動觸發 404 錯誤。首先,你可以使用 abort 輔助函式。abort 輔助函式只是簡單的拋出一個帶有指定狀態代碼的 Symfony\Component\HttpFoundation\Exception\HttpException

abort(404);

第二,你可以手動拋出 Symfony\Component\HttpKernel\Exception\NotFoundHttpException 的實例。

更多有關如何操作 404 例外和自訂的回應,可以到錯誤章節內參考文件。