Laravel 路由知識回顧記錄 轉自laravel 學院Laravel5.8

點擊跳轉的傳送門

路由入門

最基本的 Laravel 路由只接收一個 URI 和一個閉包,並以此爲基礎提供一個非常簡單優雅的路由定義方法:

Route::get('hello', function () {
    return 'Hello, Welcome to LaravelAcademy.org';
});

我們以在安裝配置文檔中新建的 blog 應用爲例,在 routes/web.php 中定義該路由:

在瀏覽器中通過 http://blog.test/hello 即可訪問我們剛剛定義的路由,頁面輸出內容如下:

Hello, welcome to LaravelAcademy.org

默認路由文件

所有 Laravel 路由都定義在位於 routes 目錄下的路由文件中,這些文件通過框架自動加載,相應邏輯位於 app/Providers/RouteServiceProvider 類。routes/web.php 文件定義了 Web 界面的路由,這些路由被分配到了 web 中間件組,從而可以使用 Session 和 CSRF 保護等功能。routes/api.php 中的路由是無狀態的,這是因爲被分配到了 api 中間件組。

對大多數應用而言,都是從 routes/web.php 文件開始定義路由。定義在 routes/web.php 中的路由可以通過在瀏覽器地址欄輸入相應的 URL 進行訪問,例如,你可以通過 http://blog.test/user 訪問下面的路由:

Route::get('/user', 'UsersController@index');

正如前面所提到的,定義在 routes/api.php 文件中的路由通過 app/Providers/RouteServiceProvider 的處理被嵌套在一個路由羣組中, 在這個羣組中,所有路由會被自動添加 /api 前綴,所以你不需要再到路由文件中爲每個路由手動添加,你可以通過編輯 RouteServiceProvider 類來修改路由前綴以及其他的路由羣組選項:

有效的路由方法

我們可以註冊路由來響應任何 HTTP 請求動作:

Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

有時候還需要註冊一個路由響應多種 HTTP 請求動作 —— 這可以通過 match 方法來實現。或者,可以使用 any 方法註冊一個路由來響應所有 HTTP 請求動作:

Route::match(['get', 'post'], 'foo', function () {
    return 'This is a request from get or post';
});

Route::any('bar', function () {
    return 'This is a request from any HTTP verb';
});

測試 GET 請求的時候直接在瀏覽器中輸入請求地址即可,測試 POST 請求可以通過客戶端工具,比如 Postman,此外如果上面的路由是定義在 routes/web.php 的話,在測試 POST 請求之前,需要將對應路由取消 CSRF 保護檢查,否則會返回 419 狀態碼導致無法請求成功,取消的方法是在 app/Http/Middleware/VerifyCsrfToken 中設置排除檢查路由:

下面我們來測試下 POST 請求:

如果路由是定義在 routes/api.php 的話,則無需關注 CSRF 保護問題,比如我們在 routes/api.php 定義 bar 路由,並且在 VerifyCsrfToken 的 $except 屬性數組中移除 bar,然後我們測試下對 http://blog.test/api/bar 的 POST 請求:

正如我們所預測的,完全沒有任何問題,背後的原因是因爲 Web 路由文件中定義的路由都位於 web 中間件羣組,該羣組默認啓用 CSRF 保護檢查,而 API 路由文件位於 api 路由羣組,該羣組下的路由主要用於 第三方 API 請求,沒辦法進行 CSRF 檢查,所以不需要做任何處理。

CSRF 保護

在 routes/web.php 路由文件中所有請求方式爲 PUTPOST 或 DELETE 的路由對應的 HTML 表單都必須包含一個 CSRF 令牌字段,否則,請求會被拒絕。關於 CSRF 的更多細節,可以參考 CSRF文檔

<form method="POST" action="/profile">
    @csrf
    ...
</form>

還是以上面的 foo 路由爲例,如果我們不在 VerifyCsrfToken 中間件中排除對它的檢查(事實上,這樣的操作也不安全),那麼就需要在表單提交中帶上 csrf_token 字段:

這樣,當我們訪問 http://blog.test/form 然後在頁面點擊提交按鈕後,頁面會跳轉到 http://blog.test/foo 並顯示如下內容:

This is a request from get or post

路由重定向

如果你需要定義一個重定向到其他 URI 的路由,可以使用 Route::redirect 方法,該方法非常方便,以至於你不需要再定義額外的路由或控制器來執行簡單的重定向邏輯:

Route::redirect('/here', '/there');

其中 here 表示原路由,there 表示重定向之後的路由。默認情況下,Route::redirect 返回 302 狀態碼,你可以使用可選的第三個參數來自定義這個狀態碼:

Route::redirect('/here', '/there', 301);

你還可以使用 Route::permanentRedirect 方法來返回 301 狀態碼:

Route::permanentRedirect('/here', '/there');

路由視圖

