路由

基礎路由

最基礎的 Laravel Route (路由) 就是接受一個 URI 與一個閉包,我們可以使用簡單直觀的方法來定義 Route 與其行為,而不需複雜 Route 設定檔:

1use Illuminate\Support\Facades\Route;
2 
3Route::get('/greeting', function () {
4 return 'Hello World';
5});
1use Illuminate\Support\Facades\Route;
2 
3Route::get('/greeting', function () {
4 return 'Hello World';
5});

預設的 Route 檔案

Laravel 中所有的 Route 都在 Route 檔案中定義,這些檔案位在 routes 目錄下。這些檔案會由專案中的 App\Providers\RouteServiceProvider 自動載入。routes/web.php 檔案中定義了網頁介面的 Route。這些 Route 被指派到 web Middleware 群組中,該 Middleware 群組提供了一些如 Session 狀態與 CSRF 保護等功能。routes/api.php 是無狀態的 (Stateless),裡面的 Route 會被指派給 api Middleware 群組。

對於大多數的程式來說,我們會在 routes/web.php 檔案中定義 Route。我們可以在瀏覽器中打開 Route 定義的 URL 來存取 routes/web.php 中定義的路由。舉例來說,我們可以在瀏覽器中打開 http://example.com/user 來存取下來路由:

1use App\Http\Controllers\UserController;
2 
3Route::get('/user', [UserController::class, 'index']);
1use App\Http\Controllers\UserController;
2 
3Route::get('/user', [UserController::class, 'index']);

routes/api.php 檔案中定義的 Route 放在巢狀放置在 RouteServiceProvider 中的 Route 群組內。在這個群組中,Laravel 會自動加上 /api URI 前置詞 (Prefix),因此在這個檔案中,我們不需手動在所有 Route 前方加上 /api。我們也可以修改 RouteServiceProvider 類別來修改這個前置詞以及其他一些 Route 群組的選項。

可用的 Router 方法

使用 Router 就能讓我們註冊能回應任何 HTTP 動詞的 Route:

1Route::get($uri, $callback);
2Route::post($uri, $callback);
3Route::put($uri, $callback);
4Route::patch($uri, $callback);
5Route::delete($uri, $callback);
6Route::options($uri, $callback);
1Route::get($uri, $callback);
2Route::post($uri, $callback);
3Route::put($uri, $callback);
4Route::patch($uri, $callback);
5Route::delete($uri, $callback);
6Route::options($uri, $callback);

有時候,我們可能需要註冊一個能回應多個 HTTP 動詞的 Route。這時可以使用 match 方法。或者,我們甚至可以使用 any 方法來註冊一個回應所有 HTTP 動詞的 Route:

1Route::match(['get', 'post'], '/', function () {
2 //
3});
4 
5Route::any('/', function () {
6 //
7});
1Route::match(['get', 'post'], '/', function () {
2 //
3});
4 
5Route::any('/', function () {
6 //
7});
lightbulb

註冊多個共享同 URI 的 Route 時,應將這些 any, match, 與 redirect 方法的 Route 定義在 get, post, put, patch, delete, 與 options 方法定義之前。這樣一來可以確保連入的 Request 被配對到正確的 Route 上。

相依性插入

可以在 Route 的回呼簽章 (Signature) 上型別提示 (Type-Hint) 任何 Route 所需的相依性。Laravel 的 Service Container 會自動解析並插入所定義的相依性。舉例來說,我們可以型別提示 Illuminate\Http\Request 並自動插入到 Route 回呼中,該類別代表目前的 HTTP Request:

1use Illuminate\Http\Request;
2 
3Route::get('/users', function (Request $request) {
4 // ...
5});
1use Illuminate\Http\Request;
2 
3Route::get('/users', function (Request $request) {
4 // ...
5});

CSRF 保護

請記得,當 HTML 表單指向 web Route 檔的 POST, PUT, PATCH, 與 DELETE Route 時,都應包含一個 CSRF 權杖欄位。若未包含權杖欄位,則該 Request 會被拒絕。更多有關 CSRF 保護的資訊可以參考 CSRF 說明文件

1<form method="POST" action="/profile">
2 @csrf
3 ...
4</form>
1<form method="POST" action="/profile">
2 @csrf
3 ...
4</form>

重新導向的 Route

若想定義可以重新導向到另一個 URI 的 Route,可以使用 Route::redirect 方法。這個方法提供了一個方便的捷徑,讓你不需要為了簡單的重新導向定義完整的 Route 或 Controller:

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

預設情況下,Route::redirect 回傳 302 狀態碼。我們可以使用可選的第三個參數來自訂狀態碼:

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

或者,我們也可以使用 Route::permanentRedirect 方法來回傳 301 狀態碼:

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