如果你的路由需要返回一個視圖,可以使用 Route::view 方法,和 redirect 方法類似,這個方法也很方便,以至於你不需要在額外定義一個路由或控制器。view 方法接收一個 URI 作爲第一個參數,以及一個視圖名稱作爲第二個參數,此外,你還可以提供一個數組數據傳遞到該視圖方法作爲可選的第三個參數,該數組數據可用於視圖中的數據渲染:

Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => '學院君']);

我們在 routes/web.php 定義一個路由視圖如下:

爲了保證可以共用 welcome.blade.php 這個視圖文件,我們也對默認提供的 / 路由做了調整,接下來,我們需要修改 resources/views/welcome.blade.php 代碼以支持 website 數據變量:

我們將原來寫死的 Laravel 文本調整爲支持變量傳入的方式,這樣,我們就可以在瀏覽器中通過 http://blog.test/view 訪問路由視圖了:

路由參數

必選參數

有時我們需要在路由中獲取 URI 請求參數。例如,如果要從 URL 中獲取用戶ID,需要通過如下方式定義路由參數:

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

這樣我們在瀏覽器中訪問 http://blog.test/user/1,就會得到以下輸出:

User 1

可以根據需要在路由中定義多個路由參數:

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

根據上面的示例,路由參數需要通過花括號 {} 進行包裹並且是拼音字母,這些參數在路由被執行時會被傳遞到路由的閉包。路由參數名稱不能包含 - 字符,如果需要的話可以使用 _ 替代,比如如果某個路由參數定義成 {post-id} 則訪問路由會報錯,應該修改成 {post_id} 才行。路由參數被注入到路由回調/控制器取決於它們的順序,與回調/控制器名稱無關。

可選參數

有必選參數就有可選參數,這可以通過在參數名後加一個 ? 標記來實現,這種情況下需要給相應的變量指定默認值,當對應的路由參數爲空時,使用默認值:

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

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

這時如果定義的路由是下面這個的話,訪問 http://blog.test/user 會返回 John

正則約束

可以通過路由實例上的 where 方法來約束路由參數的格式。where 方法接收參數名和一個正則表達式來定義該參數如何被約束:

Route::get('user/{name}', function ($name) {
    // $name 必須是字母且不能爲空
})->where('name', '[A-Za-z]+');

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

Route::get('user/{id}/{name}', function ($id, $name) {
    // 同時指定 id 和 name 的數據格式
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

使用正則約束還有一個好處就是避免了 user/{id} 和 user/{name} 的混淆。

全局約束

如果想要路由參數在全局範圍內被給定正則表達式約束,可以使用 pattern 方法。需要在 RouteServiceProvider 類的 boot 方法中定義這種約束模式:

/**
 * 定義路由模型綁定,模式過濾器等
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 * @translator  http://laravelacademy.org
 */
public function boot()
{
    Route::pattern('id', '[0-9]+');
    parent::boot();
}

一旦模式被定義,將會自動應用到所有包含該參數名的路由中:

Route::get('user/{id}', function ($id) {
    // 只有當 {id} 是數字時纔會被調用
});

除此之外,該模式還會被應用到諸如下面這些路由參數上:

Route::get('post/{id}', function ($id) {
    // 只有當 {id} 是數字時纔會被調用
});

Route::get(`product/{id}', function ($id) {
    // 只有當 {id} 是數字時纔會被調用
});

很顯然這種方式讓代碼更簡潔,也爲我們實現同一參數統一約束帶來了方便。

編碼/

Laravel 路由組件支持除 / 之外的所有字符,如果要在佔位符中使用 / 需要通過 where 條件正則表達式顯式允許:

Route::get('search/{search}', function ($search) {
    return $search;
})->where('search', '.*');

注:只有最後一個路由參數片段中才支持編碼正斜槓/

命名路由

命名路由爲生成 URL 或重定向提供了方便,實現起來也很簡單,在路由定義之後使用 name 方法鏈的方式來定義該路由的名稱:

Route::get('user/profile', function () {
    // 通過路由名稱生成 URL
    return 'my url: ' . route('profile');
})->name('profile');

還可以爲控制器動作指定路由名稱:

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

這樣我們就可以通過以下方式定義重定向:

Route::get('redirect', function() {
    // 通過路由名稱進行重定向
    return redirect()->route('profile');
});

爲命名路由生成 URL

正如上面代碼所展示的,爲給定路由分配名稱之後,就可以通過輔助函數 route 爲該命名路由生成 URL 或者通過 redirect 函數進行重定向:

// 生成URL
$url = route('profile');

// 生成重定向
return redirect()->route('profile');

如果命名路由定義了參數,可以將該參數作爲第二個參數傳遞給 route 函數。給定的路由參數將會自動插入到 URL 中:

Route::get('user/{id}/profile', function ($id) {
    $url = route('profile', ['id' => 1]);
    return $url;
})->name('profile');

這樣,當我們訪問 http://blog.test/user/123/profile 頁面輸出內容也是 http://blog.test/user/123/profile

檢查當前路由

如果你想要判斷當前請求是否被路由到給定命名路由,可以使用 Route 實例上的 named 方法,例如,你可以從路由中間件中檢查當前路由名稱:

/**
 * 處理輸入請求
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
    if ($request->route()->named('profile')) {
        //
    }

    return $next($request);
}

路由分組

路由分組的目的是讓我們在多個路由中共享相同的路由屬性,比如中間件和命名空間等,這樣的話我們定義了大量的路由時就不必爲每一個路由單獨定義屬性。共享屬性以數組的形式作爲第一個參數被傳遞給 Route::group 方法。

嵌套的分組會嘗試智能地將屬性合併到父分組中,中間件和 where 條件會直接被合併,而路由命名、命名空間、以及路由前綴會被附加到父組件對應屬性之後。命名空間分隔符和 URI 中的斜槓會被自動添加到合適的位置。

中間件

要給某個路由分組中定義的所有路由分配中間件,可以在定義分組之前使用 middleware 方法。中間件將會按照數組中定義的順序依次執行:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // Uses first & second Middleware
    });

    Route::get('user/profile', function () {
        // Uses first & second Middleware
    });
});

關於中間件的使用我們在後面單獨講中間件時再進行示例演示,這裏我們先了解這樣使用就行。

命名空間

路由分組另一個通用的例子是使用 namespace 方法分配同一個 PHP 命名空間給該分組下的多個控制器:

Route::namespace('Admin')->group(function () {
    // Controllers Within The "App\Http\Controllers\Admin" Namespace
});

默認情況下,RouteServiceProvider 在一個命名空間分組下引入所有路由文件,並指定所有控制器類所在的默認命名空間是 App\Http\Controllers,因此,我們在定義控制器的時候只需要指定命名空間 App\Http\Controllers 之後的部分即可。

子域名路由

路由分組還可以被用於處理子域名路由,子域名可以像 URI 一樣被分配給路由參數,從而允許捕獲子域名的部分用於路由或者控制器,子域名可以在定義分組之前調用 domain 方法來指定:

Route::domain('{account}.blog.dev')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        return 'This is ' . $account . ' page of User ' . $id;
    });
});

比如我們設置會員子域名爲 account.blog.test,那麼就可以通過 http://account.blog.test/user/1 訪問用戶ID爲 1 的會員信息了:

This is account page of User 1

路由前綴

prefix 方法可以用來爲分組中每個路由添加一個給定 URI 前綴,例如,你可以爲分組中所有路由 URI 添加 admin 前綴 :

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // Matches The "/admin/users" URL
    });
});

這樣我們就可以通過 http://blog.test/admin/users 訪問路由了。

路由名稱前綴

name 方法可通過傳入字符串爲分組中的每個路由名稱設置前綴,例如,你可能想要在所有分組路由的名稱前添加 admin 前綴,由於給定字符串和指定路由名稱前綴字符串完全一樣,所以需要在前綴字符串末尾後加上 . 字符:

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // 新的路由名稱爲 "admin.users"...
    })->name('users');
});

路由模型綁定

注入模型 ID 到路由或控制器動作時,通常需要查詢數據庫才能獲取相應的模型數據。Laravel 路由模型綁定讓注入模型實例到路由變得簡單,例如,你可以將匹配給定 ID 的整個 User 類實例注入到路由中,而不只是注入用戶 ID。

隱式綁定

Laravel 會自動解析定義在路由或控制器動作(變量名匹配路由片段)中的 Eloquent 模型類型聲明,例如(我們將這個路由定義在 routes/api.php 文件中):

Route::get('users/{user}', function (App\User $user) {
    return $user->email;
});

在這個例子中,由於類型聲明瞭 Eloquent 模型 App\User,對應的變量名 $user 會匹配路由片段中的 {user},這樣,Laravel 會自動注入與請求 URI 中傳入的 ID 對應的用戶模型實例。如果匹配模型實例在數據庫中不存在,會自動生成 404 響應。

在演示本功能之前,我們需要先創建數據表,由於我是在 Valet 開發環境中開發,需要自己創建數據庫,我們將數據庫命名爲 valet,本地的數據庫用戶名爲 root,密碼爲空,對應地,修改 .env 文件配置如下:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=valet
DB_USERNAME=root
DB_PASSWORD=

具體配置值以你自己的開發環境設置爲準。我們將基於 Laravel 強大的數據庫遷移功能創建 users 表,關於數據庫遷移後面在數據庫部分會詳細討論,這裏我們通過以下命令來生成 users 表即可:

php artisan migrate

進入數據庫可以看到該表已經生成:

這時,users 數據表還沒有任何記錄,如果數據庫中找不到對應的模型實例,會自動生成 HTTP 404 響應,提示頁面不存在,所以我們需要在這張表中插入一條記錄,這裏我們基於 Laravel 強大的數據庫填充器來快速完成數據填充功能,首先通過如下命令生成 users 對應的數據表填充器:

php artisan make:seeder UsersTableSeeder

該命令會在 database/seeds 目錄下生成一個 UsersTableSeeder 文件,編輯該文件內容如下:

然後編輯同目錄下的 DatabaseSeeder.php 文件如下(取消調用數據表填充器前的註釋):

最後執行 php artisan db:seed 即可插入對應數據到 users 表了,這樣我們在瀏覽器中再次訪問 http://blog.test/api/users/1 的時候就會顯示 User 模型數據了:

接下來,你就可以在應用代碼中直接拿 $user 模型去做你想做的事情了,而不需要自己去數據庫查詢,從而提高了開發的效率。

自定義鍵名

如果你想要在隱式模型綁定中使用數據表的其它字段而不是 id 字段,可以重寫 Eloquent 模型類的 getRouteKeyName 方法,以 User 模型爲例,可以在該模型類中添加這個方法 :

/**
 * Get the route key for the model.
 *
 * @return string
 */
public function getRouteKeyName()
{
    return 'name';
}

這樣我們就可以通過 http://blog.test/api/users/jroJoGP71W 訪問同一個模型實例了。這裏需要注意的點是如果該字段不是唯一鍵,則會返回結果集的第一條記錄,對應的底層實現在這裏:

顯式綁定

有隱式綁定,就有顯式綁定。要註冊顯式綁定,可以使用路由器的 model 方法來爲給定參數指定綁定類。你需要在 RouteServiceProvider 類的 boot 方法中定義顯式模型綁定:

public function boot()
{
    parent::boot();
    Route::model('user_model', App\User::class);
}

接下來,在 routes/api.php 中定義一個包含 {user} 參數的路由:

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

由於我們已經綁定 {user_model} 參數到 App\User 模型,User 實例會被注入到該路由。因此,如果請求 URL 是 http://blog.test/api/profile/1,就會注入一個用戶 ID 爲 1 的 User 實例。

如果匹配的模型實例在數據庫不存在,會自動生成並返回 HTTP 404 響應。

自定義解析邏輯

如果你想要使用自定義的解析邏輯,可以在 RouteServiceProvider 類的 boot 方法中使用 Route::bind 方法,傳遞到 bind 方法的閉包會獲取到 URI 請求參數中的值,並且返回你想要在該路由中注入的類實例:

public function boot()
{
    parent::boot();

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

此外,你還可以在 Eloquent 模型中覆蓋 resolveRouteBinding 方法,這個方法會獲取 URI 片段中的值並返回應該被注入的路由模型類實例:

/**
 * Retrieve the model for a bound value.
 *
 * @param  mixed  $value
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value)
{
    return $this->where('name', $value)->first() ?? abort(404);
}

兜底路由

使用 Route::fallback 方法可以定義一個當所有其他路由都未能匹配請求 URL 時所執行的路由。通常,未處理請求會通過 Laravel 的異常處理器自動渲染一個「404」頁面,不過,如果你在 routes/web.php 文件中定義了 fallback 路由的話,所有 web 中間件組中的路由都會應用此路由作爲兜底,當然,如果需要的話,你還可以添加額外的中間件到此路由:

Route::fallback(function () {
    //
});

注:兜底路由應該總是放到應用註冊的所有路由的最後。

頻率限制

Laravel 自帶了一個中間件用於限制對應用路由的訪問頻率。開始使用該功能之前,分配 throttle 中間件到某個路由或路由分組,throttle 中間件接收兩個參數用於判斷給定時間內(單位:分鐘)的最大請求次數。例如,我們指定登錄用戶每分鐘只能訪問下面的分組路由 60 次:

Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

超出訪問次數後,會返回 429 狀態碼並提示「Too many requests」。

動態頻率限制

此外,還可以基於 User 模型的屬性來動態設置最大請求次數。例如,如果 User 模型包含 rate_limit 屬性,就可以將其這個屬性名傳遞到 throttle 中間件,這樣就可以將屬性值作爲計算最大請求次數的數據來源:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

表單方法僞造

HTML 表單不支持 PUTPATCH 或者 DELETE 請求方法,因此,在 HTML 表單中調用 PUTPATCH 或 DELETE 路由時,需要添加一個隱藏的 _method 字段,其值被用作該表單的 HTTP 請求方法:

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

還可以直接使用 Blade 指令 @method 來生成 _method 字段:

<form action="/foo/bar" method="POST">
    @method('PUT')
    @csrf
</form>

訪問當前路由

你可以使用 Route 門面上的 currentcurrentRouteName 和 currentRouteAction 方法來訪問處理當前輸入請求的路由信息:

// 獲取當前路由實例
$route = Route::current(); 
// 獲取當前路由名稱
$name = Route::currentRouteName();
// 獲取當前路由action屬性
$action = Route::currentRouteAction();

參考 API 文檔瞭解路由門面底層類以及Route實例的更多可用方法。

路由入門

最基本的 Laravel 路由只接收一個 URI 和一個閉包,並以此爲基礎提供一個非常簡單優雅的路由定義方法:

Route::get('hello', function () {
    return 'Hello, Welcome to LaravelAcademy.org';
});

我們以在安裝配置文檔中新建的 blog 應用爲例,在 routes/web.php 中定義該路由:

在瀏覽器中通過 http://blog.test/hello 即可訪問我們剛剛定義的路由,頁面輸出內容如下:

Hello, welcome to LaravelAcademy.org

默認路由文件

所有 Laravel 路由都定義在位於 routes 目錄下的路由文件中,這些文件通過框架自動加載,相應邏輯位於 app/Providers/RouteServiceProvider 類。routes/web.php 文件定義了 Web 界面的路由,這些路由被分配到了 web 中間件組,從而可以使用 Session 和 CSRF 保護等功能。routes/api.php 中的路由是無狀態的,這是因爲被分配到了 api 中間件組。

對大多數應用而言,都是從 routes/web.php 文件開始定義路由。定義在 routes/web.php 中的路由可以通過在瀏覽器地址欄輸入相應的 URL 進行訪問,例如,你可以通過 http://blog.test/user 訪問下面的路由:

Route::get('/user', 'UsersController@index');

正如前面所提到的,定義在 routes/api.php 文件中的路由通過 app/Providers/RouteServiceProvider 的處理被嵌套在一個路由羣組中, 在這個羣組中,所有路由會被自動添加 /api 前綴,所以你不需要再到路由文件中爲每個路由手動添加,你可以通過編輯 RouteServiceProvider 類來修改路由前綴以及其他的路由羣組選項:

有效的路由方法

我們可以註冊路由來響應任何 HTTP 請求動作:

Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);

有時候還需要註冊一個路由響應多種 HTTP 請求動作 —— 這可以通過 match 方法來實現。或者,可以使用 any 方法註冊一個路由來響應所有 HTTP 請求動作:

Route::match(['get', 'post'], 'foo', function () {
    return 'This is a request from get or post';
});

Route::any('bar', function () {
    return 'This is a request from any HTTP verb';
});

測試 GET 請求的時候直接在瀏覽器中輸入請求地址即可,測試 POST 請求可以通過客戶端工具,比如 Postman,此外如果上面的路由是定義在 routes/web.php 的話,在測試 POST 請求之前,需要將對應路由取消 CSRF 保護檢查,否則會返回 419 狀態碼導致無法請求成功,取消的方法是在 app/Http/Middleware/VerifyCsrfToken 中設置排除檢查路由:

下面我們來測試下 POST 請求:

如果路由是定義在 routes/api.php 的話,則無需關注 CSRF 保護問題,比如我們在 routes/api.php 定義 bar 路由,並且在 VerifyCsrfToken 的 $except 屬性數組中移除 bar,然後我們測試下對 http://blog.test/api/bar 的 POST 請求:

正如我們所預測的,完全沒有任何問題,背後的原因是因爲 Web 路由文件中定義的路由都位於 web 中間件羣組,該羣組默認啓用 CSRF 保護檢查,而 API 路由文件位於 api 路由羣組,該羣組下的路由主要用於 第三方 API 請求,沒辦法進行 CSRF 檢查,所以不需要做任何處理。

CSRF 保護

在 routes/web.php 路由文件中所有請求方式爲 PUTPOST 或 DELETE 的路由對應的 HTML 表單都必須包含一個 CSRF 令牌字段,否則,請求會被拒絕。關於 CSRF 的更多細節,可以參考 CSRF文檔

<form method="POST" action="/profile">
    @csrf
    ...
</form>

還是以上面的 foo 路由爲例,如果我們不在 VerifyCsrfToken 中間件中排除對它的檢查(事實上,這樣的操作也不安全),那麼就需要在表單提交中帶上 csrf_token 字段:

這樣,當我們訪問 http://blog.test/form 然後在頁面點擊提交按鈕後,頁面會跳轉到 http://blog.test/foo 並顯示如下內容:

This is a request from get or post

路由重定向

如果你需要定義一個重定向到其他 URI 的路由,可以使用 Route::redirect 方法,該方法非常方便,以至於你不需要再定義額外的路由或控制器來執行簡單的重定向邏輯:

Route::redirect('/here', '/there');

其中 here 表示原路由,there 表示重定向之後的路由。默認情況下,Route::redirect 返回 302 狀態碼,你可以使用可選的第三個參數來自定義這個狀態碼:

Route::redirect('/here', '/there', 301);

你還可以使用 Route::permanentRedirect 方法來返回 301 狀態碼:

Route::permanentRedirect('/here', '/there');

路由視圖

如果你的路由需要返回一個視圖,可以使用 Route::view 方法,和 redirect 方法類似,這個方法也很方便,以至於你不需要在額外定義一個路由或控制器。view 方法接收一個 URI 作爲第一個參數,以及一個視圖名稱作爲第二個參數,此外,你還可以提供一個數組數據傳遞到該視圖方法作爲可選的第三個參數,該數組數據可用於視圖中的數據渲染:

Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => '學院君']);

我們在 routes/web.php 定義一個路由視圖如下:

爲了保證可以共用 welcome.blade.php 這個視圖文件,我們也對默認提供的 / 路由做了調整,接下來,我們需要修改 resources/views/welcome.blade.php 代碼以支持 website 數據變量:

我們將原來寫死的 Laravel 文本調整爲支持變量傳入的方式,這樣,我們就可以在瀏覽器中通過 http://blog.test/view 訪問路由視圖了:

路由參數

必選參數

有時我們需要在路由中獲取 URI 請求參數。例如,如果要從 URL 中獲取用戶ID,需要通過如下方式定義路由參數:

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

這樣我們在瀏覽器中訪問 http://blog.test/user/1,就會得到以下輸出:

User 1

可以根據需要在路由中定義多個路由參數:

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

根據上面的示例,路由參數需要通過花括號 {} 進行包裹並且是拼音字母,這些參數在路由被執行時會被傳遞到路由的閉包。路由參數名稱不能包含 - 字符,如果需要的話可以使用 _ 替代,比如如果某個路由參數定義成 {post-id} 則訪問路由會報錯,應該修改成 {post_id} 才行。路由參數被注入到路由回調/控制器取決於它們的順序,與回調/控制器名稱無關。

可選參數

有必選參數就有可選參數,這可以通過在參數名後加一個 ? 標記來實現,這種情況下需要給相應的變量指定默認值,當對應的路由參數爲空時,使用默認值:

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

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

這時如果定義的路由是下面這個的話,訪問 http://blog.test/user 會返回 John

正則約束

可以通過路由實例上的 where 方法來約束路由參數的格式。where 方法接收參數名和一個正則表達式來定義該參數如何被約束:

Route::get('user/{name}', function ($name) {
    // $name 必須是字母且不能爲空
})->where('name', '[A-Za-z]+');

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

Route::get('user/{id}/{name}', function ($id, $name) {
    // 同時指定 id 和 name 的數據格式
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

使用正則約束還有一個好處就是避免了 user/{id} 和 user/{name} 的混淆。

全局約束

如果想要路由參數在全局範圍內被給定正則表達式約束,可以使用 pattern 方法。需要在 RouteServiceProvider 類的 boot 方法中定義這種約束模式:

/**
 * 定義路由模型綁定,模式過濾器等
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 * @translator  http://laravelacademy.org
 */
public function boot()
{
    Route::pattern('id', '[0-9]+');
    parent::boot();
}

一旦模式被定義,將會自動應用到所有包含該參數名的路由中:

Route::get('user/{id}', function ($id) {
    // 只有當 {id} 是數字時纔會被調用
});

除此之外,該模式還會被應用到諸如下面這些路由參數上:

Route::get('post/{id}', function ($id) {
    // 只有當 {id} 是數字時纔會被調用
});

Route::get(`product/{id}', function ($id) {
    // 只有當 {id} 是數字時纔會被調用
});

很顯然這種方式讓代碼更簡潔,也爲我們實現同一參數統一約束帶來了方便。

編碼/

Laravel 路由組件支持除 / 之外的所有字符,如果要在佔位符中使用 / 需要通過 where 條件正則表達式顯式允許:

Route::get('search/{search}', function ($search) {
    return $search;
})->where('search', '.*');

注:只有最後一個路由參數片段中才支持編碼正斜槓/

命名路由

命名路由爲生成 URL 或重定向提供了方便,實現起來也很簡單,在路由定義之後使用 name 方法鏈的方式來定義該路由的名稱:

Route::get('user/profile', function () {
    // 通過路由名稱生成 URL
    return 'my url: ' . route('profile');
})->name('profile');

還可以爲控制器動作指定路由名稱:

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

這樣我們就可以通過以下方式定義重定向:

Route::get('redirect', function() {
    // 通過路由名稱進行重定向
    return redirect()->route('profile');
});

爲命名路由生成 URL

正如上面代碼所展示的,爲給定路由分配名稱之後,就可以通過輔助函數 route 爲該命名路由生成 URL 或者通過 redirect 函數進行重定向:

// 生成URL
$url = route('profile');

// 生成重定向
return redirect()->route('profile');

如果命名路由定義了參數,可以將該參數作爲第二個參數傳遞給 route 函數。給定的路由參數將會自動插入到 URL 中:

Route::get('user/{id}/profile', function ($id) {
    $url = route('profile', ['id' => 1]);
    return $url;
})->name('profile');

這樣,當我們訪問 http://blog.test/user/123/profile 頁面輸出內容也是 http://blog.test/user/123/profile

檢查當前路由

如果你想要判斷當前請求是否被路由到給定命名路由,可以使用 Route 實例上的 named 方法,例如,你可以從路由中間件中檢查當前路由名稱:

/**
 * 處理輸入請求
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @return mixed
 */
public function handle($request, Closure $next)
{
    if ($request->route()->named('profile')) {
        //
    }

    return $next($request);
}

路由分組

路由分組的目的是讓我們在多個路由中共享相同的路由屬性,比如中間件和命名空間等,這樣的話我們定義了大量的路由時就不必爲每一個路由單獨定義屬性。共享屬性以數組的形式作爲第一個參數被傳遞給 Route::group 方法。

嵌套的分組會嘗試智能地將屬性合併到父分組中,中間件和 where 條件會直接被合併,而路由命名、命名空間、以及路由前綴會被附加到父組件對應屬性之後。命名空間分隔符和 URI 中的斜槓會被自動添加到合適的位置。

中間件

要給某個路由分組中定義的所有路由分配中間件,可以在定義分組之前使用 middleware 方法。中間件將會按照數組中定義的順序依次執行:

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // Uses first & second Middleware
    });

    Route::get('user/profile', function () {
        // Uses first & second Middleware
    });
});