在重新導向 Route 中使用 Route 參數時,有幾個參數名稱是 Laravel 的保留字,無法使用:destinationstatus

View 的 Route

若某個 Route 只需要回傳一個 View,則可以使用 Route::view 方法。與 redirect 方法類似,這個方法提供了一個簡單的捷徑,能讓我們不需定義完整的 Route 或 Controller。view 方法接受一個 URI 作為其第一個引數,而第二個引數則是 View 的名稱。此外,也可以提供一組陣列,其中包含要傳給 View 的資料,並作為可選的第三個引數傳入:

1Route::view('/welcome', 'welcome');
2 
3Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
1Route::view('/welcome', 'welcome');
2 
3Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
exclamation

在 View 的 Route 中使用 Route 參數時,有幾個參數名稱是 Laravel 的保留字,無法使用:viewdatastatusheader

Route 列表

使用 route:list Artisan 指令就可輕鬆檢視專案中定義的所有 Route 一覽:

1php artisan route:list
1php artisan route:list

預設情況下,指派給各個 Route 的 Middleware 不會顯示在 route:list 輸出中。不過,我們可以在該指令後加上 -v 選項來讓 Laravel 顯示 Route Middleware:

1php artisan route:list -v
1php artisan route:list -v

也可以讓 Laravel 值顯示以給定 URI 開頭的 Route:

1php artisan route:list --path=api
1php artisan route:list --path=api

此外,也可以在執行 route:list 指令時提供 --except-vendor 選項來讓 Laravel 隱藏由第三方套件所定義的 Route:

1php artisan route:list --except-vendor
1php artisan route:list --except-vendor

類似地,執行 route:list 指令時,也可以提供 --only-vendor 選項來讓 Laravel 只顯示第三方套件定義的 Route:

1php artisan route:list --only-vendor
1php artisan route:list --only-vendor

Route 參數

必填參數

在 Route 中,有時候我們會想從 URI 中擷取一個片段。舉例來說,我們可能會需要從 URI 中擷取出使用者的 ID。為此,我們可以定義 Route 參數:

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

根據 Route 的需求,我們可以定義不限數量的 Route 參數:

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

Route 參數必須要包裝在 {} 大括號中,且只能使用字母。在 Route 參數名稱中也可以使用 (_)。Route 參數會依照順序插入到 Route 的回呼或 Controller 上 —— Route 的回呼或 Controller 中的名稱並不影響。

參數與相依性插入

若你的 Route 有使用讓 Laravel Service Container 自動插入到 Route 回呼的相依性的話,請將 Route 參數列在相依性之後:

1use Illuminate\Http\Request;
2 
3Route::get('/user/{id}', function (Request $request, $id) {
4 return 'User '.$id;
5});
1use Illuminate\Http\Request;
2 
3Route::get('/user/{id}', function (Request $request, $id) {
4 return 'User '.$id;
5});

可選的參數

有時候,我們可能會讓某個 Route 參數不需要出現在每個 URI 上。為此,我們可以在參數名稱後方放置一個 ? 符號。請先確定這個 Route 中對應的變數有預設值:

1Route::get('/user/{name?}', function ($name = null) {
2 return $name;
3});
4 
5Route::get('/user/{name?}', function ($name = 'John') {
6 return $name;
7});
1Route::get('/user/{name?}', function ($name = null) {
2 return $name;
3});
4 
5Route::get('/user/{name?}', function ($name = 'John') {
6 return $name;
7});

正規表示式條件

可以在 Route 實體上使用 where 方法來規定 Route 參數的格式。where 方法接受一個參數名稱、以及一個用來規範參數格式的正規表示式:

1Route::get('/user/{name}', function ($name) {
2 //
3})->where('name', '[A-Za-z]+');
4 
5Route::get('/user/{id}', function ($id) {
6 //
7})->where('id', '[0-9]+');
8 
9Route::get('/user/{id}/{name}', function ($id, $name) {
10 //
11})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
1Route::get('/user/{name}', function ($name) {
2 //
3})->where('name', '[A-Za-z]+');
4 
5Route::get('/user/{id}', function ($id) {
6 //
7})->where('id', '[0-9]+');
8 
9Route::get('/user/{id}/{name}', function ($id, $name) {
10 //
11})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

為了方便起見,一些常用的正規式都有輔助方法,可以讓你快速將這些格式套用到 Route 上:

1Route::get('/user/{id}/{name}', function ($id, $name) {
2 //
3})->whereNumber('id')->whereAlpha('name');
4 
5Route::get('/user/{name}', function ($name) {
6 //
7})->whereAlphaNumeric('name');
8 
9Route::get('/user/{id}', function ($id) {
10 //
11})->whereUuid('id');
12 
13Route::get('/user/{id}', function ($id) {
14 //
15})->whereUlid('id');
16 
17Route::get('/category/{category}', function ($category) {
18 //
19})->whereIn('category', ['movie', 'song', 'painting']);
1Route::get('/user/{id}/{name}', function ($id, $name) {
2 //
3})->whereNumber('id')->whereAlpha('name');
4 
5Route::get('/user/{name}', function ($name) {
6 //
7})->whereAlphaNumeric('name');
8 
9Route::get('/user/{id}', function ($id) {
10 //
11})->whereUuid('id');
12 
13Route::get('/user/{id}', function ($id) {
14 //
15})->whereUlid('id');
16 
17Route::get('/category/{category}', function ($category) {
18 //
19})->whereIn('category', ['movie', 'song', 'painting']);

若連入 Request 不符合 Route 的格式限制,則會回傳 404 HTTP Response。

全域條件限制

若想以某個正規式規範所有相同的 Route 參數,可以使用 pattern 方法。可以在專案的 App\Providers\RouteServiceProvider 類別中 boot 方法內定義這些格式:

1/**
2 * Define your route model bindings, pattern filters, etc.
3 *
4 * @return void
5 */
6public function boot()
7{
8 Route::pattern('id', '[0-9]+');
9}
1/**
2 * Define your route model bindings, pattern filters, etc.
3 *
4 * @return void
5 */
6public function boot()
7{
8 Route::pattern('id', '[0-9]+');
9}

定義好之後,這個規則會自動套用到有使用這個參數名稱的 Route:

1Route::get('/user/{id}', function ($id) {
2 // 只會在 {id} 為數字時執行...
3});
1Route::get('/user/{id}', function ($id) {
2 // 只會在 {id} 為數字時執行...
3});

編碼斜線

Laravel 的路由元件能接受除了 / 外的所有字元出現在 Route 的參數值內。請使用 where 正規表示式條件來顯式允許 / 出現在預留位置中:

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

只有最後一個 Route 片段才支援編碼斜線。

命名的 Route

命名 Route 可以方便地未特定 Route 產生 URL 或重新導向。我們可以通過在 Route 定義後方串上 name 方法來為 Route 指定名稱:

1Route::get('/user/profile', function () {
2 //
3})->name('profile');
1Route::get('/user/profile', function () {
2 //
3})->name('profile');

也可以為 Controller 動作指定 Route 名稱:

1Route::get(
2 '/user/profile',
3 [UserProfileController::class, 'show']
4)->name('profile');
1Route::get(
2 '/user/profile',
3 [UserProfileController::class, 'show']
4)->name('profile');
exclamation

Route 名稱不可重複。

產生命名 Route 的 URL

給某個 Route 指定好名稱後,我們就可以使用 Laravel 的 routeredirect 輔助函式來在產生 URL 或重新導向時使用 Route 的名稱:

1// 產生 URL...
2$url = route('profile');
3 
4// 產生重新導向...
5return redirect()->route('profile');
6 
7return to_route('profile');
1// 產生 URL...
2$url = route('profile');
3 
4// 產生重新導向...
5return redirect()->route('profile');
6 
7return to_route('profile');

若命名 Route 有定義參數,則可以將這些參數作為第二個引數傳給 route 函式。傳入的參數會自動依照正確位置插入到產生的 URL 裡:

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

若該陣列中有傳入額外的參數,則這些額外的索引鍵 / 值配對會自動被插入到產生的 URL 中之查詢字串 (Query String) 上:

1Route::get('/user/{id}/profile', function ($id) {
2 //
3})->name('profile');
4 
5$url = route('profile', ['id' => 1, 'photos' => 'yes']);
6 
7// /user/1/profile?photos=yes
1Route::get('/user/{id}/profile', function ($id) {
2 //
3})->name('profile');
4 
5$url = route('profile', ['id' => 1, 'photos' => 'yes']);
6 
7// /user/1/profile?photos=yes
lightbulb

有時候,我們可能會想為 URL 引數指定 Request 層級的預設值,例如目前使用的語系等。為此,可以使用 URL::defaults 方法

檢查目前 Route

若想判斷目前的 Request 是否有被路由到給定的命名 Route 上,可以使用 Route 實體上的 named 方法。舉例來說,我們可以從某個 Route 的 Middleware 上檢查目前的 Route 名稱:

1/**
2 * Handle an incoming request.
3 *
4 * @param \Illuminate\Http\Request $request
5 * @param \Closure $next
6 * @return mixed
7 */
8public function handle($request, Closure $next)
9{
10 if ($request->route()->named('profile')) {
11 //
12 }
13 
14 return $next($request);
15}
1/**
2 * Handle an incoming request.
3 *
4 * @param \Illuminate\Http\Request $request
5 * @param \Closure $next
6 * @return mixed
7 */
8public function handle($request, Closure $next)
9{
10 if ($request->route()->named('profile')) {
11 //
12 }
13 
14 return $next($request);
15}

Route 群組

使用 Route 群組,我們就可以在多個 Route 間共享相同的 Route 參數(如:使用相同的 Middleware),而不需要手動在個別 Route 上定義這些參數。

巢狀群組會嘗試智慧地將屬性「合併」到上層群組中。Middleware 與 where 條件會被合併,而命名 Route 的名稱則會被作為前置詞放到前面。Laravel 會自動在適當的時候往 URI 前方插入 Namespace 分隔符號或斜線。

Middleware

若要將 Middleware 設定給群組中的所有 Route,可以在定義群組前使用 middleware 方法。Middleware 會以陣列中列出的順序執行:

1Route::middleware(['first', 'second'])->group(function () {
2 Route::get('/', function () {
3 // 使用 first 與 second Middleware...
4 });
5 
6 Route::get('/user/profile', function () {
7 // 使用 first 與 second Middleware...
8 });
9});
1Route::middleware(['first', 'second'])->group(function () {
2 Route::get('/', function () {
3 // 使用 first 與 second Middleware...
4 });
5 
6 Route::get('/user/profile', function () {
7 // 使用 first 與 second Middleware...
8 });
9});

Controller

若有一組 Route 全部都使用了相同的 Controller,則我們可以使用 controller 方法來在路由群組中為所有的路由定義通用的 Controller。定義好之後,當定義路由時,就只需要提供要叫用的 Controller 方法即可:

1use App\Http\Controllers\OrderController;
2 
3Route::controller(OrderController::class)->group(function () {
4 Route::get('/orders/{id}', 'show');
5 Route::post('/orders', 'store');
6});
1use App\Http\Controllers\OrderController;
2 
3Route::controller(OrderController::class)->group(function () {
4 Route::get('/orders/{id}', 'show');
5 Route::post('/orders', 'store');
6});

子網域路由

Route 群組也可以用來處理子網域路由。我們可以像在設定 Route URI 一樣,在Route 參數內指派子網域。這樣一來我們就可以在 Route 或 Controller 內取得子網域的部分。可以通過在定義群組前呼叫 domain 來指定子網域:

1Route::domain('{account}.example.com')->group(function () {
2 Route::get('user/{id}', function ($account, $id) {
3 //
4 });
5});
1Route::domain('{account}.example.com')->group(function () {
2 Route::get('user/{id}', function ($account, $id) {
3 //
4 });
5});
exclamation

為了確保子網域 Route 有效,請在註冊任何根網域 Route 前先註冊子網域 Route。這樣可以避免根網域的 Route 去複寫到子網域 Route 中有相同 URI 路徑的 Route。

Route 前置詞

可以使用 prefix 方法來為群組中的每個 Route 都加上給定 URI 的前置詞。舉例來說,我們可能會想把某個群組中的所有 Route URI 都加上 admin 前置詞:

1Route::prefix('admin')->group(function () {
2 Route::get('/users', function () {
3 // 配對到「/admin/users」URL
4 });
5});
1Route::prefix('admin')->group(function () {
2 Route::get('/users', function () {
3 // 配對到「/admin/users」URL
4 });
5});

命名 Route 的名稱前置詞

可以使用 name 方法來給群組中的每個 Route 名稱都加上給定字串的前置詞。舉例來說,我們可能會想給群組 Route 中的所有 Route 名稱都加上 admin 前置詞。給定字串會直接以指定時的樣子被加到 Route 名稱上。因此,請確保有在前置詞後方加上 . 字元:

1Route::name('admin.')->group(function () {
2 Route::get('/users', function () {
3 // Route 被指派名稱為「admin.users」...
4 })->name('users');
5});
1Route::name('admin.')->group(function () {
2 Route::get('/users', function () {
3 // Route 被指派名稱為「admin.users」...
4 })->name('users');
5});

Route 的 Model 繫結

在將 Model ID 插入到 Route 或 Controller 動作時,我們常常會需要查詢資料庫來取得相應於該 ID 的 Model。Laravel 的 Route Model 繫結提供了能自動將 Model 實體插入到 Route 中的方便方法。舉例來說,我們可以插入符合給定 ID 的整個 User Model 實體,而不是插入使用者的 ID。

隱式繫結

當 Route 或 Controller 動作中定義的變數名稱符合某個 Route 片段名稱,且該變數有型別提示時,Laravel 會自動解析 Eloquent Model。舉例來說:

1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 return $user->email;
5});
1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 return $user->email;
5});

由於 $user 變數有型別提示為 App\Models\User Eloquent Model,且該變數名稱符合 {user} URI 片段,因此 Laravel 會自動將 ID 符合 Request URI 中相應值的 Model 實體插入進去。若資料庫中找不到對應的 Model 實體,則會自動產生 404 HTTP Response。

當然,在使用 Controller 方法時也能使用隱式繫結。再強調一次,必須注意 {user} URI 片段要符合 Controller 中有 App\Models\User 型別提示的 $user 變數:

1use App\Http\Controllers\UserController;
2use App\Models\User;
3 
4// Route 定義...
5Route::get('/users/{user}', [UserController::class, 'show']);
6 
7// Controller 方法定義...
8public function show(User $user)
9{
10 return view('user.profile', ['user' => $user]);
11}
1use App\Http\Controllers\UserController;
2use App\Models\User;
3 
4// Route 定義...
5Route::get('/users/{user}', [UserController::class, 'show']);
6 
7// Controller 方法定義...
8public function show(User $user)
9{
10 return view('user.profile', ['user' => $user]);
11}

軟刪除的 Model

一般來說,隱式型別細節不會去的被軟刪除的 Model。不過,我們也可以在 Route 的定義後方串上 withTrashed 方法來讓隱式型別綁定取得這些 Model:

1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 return $user->email;
5})->withTrashed();
1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 return $user->email;
5})->withTrashed();

自訂索引鍵

有時候,我們可能會像讓 Eloquent 解析 id 以外的其他欄位。為此,可以在 Route 的參數定義中指定這個欄位:

1use App\Models\Post;
2 
3Route::get('/posts/{post:slug}', function (Post $post) {
4 return $post;
5});
1use App\Models\Post;
2 
3Route::get('/posts/{post:slug}', function (Post $post) {
4 return $post;
5});

若想讓 Model 繫結在給定 Model 類別上總是使用 id 以外的其他欄位,可以在 Eloquent Model 上複寫 getRouteKeyName 方法:

1/**
2 * Get the route key for the model.
3 *
4 * @return string
5 */
6public function getRouteKeyName()
7{
8 return 'slug';
9}
1/**
2 * Get the route key for the model.
3 *
4 * @return string
5 */
6public function getRouteKeyName()
7{
8 return 'slug';
9}

自訂索引鍵與作用範圍

當我們在單一 Route 定義中隱式繫結多個 Eloquent Model 時,我們可以限定第二個 Eloquent Model 一定要是前一個 Eloquent Model 的子 Model。舉例來說,假設有下列這樣通過 Slug 取得特定使用者的部落格貼文的 Route 定義:

1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
5 return $post;
6});
1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
5 return $post;
6});

當使用自訂鍵值的隱式繫結作為巢狀路由參數時,Laravel 會自動以慣例推測其上層 Model 上的關聯名稱來將限制巢狀 Model 的查詢範圍。在這個例子中,Laravel 會假設 User Model 有個名為 posts 的關聯 (即路由參數名稱的複數形),該關聯將用於取得 Post Model。

若有需要的話,就算沒有提供自訂索引鍵,我們還是可以告訴 Laravel 要如何限定「子」繫結的限定。為此,我們可以在定義 Route 時叫用 scopeBindings 方法:

1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
5 return $post;
6})->scopeBindings();
1use App\Models\Post;
2use App\Models\User;
3 
4Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
5 return $post;
6})->scopeBindings();

或者,也可以讓整個 Route 定義群組使用限定範圍的繫結:

1Route::scopeBindings()->group(function () {
2 Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
3 return $post;
4 });
5});
1Route::scopeBindings()->group(function () {
2 Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
3 return $post;
4 });
5});

類似地,也可以通過呼叫 withoutScopedBindings 方法來明顯讓 Laravel 不使用限定範圍的繫結:

1Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
2 return $post;
3})->withoutScopedBindings();
1Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
2 return $post;
3})->withoutScopedBindings();

自訂找不到 Model 的行為

通常來說,若找不到隱式繫結的 Model 時會產生一個 404 HTTP 回應。不過,可以在定義 Route 時呼叫 missing 方法來自訂這個行為。missing 方法接受一個閉包,該閉包會在找不到隱式繫結的 Model 時被叫用:

1use App\Http\Controllers\LocationsController;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\Redirect;
4 
5Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
6 ->name('locations.view')
7 ->missing(function (Request $request) {
8 return Redirect::route('locations.index');
9 });
1use App\Http\Controllers\LocationsController;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\Redirect;
4 
5Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
6 ->name('locations.view')
7 ->missing(function (Request $request) {
8 return Redirect::route('locations.index');
9 });