關於中間件的使用我們在後面單獨講中間件時再進行示例演示,這裏我們先了解這樣使用就行。

命名空間

路由分組另一個通用的例子是使用 namespace 方法分配同一個 PHP 命名空間給該分組下的多個控制器:

Route::namespace('Admin')->group(function () {
    // Controllers Within The "App\Http\Controllers\Admin" Namespace
});

默認情況下,RouteServiceProvider 在一個命名空間分組下引入所有路由文件,並指定所有控制器類所在的默認命名空間是 App\Http\Controllers,因此,我們在定義控制器的時候只需要指定命名空間 App\Http\Controllers 之後的部分即可。

子域名路由

路由分組還可以被用於處理子域名路由,子域名可以像 URI 一樣被分配給路由參數,從而允許捕獲子域名的部分用於路由或者控制器,子域名可以在定義分組之前調用 domain 方法來指定:

Route::domain('{account}.blog.dev')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        return 'This is ' . $account . ' page of User ' . $id;
    });
});

比如我們設置會員子域名爲 account.blog.test,那麼就可以通過 http://account.blog.test/user/1 訪問用戶ID爲 1 的會員信息了:

This is account page of User 1

路由前綴

prefix 方法可以用來爲分組中每個路由添加一個給定 URI 前綴,例如,你可以爲分組中所有路由 URI 添加 admin 前綴 :

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // Matches The "/admin/users" URL
    });
});

這樣我們就可以通過 http://blog.test/admin/users 訪問路由了。

路由名稱前綴

name 方法可通過傳入字符串爲分組中的每個路由名稱設置前綴,例如,你可能想要在所有分組路由的名稱前添加 admin 前綴,由於給定字符串和指定路由名稱前綴字符串完全一樣,所以需要在前綴字符串末尾後加上 . 字符:

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // 新的路由名稱爲 "admin.users"...
    })->name('users');
});

路由模型綁定

注入模型 ID 到路由或控制器動作時,通常需要查詢數據庫才能獲取相應的模型數據。Laravel 路由模型綁定讓注入模型實例到路由變得簡單,例如,你可以將匹配給定 ID 的整個 User 類實例注入到路由中,而不只是注入用戶 ID。

隱式綁定

Laravel 會自動解析定義在路由或控制器動作(變量名匹配路由片段)中的 Eloquent 模型類型聲明,例如(我們將這個路由定義在 routes/api.php 文件中):

Route::get('users/{user}', function (App\User $user) {
    return $user->email;
});

在這個例子中,由於類型聲明瞭 Eloquent 模型 App\User,對應的變量名 $user 會匹配路由片段中的 {user},這樣,Laravel 會自動注入與請求 URI 中傳入的 ID 對應的用戶模型實例。如果匹配模型實例在數據庫中不存在,會自動生成 404 響應。