隱式 Enum 繫結

PHP 8.1 新增了對 Enum 的支援。為了配合這個功能,Laravel 中提供了能在 Route 定義中對 String-Backed Enum 進行型別提示的功能。加上型別提示後,只有當網址中的相應的 Route 片段為有效的 Enum 時,Laravel 才會叫用該 Route。若不是有效的 Enum 值,則會自動回傳 404 HTTP Response。舉例來說,假設有下列 Enum:

1<?php
2 
3namespace App\Enums;
4 
5enum Category: string
6{
7 case Fruits = 'fruits';
8 case People = 'people';
9}
1<?php
2 
3namespace App\Enums;
4 
5enum Category: string
6{
7 case Fruits = 'fruits';
8 case People = 'people';
9}

我們可以定義一個只有當 {category} 路由片段為 fruitspeople 時才會被叫用的路由。若為其他值,Laravel 會回傳 HTTP 404 Response:

1use App\Enums\Category;
2use Illuminate\Support\Facades\Route;
3 
4Route::get('/categories/{category}', function (Category $category) {
5 return $category->value;
6});
1use App\Enums\Category;
2use Illuminate\Support\Facades\Route;
3 
4Route::get('/categories/{category}', function (Category $category) {
5 return $category->value;
6});

顯式繫結

Model 繫結不一定要使用 Laravel 的隱式的,隱式繫結是基於慣例的 Model 解析。我們也可以顯式定義 Route 參數要怎麼對應到 Model。若要註冊顯式細節,請使用 Router 的 model 方法來為給定參數指定類別。應在 RouteServiceProvider 類別中 boot 方法內的開頭定義顯式 Model 繫結:

1use App\Models\User;
2use Illuminate\Support\Facades\Route;
3 
4/**
5 * Define your route model bindings, pattern filters, etc.
6 *
7 * @return void
8 */
9public function boot()
10{
11 Route::model('user', User::class);
12 
13 // ...
14}
1use App\Models\User;
2use Illuminate\Support\Facades\Route;
3 
4/**
5 * Define your route model bindings, pattern filters, etc.
6 *
7 * @return void
8 */
9public function boot()
10{
11 Route::model('user', User::class);
12 
13 // ...
14}

接著,請定義含有 {user} 參數的 Route:

1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 //
5});
1use App\Models\User;
2 
3Route::get('/users/{user}', function (User $user) {
4 //
5});

我們已經將所有 {user} 參數繫結到 App\Models\User Model 上了。User Model 的實體會被插入到這個 Route 中。因此,舉例來說,對 users/1 的 Request 將會插入一個資料庫中 ID 為 1User 實體。

若資料庫中找不到相符合的 Model 實體,則會自動產生 404 HTTP Response。

自訂解析邏輯

若想定義你自己的 Model 繫結解析邏輯,則可以使用 Route::bind 方法。我們可以傳入一個閉包給 bind 方法,用來接受 URI 片段中的值,並回應要插入到 Route 中的類別實體。同樣的,這個自訂邏輯應放在專案的 RouteServiceProviderboot 方法內:

1use App\Models\User;
2use Illuminate\Support\Facades\Route;
3 
4/**
5 * Define your route model bindings, pattern filters, etc.
6 *
7 * @return void
8 */
9public function boot()
10{
11 Route::bind('user', function ($value) {
12 return User::where('name', $value)->firstOrFail();
13 });
14 
15 // ...
16}
1use App\Models\User;
2use Illuminate\Support\Facades\Route;
3 
4/**
5 * Define your route model bindings, pattern filters, etc.
6 *
7 * @return void
8 */
9public function boot()
10{
11 Route::bind('user', function ($value) {
12 return User::where('name', $value)->firstOrFail();
13 });
14 
15 // ...
16}

或者,我們也可以在 Eloquent Model 上複寫 resolveRouteBinding 方法。這個方法會接收 URI 片段中的值,並應回傳要插入到 Route 中的類別實體:

1/**
2 * Retrieve the model for a bound value.
3 *
4 * @param mixed $value
5 * @param string|null $field
6 * @return \Illuminate\Database\Eloquent\Model|null
7 */
8public function resolveRouteBinding($value, $field = null)
9{
10 return $this->where('name', $value)->firstOrFail();
11}
1/**
2 * Retrieve the model for a bound value.
3 *
4 * @param mixed $value
5 * @param string|null $field
6 * @return \Illuminate\Database\Eloquent\Model|null
7 */
8public function resolveRouteBinding($value, $field = null)
9{
10 return $this->where('name', $value)->firstOrFail();
11}

如有 Route 是使用限定範圍的隱式細節,則在解析上層 Model 的子繫結時會使用 resolveChildRouteBinding 方法:

1/**
2 * Retrieve the child model for a bound value.
3 *
4 * @param string $childType
5 * @param mixed $value
6 * @param string|null $field
7 * @return \Illuminate\Database\Eloquent\Model|null
8 */
9public function resolveChildRouteBinding($childType, $value, $field)
10{
11 return parent::resolveChildRouteBinding($childType, $value, $field);
12}
1/**
2 * Retrieve the child model for a bound value.
3 *
4 * @param string $childType
5 * @param mixed $value
6 * @param string|null $field
7 * @return \Illuminate\Database\Eloquent\Model|null
8 */
9public function resolveChildRouteBinding($childType, $value, $field)
10{
11 return parent::resolveChildRouteBinding($childType, $value, $field);
12}

遞補的 Route

使用 Route::fallback 方法,就可以定義當沒有其他 Route 符合連入 Request 時要執行的 Route。一般來說,專案中的例外處理常式會自動幫未處理的 Request 會轉譯出「404」頁面。不過,因為我們通常會在 routes/web.php 檔案中定義 fallback Route,因此在 web Middleware 群組中的所有 Middleware 也會被套用到該 Route 中。有需要的話也可以為這個 Route 定義額外的 Middleware:

1Route::fallback(function () {
2 //
3});
1Route::fallback(function () {
2 //
3});
exclamation

遞補的 Route 應該要保持為專案中最後一個註冊的 Route。

頻率限制

定義 Rate Limiter (頻率限制程式)

Laravel 中包含了強大且可客製化的頻率限制服務,可以用來為給定的 Route 或 Route 群組限制流量。要開始使用頻率限制,我們需要先依照專案需求定義 Rate Limiter (頻率限制程式) 的設定。一般來說,應在 App\Providers\RouteServiceProvider 類別的 configureRateLimiting 方法中定義。

使用 RateLimiter Facade 的 for 方法來定義 Rate Limiter。for 方法接受 Rate Limiter 的名稱以及一個閉包。該閉包應回傳用來套用到指派了這個 Rate Limiter 上的 Route 所需要的頻率限制設定。頻率限制的設定使用 Illuminate\Cache\RateLimiting\Limit 類別的實體。這個實體中包含了實用的「建構程式 (Builder)」,可讓你快速定義限制。Rate Limiter 的名稱可以為任意字串:

1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\RateLimiter;
4 
5/**
6 * Configure the rate limiters for the application.
7 *
8 * @return void
9 */
10protected function configureRateLimiting()
11{
12 RateLimiter::for('global', function (Request $request) {
13 return Limit::perMinute(1000);
14 });
15}
1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\RateLimiter;
4 
5/**
6 * Configure the rate limiters for the application.
7 *
8 * @return void
9 */
10protected function configureRateLimiting()
11{
12 RateLimiter::for('global', function (Request $request) {
13 return Limit::perMinute(1000);
14 });
15}

若連入的 Request 超過了指定的頻率限制,Laravel 會自動回傳一個 429 HTTP 狀態碼。若想自訂頻率限制回傳的 Response,可使用 response 方法:

1RateLimiter::for('global', function (Request $request) {
2 return Limit::perMinute(1000)->response(function (Request $request, array $headers) {
3 return response('Custom response...', 429, $headers);
4 });
5});
1RateLimiter::for('global', function (Request $request) {
2 return Limit::perMinute(1000)->response(function (Request $request, array $headers) {
3 return response('Custom response...', 429, $headers);
4 });
5});

由於頻率限制程式的回呼會接收連入 HTTP Request 實體,因此我們可以依據連入 Request 或登入使用者來動態調整適當的頻率限制:

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100);
5});
1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100);
5});

區塊化的頻率限制

有時候,我們可能會像依照某個值來做分區的頻率限制。舉例來說,我們可能會想讓某個使用者在每個 IP 位址上每分鐘只能存取某個 Route 100 次。為此,可以在設定頻率限制時使用 by 方法:

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100)->by($request->ip());
5});
1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()->vipCustomer()
3 ? Limit::none()
4 : Limit::perMinute(100)->by($request->ip());
5});

我們來看看另一個使用這個功能的例子。我們可以像這樣限制某個 Route 對已登入使用者的限制時 100 次/分鐘,而未登入使用者則是 10 次/分鐘:

1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()
3 ? Limit::perMinute(100)->by($request->user()->id)
4 : Limit::perMinute(10)->by($request->ip());
5});
1RateLimiter::for('uploads', function (Request $request) {
2 return $request->user()
3 ? Limit::perMinute(100)->by($request->user()->id)
4 : Limit::perMinute(10)->by($request->ip());
5});

多個頻率限制

當然,對於某個 Rate Limiter 的設定,我們也可以回傳一組包含頻率限制的陣列。每個頻率限制會依據陣列中的順序被套用在 Route 上:

1RateLimiter::for('login', function (Request $request) {
2 return [
3 Limit::perMinute(500),
4 Limit::perMinute(3)->by($request->input('email')),
5 ];
6});
1RateLimiter::for('login', function (Request $request) {
2 return [
3 Limit::perMinute(500),
4 Limit::perMinute(3)->by($request->input('email')),
5 ];
6});

將 Rate Limiter 附加到 Route 上

可以使用 throttle Middleware 來將 Rate Limiter 附加到 Route 或 Route 群組上。這個 Throttle Middleware 接受欲指派給 Route 的 Rate Limiter 名稱:

1Route::middleware(['throttle:uploads'])->group(function () {
2 Route::post('/audio', function () {
3 //
4 });
5 
6 Route::post('/video', function () {
7 //
8 });
9});
1Route::middleware(['throttle:uploads'])->group(function () {
2 Route::post('/audio', function () {
3 //
4 });
5 
6 Route::post('/video', function () {
7 //
8 });
9});

使用 Redis 來做頻率限制

一般來說,throttle Middleware 被映射到 Illuminate\Routing\Middleware\ThrottleRequests 類別。這個映射定義在程式的 HTTP Kernel (App\Http\Kernel) 中。不過,如果你使用 Redis 來作為快取的 Driver,則可以將這個映射改為使用 Illuminate\Routing\Middleware\ThrottleRequestsWithRedis 類別。這個類別能更有效率地使用 Redis 來管理頻率限制:

1'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
1'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,

表單方法的變更

HTML 表單不支援 PUT, PATCH, 與 DELETE 動作,因此,當我們在定義會由 HTML 表單呼叫的 PUT, PATCH, 或 DELETE Route 時,我們需要在表單內加上一個隱藏的 _method 欄位。包含在 _method 欄位裡的值會被當作 HTTP Request 方法使用:

1<form action="/example" method="POST">
2 <input type="hidden" name="_method" value="PUT">
3 <input type="hidden" name="_token" value="{{ csrf_token() }}">
4</form>
1<form action="/example" method="POST">
2 <input type="hidden" name="_method" value="PUT">
3 <input type="hidden" name="_token" value="{{ csrf_token() }}">
4</form>

為了方便起見,也可以使用 @method Blade 指示詞來產生 _method 輸入欄位:

1<form action="/example" method="POST">
2 @method('PUT')
3 @csrf
4</form>
1<form action="/example" method="POST">
2 @method('PUT')
3 @csrf
4</form>

存取目前的 Route

可以使用 Route Facade 上的 current, currentRouteNamecurrentRouteAction 方法來存取有關處理本次連入 Request 的 Route 資訊:

1use Illuminate\Support\Facades\Route;
2 
3$route = Route::current(); // Illuminate\Routing\Route
4$name = Route::currentRouteName(); // string
5$action = Route::currentRouteAction(); // string
1use Illuminate\Support\Facades\Route;
2 
3$route = Route::current(); // Illuminate\Routing\Route
4$name = Route::currentRouteName(); // string
5$action = Route::currentRouteAction(); // string

請參考 Route Facade 底層的類別Route 實體的 API 說明文件以瞭解 Router 與 Route 類別提供的全部方法。

跨原始來源資源共用 (CORS, Cross-Origin Resource Sharing)

Laravel 會自動依照你設定的值來回應 CORS 的 OPTIONS HTTP Request。可以在專案的 config/cors.php 設定檔中設定所有的 CORS 設定。 HandleCors Middleware 會自動處理 OPTIONS Request,該 Middleware 預設包含在全域的 Middleware Stack 中。全域的 Middleware Stack 存在 HTTP Kernel (App\Http\Kernel) 中。

lightbulb

更多有關 CORS 與 CORS 標頭的資訊,請參考 MDN 網頁說明文件上的 CORS

Route 的快取

在將專案部署到正式環境時,應使用 Laravel 的 Route 快取功能。使用 Route 快取就能大大地降低註冊所有 Route 所需的時間。要產生 Route 快取,請執行 route:cache Artisan 指令:

1php artisan route:cache
1php artisan route:cache

執行這個指令後,每個 Request 都會自動載入快取的 Route 檔。請記得,當新增新 Route 後,必須重新產生 Route 快取。因此,應在進行專案部署的時候才執行 route:cache 指令。

可以使用 route:clear 指令來清除 Route 快取:

1php artisan route:clear
1php artisan route:clear
翻譯進度
100% 已翻譯
更新時間:
2023年2月11日 上午10:28:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.