在演示本功能之前,我們需要先創建數據表,由於我是在 Valet 開發環境中開發,需要自己創建數據庫,我們將數據庫命名爲 valet,本地的數據庫用戶名爲 root,密碼爲空,對應地,修改 .env 文件配置如下:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=valet
DB_USERNAME=root
DB_PASSWORD=

具體配置值以你自己的開發環境設置爲準。我們將基於 Laravel 強大的數據庫遷移功能創建 users 表,關於數據庫遷移後面在數據庫部分會詳細討論,這裏我們通過以下命令來生成 users 表即可:

php artisan migrate

進入數據庫可以看到該表已經生成:

這時,users 數據表還沒有任何記錄,如果數據庫中找不到對應的模型實例,會自動生成 HTTP 404 響應,提示頁面不存在,所以我們需要在這張表中插入一條記錄,這裏我們基於 Laravel 強大的數據庫填充器來快速完成數據填充功能,首先通過如下命令生成 users 對應的數據表填充器:

php artisan make:seeder UsersTableSeeder

該命令會在 database/seeds 目錄下生成一個 UsersTableSeeder 文件,編輯該文件內容如下:

然後編輯同目錄下的 DatabaseSeeder.php 文件如下(取消調用數據表填充器前的註釋):

最後執行 php artisan db:seed 即可插入對應數據到 users 表了,這樣我們在瀏覽器中再次訪問 http://blog.test/api/users/1 的時候就會顯示 User 模型數據了:

接下來,你就可以在應用代碼中直接拿 $user 模型去做你想做的事情了,而不需要自己去數據庫查詢,從而提高了開發的效率。

自定義鍵名

如果你想要在隱式模型綁定中使用數據表的其它字段而不是 id 字段,可以重寫 Eloquent 模型類的 getRouteKeyName 方法,以 User 模型爲例,可以在該模型類中添加這個方法 :

/**
 * Get the route key for the model.
 *
 * @return string
 */
public function getRouteKeyName()
{
    return 'name';
}

這樣我們就可以通過 http://blog.test/api/users/jroJoGP71W 訪問同一個模型實例了。這裏需要注意的點是如果該字段不是唯一鍵,則會返回結果集的第一條記錄,對應的底層實現在這裏:

顯式綁定

有隱式綁定,就有顯式綁定。要註冊顯式綁定,可以使用路由器的 model 方法來爲給定參數指定綁定類。你需要在 RouteServiceProvider 類的 boot 方法中定義顯式模型綁定:

public function boot()
{
    parent::boot();
    Route::model('user_model', App\User::class);
}

接下來,在 routes/api.php 中定義一個包含 {user} 參數的路由:

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

由於我們已經綁定 {user_model} 參數到 App\User 模型,User 實例會被注入到該路由。因此,如果請求 URL 是 http://blog.test/api/profile/1,就會注入一個用戶 ID 爲 1 的 User 實例。

如果匹配的模型實例在數據庫不存在,會自動生成並返回 HTTP 404 響應。

自定義解析邏輯

如果你想要使用自定義的解析邏輯,可以在 RouteServiceProvider 類的 boot 方法中使用 Route::bind 方法,傳遞到 bind 方法的閉包會獲取到 URI 請求參數中的值,並且返回你想要在該路由中注入的類實例:

public function boot()
{
    parent::boot();

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

此外,你還可以在 Eloquent 模型中覆蓋 resolveRouteBinding 方法,這個方法會獲取 URI 片段中的值並返回應該被注入的路由模型類實例:

/**
 * Retrieve the model for a bound value.
 *
 * @param  mixed  $value
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value)
{
    return $this->where('name', $value)->first() ?? abort(404);
}

兜底路由

使用 Route::fallback 方法可以定義一個當所有其他路由都未能匹配請求 URL 時所執行的路由。通常,未處理請求會通過 Laravel 的異常處理器自動渲染一個「404」頁面,不過,如果你在 routes/web.php 文件中定義了 fallback 路由的話,所有 web 中間件組中的路由都會應用此路由作爲兜底,當然,如果需要的話,你還可以添加額外的中間件到此路由:

Route::fallback(function () {
    //
});

注:兜底路由應該總是放到應用註冊的所有路由的最後。

頻率限制

Laravel 自帶了一個中間件用於限制對應用路由的訪問頻率。開始使用該功能之前,分配 throttle 中間件到某個路由或路由分組,throttle 中間件接收兩個參數用於判斷給定時間內(單位:分鐘)的最大請求次數。例如,我們指定登錄用戶每分鐘只能訪問下面的分組路由 60 次:

Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

超出訪問次數後,會返回 429 狀態碼並提示「Too many requests」。

動態頻率限制

此外,還可以基於 User 模型的屬性來動態設置最大請求次數。例如,如果 User 模型包含 rate_limit 屬性,就可以將其這個屬性名傳遞到 throttle 中間件,這樣就可以將屬性值作爲計算最大請求次數的數據來源:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

表單方法僞造

HTML 表單不支持 PUTPATCH 或者 DELETE 請求方法,因此,在 HTML 表單中調用 PUTPATCH 或 DELETE 路由時,需要添加一個隱藏的 _method 字段,其值被用作該表單的 HTTP 請求方法:

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

還可以直接使用 Blade 指令 @method 來生成 _method 字段:

<form action="/foo/bar" method="POST">
    @method('PUT')
    @csrf
</form>

訪問當前路由

你可以使用 Route 門面上的 currentcurrentRouteName 和 currentRouteAction 方法來訪問處理當前輸入請求的路由信息:

// 獲取當前路由實例
$route = Route::current(); 
// 獲取當前路由名稱
$name = Route::currentRouteName();
// 獲取當前路由action屬性
$action = Route::currentRouteAction();

參考 API 文檔瞭解路由門面底層類以及Route實例的更多可用方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章