路由
- 基礎路由
- Route 參數
- 命名 Route
- Route 群組
- Route 的 Model 繫結
- 遞補 Route
- 頻率限制
- Form Method 的模擬
- 存取目前 Route
- [跨原始來源資源共用 (CORS, Cross-Origin Resource Sharing)(#cors)
- Route 快取
基礎路由
最基礎的 Laravel Route (路由) 就是接受一個 URI 與一個閉包,我們可以使用簡單直觀的方法來定義 Route 與其行為,而不需複雜 Route 設定檔:
1use Illuminate\Support\Facades\Route;23Route::get('/greeting', function () {4 return 'Hello World';5});
1use Illuminate\Support\Facades\Route;23Route::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;23Route::get('/user', [UserController::class, 'index']);
1use App\Http\Controllers\UserController;23Route::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});45Route::any('/', function () {6 // ...7});
1Route::match(['get', 'post'], '/', function () {2 // ...3});45Route::any('/', function () {6 // ...7});
註冊多個共享同 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;23Route::get('/users', function (Request $request) {4 // ...5});
1use Illuminate\Http\Request;23Route::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 @csrf3 ...4</form>
1<form method="POST" action="/profile">2 @csrf3 ...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');
在重新導向 Route 中使用 Route 參數時,有幾個參數名稱是 Laravel 的保留字,無法使用:destination
與 status
。
View 的 Route
若某個 Route 只需要回傳一個 View,則可以使用 Route::view
方法。與 redirect
方法類似,這個方法提供了一個簡單的捷徑,能讓我們不需定義完整的 Route 或 Controller。view
方法接受一個 URI 作為其第一個引數,而第二個引數則是 View 的名稱。此外,也可以提供一組陣列,其中包含要傳給 View 的資料,並作為可選的第三個引數傳入:
1Route::view('/welcome', 'welcome');23Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
1Route::view('/welcome', 'welcome');23Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
在 View 的 Route 中使用 Route 參數時,有幾個參數名稱是 Laravel 的保留字,無法使用:view
、data
、status
、header
。
Route 列表
使用 route:list
Artisan 指令就可輕鬆檢視專案中定義的所有 Route 一覽:
1php artisan route:list
1php artisan route:list
預設情況下,指派給各個 Route 的 Middleware 不會顯示在 route:list
輸出中。不過,我們可以在該指令後加上 -v
選項來讓 Laravel 顯示 Route Middleware 與 Middleware Group 的名稱:
1php artisan route:list -v23# Expand middleware groups...4php artisan route:list -vv
1php artisan route:list -v23# Expand middleware groups...4php artisan route:list -vv
也可以讓 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 (string $id) {2 return 'User '.$id;3});
1Route::get('/user/{id}', function (string $id) {2 return 'User '.$id;3});
根據 Route 的需求,我們可以定義不限數量的 Route 參數:
1Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) {2 // ...3});
1Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) {2 // ...3});
Route 參數必須要包裝在 {}
大括號中,且只能使用字母。在 Route 參數名稱中也可以使用 (_
)。Route 參數會依照順序插入到 Route 的回呼或 Controller 上 —— Route 的回呼或 Controller 中的名稱並不影響。
參數與相依性插入
若你的 Route 有使用讓 Laravel Service Container 自動插入到 Route 回呼的相依性的話,請將 Route 參數列在相依性之後:
1use Illuminate\Http\Request;23Route::get('/user/{id}', function (Request $request, string $id) {4 return 'User '.$id;5});
1use Illuminate\Http\Request;23Route::get('/user/{id}', function (Request $request, string $id) {4 return 'User '.$id;5});
可選的參數
有時候,我們可能會讓某個 Route 參數不需要出現在每個 URI 上。為此,我們可以在參數名稱後方放置一個 ?
符號。請先確定這個 Route 中對應的變數有預設值:
1Route::get('/user/{name?}', function (?string $name = null) {2 return $name;3});45Route::get('/user/{name?}', function (?string $name = 'John') {6 return $name;7});
1Route::get('/user/{name?}', function (?string $name = null) {2 return $name;3});45Route::get('/user/{name?}', function (?string $name = 'John') {6 return $name;7});
正規表示式條件
可以在 Route 實體上使用 where
方法來規定 Route 參數的格式。where
方法接受一個參數名稱、以及一個用來規範參數格式的正規表示式:
1Route::get('/user/{name}', function (string $name) {2 // ...3})->where('name', '[A-Za-z]+');45Route::get('/user/{id}', function (string $id) {6 // ...7})->where('id', '[0-9]+');89Route::get('/user/{id}/{name}', function (string $id, string $name) {10 // ...11})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
1Route::get('/user/{name}', function (string $name) {2 // ...3})->where('name', '[A-Za-z]+');45Route::get('/user/{id}', function (string $id) {6 // ...7})->where('id', '[0-9]+');89Route::get('/user/{id}/{name}', function (string $id, string $name) {10 // ...11})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
為了方便起見,一些常用的正規式都有輔助方法,可以讓你快速將這些格式套用到 Route 上:
1Route::get('/user/{id}/{name}', function (string $id, string $name) {2 // ...3})->whereNumber('id')->whereAlpha('name');45Route::get('/user/{name}', function (string $name) {6 // ...7})->whereAlphaNumeric('name');89Route::get('/user/{id}', function (string $id) {10 // ...11})->whereUuid('id');1213Route::get('/user/{id}', function (string $id) {14 //15})->whereUlid('id');1617Route::get('/category/{category}', function (string $category) {18 // ...19})->whereIn('category', ['movie', 'song', 'painting']);
1Route::get('/user/{id}/{name}', function (string $id, string $name) {2 // ...3})->whereNumber('id')->whereAlpha('name');45Route::get('/user/{name}', function (string $name) {6 // ...7})->whereAlphaNumeric('name');89Route::get('/user/{id}', function (string $id) {10 // ...11})->whereUuid('id');1213Route::get('/user/{id}', function (string $id) {14 //15})->whereUlid('id');1617Route::get('/category/{category}', function (string $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 */4public function boot(): void5{6 Route::pattern('id', '[0-9]+');7}
1/**2 * Define your route model bindings, pattern filters, etc.3 */4public function boot(): void5{6 Route::pattern('id', '[0-9]+');7}
定義好之後,這個規則會自動套用到有使用這個參數名稱的 Route:
1Route::get('/user/{id}', function (string $id) {2 // 只會在 {id} 為數字時執行...3});
1Route::get('/user/{id}', function (string $id) {2 // 只會在 {id} 為數字時執行...3});
編碼斜線
Laravel 的路由元件能接受除了 /
外的所有字元出現在 Route 的參數值內。請使用 where
正規表示式條件來顯式允許 /
出現在預留位置中:
1Route::get('/search/{search}', function (string $search) {2 return $search;3})->where('search', '.*');
1Route::get('/search/{search}', function (string $search) {2 return $search;3})->where('search', '.*');
只有最後一個 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');
Route 名稱不可重複。
產生命名 Route 的 URL
給某個 Route 指定好名稱後,我們就可以使用 Laravel 的 route
與 redirect
輔助函式來在產生 URL 或重新導向時使用 Route 的名稱:
1// 產生 URL...2$url = route('profile');34// 產生重新導向...5return redirect()->route('profile');67return to_route('profile');
1// 產生 URL...2$url = route('profile');34// 產生重新導向...5return redirect()->route('profile');67return to_route('profile');
若命名 Route 有定義參數,則可以將這些參數作為第二個引數傳給 route
函式。傳入的參數會自動依照正確位置插入到產生的 URL 裡:
1Route::get('/user/{id}/profile', function (string $id) {2 // ...3})->name('profile');45$url = route('profile', ['id' => 1]);
1Route::get('/user/{id}/profile', function (string $id) {2 // ...3})->name('profile');45$url = route('profile', ['id' => 1]);
若該陣列中有傳入額外的參數,則這些額外的索引鍵 / 值配對會自動被插入到產生的 URL 中之查詢字串 (Query String) 上:
1Route::get('/user/{id}/profile', function (string $id) {2 // ...3})->name('profile');45$url = route('profile', ['id' => 1, 'photos' => 'yes']);67// /user/1/profile?photos=yes
1Route::get('/user/{id}/profile', function (string $id) {2 // ...3})->name('profile');45$url = route('profile', ['id' => 1, 'photos' => 'yes']);67// /user/1/profile?photos=yes
有時候,我們可能會想為 URL 引數指定 Request 層級的預設值,例如目前使用的語系等。為此,可以使用 URL::defaults
方法。
檢查目前 Route
若想判斷目前的 Request 是否有被路由到給定的命名 Route 上,可以使用 Route 實體上的 named
方法。舉例來說,我們可以從某個 Route 的
Middleware 上檢查目前的 Route 名稱:
1use Closure;2use Illuminate\Http\Request;3use Symfony\Component\HttpFoundation\Response;45/**6 * Handle an incoming request.7 *8 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next9 */10public function handle(Request $request, Closure $next): Response11{12 if ($request->route()->named('profile')) {13 // ...14 }1516 return $next($request);17}
1use Closure;2use Illuminate\Http\Request;3use Symfony\Component\HttpFoundation\Response;45/**6 * Handle an incoming request.7 *8 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next9 */10public function handle(Request $request, Closure $next): Response11{12 if ($request->route()->named('profile')) {13 // ...14 }1516 return $next($request);17}
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 });56 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 });56 Route::get('/user/profile', function () {7 // 使用 first 與 second Middleware...8 });9});
Controller
若有一組 Route 全部都使用了相同的 Controller,則我們可以使用 controller
方法來在路由群組中為所有的路由定義通用的 Controller。定義好之後,當定義路由時,就只需要提供要叫用的 Controller 方法即可:
1use App\Http\Controllers\OrderController;23Route::controller(OrderController::class)->group(function () {4 Route::get('/orders/{id}', 'show');5 Route::post('/orders', 'store');6});
1use App\Http\Controllers\OrderController;23Route::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 (string $account, string $id) {3 // ...4 });5});
1Route::domain('{account}.example.com')->group(function () {2 Route::get('user/{id}', function (string $account, string $id) {3 // ...4 });5});
為了確保子網域 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」URL4 });5});
1Route::prefix('admin')->group(function () {2 Route::get('/users', function () {3 // 配對到「/admin/users」URL4 });5});
命名 Route 的名稱前置詞
The name
method may be used to prefix each route name in the group with a given string. For example, you may want to prefix the names of all of the routes in the group with admin
. The given string is prefixed to the route name exactly as it is specified, so we will be sure to provide the trailing .
character in the prefix:
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;23Route::get('/users/{user}', function (User $user) {4 return $user->email;5});
1use App\Models\User;23Route::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;34// Route 定義...5Route::get('/users/{user}', [UserController::class, 'show']);67// Controller 方法定義...8public function show(User $user)9{10 return view('user.profile', ['user' => $user]);11}
1use App\Http\Controllers\UserController;2use App\Models\User;34// Route 定義...5Route::get('/users/{user}', [UserController::class, 'show']);67// Controller 方法定義...8public function show(User $user)9{10 return view('user.profile', ['user' => $user]);11}
軟刪除的 Model
一般來說,隱式型別細節不會去的被軟刪除的 Model。不過,我們也可以在 Route 的定義後方串上 withTrashed
方法來讓隱式型別綁定取得這些 Model:
1use App\Models\User;23Route::get('/users/{user}', function (User $user) {4 return $user->email;5})->withTrashed();
1use App\Models\User;23Route::get('/users/{user}', function (User $user) {4 return $user->email;5})->withTrashed();
自訂索引鍵
有時候,我們可能會像讓 Eloquent 解析 id
以外的其他欄位。為此,可以在 Route 的參數定義中指定這個欄位:
1use App\Models\Post;23Route::get('/posts/{post:slug}', function (Post $post) {4 return $post;5});
1use App\Models\Post;23Route::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 */4public function getRouteKeyName(): string5{6 return 'slug';7}
1/**2 * Get the route key for the model.3 */4public function getRouteKeyName(): string5{6 return 'slug';7}
自訂索引鍵與作用範圍
當我們在單一 Route 定義中隱式繫結多個 Eloquent Model 時,我們可以限定第二個 Eloquent Model 一定要是前一個 Eloquent Model 的子 Model。舉例來說,假設有下列這樣通過 Slug 取得特定使用者的部落格貼文的 Route 定義:
1use App\Models\Post;2use App\Models\User;34Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {5 return $post;6});
1use App\Models\Post;2use App\Models\User;34Route::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;34Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {5 return $post;6})->scopeBindings();
1use App\Models\Post;2use App\Models\User;34Route::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;45Route::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;45Route::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<?php23namespace App\Enums;45enum Category: string6{7 case Fruits = 'fruits';8 case People = 'people';9}
1<?php23namespace App\Enums;45enum Category: string6{7 case Fruits = 'fruits';8 case People = 'people';9}
我們可以定義一個只有當 {category}
路由片段為 fruits
或 people
時才會被叫用的路由。若為其他值,Laravel 會回傳 HTTP 404 Response:
1use App\Enums\Category;2use Illuminate\Support\Facades\Route;34Route::get('/categories/{category}', function (Category $category) {5 return $category->value;6});
1use App\Enums\Category;2use Illuminate\Support\Facades\Route;34Route::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;34/**5 * Define your route model bindings, pattern filters, etc.6 */7public function boot(): void8{9 Route::model('user', User::class);1011 // ...12}
1use App\Models\User;2use Illuminate\Support\Facades\Route;34/**5 * Define your route model bindings, pattern filters, etc.6 */7public function boot(): void8{9 Route::model('user', User::class);1011 // ...12}
接著,請定義含有 {user}
參數的 Route:
1use App\Models\User;23Route::get('/users/{user}', function (User $user) {4 // ...5});
1use App\Models\User;23Route::get('/users/{user}', function (User $user) {4 // ...5});
我們已經將所有 {user}
參數繫結到 App\Models\User
Model 上了。User
Model 的實體會被插入到這個 Route 中。因此,舉例來說,對 users/1
的 Request 將會插入一個資料庫中 ID 為 1
的 User
實體。
若資料庫中找不到相符合的 Model 實體,則會自動產生 404 HTTP Response。
自訂解析邏輯
若想定義你自己的 Model 繫結解析邏輯,則可以使用 Route::bind
方法。我們可以傳入一個閉包給 bind
方法,用來接受 URI 片段中的值,並回應要插入到 Route 中的類別實體。同樣的,這個自訂邏輯應放在專案的 RouteServiceProvider
中 boot
方法內:
1use App\Models\User;2use Illuminate\Support\Facades\Route;34/**5 * Define your route model bindings, pattern filters, etc.6 */7public function boot(): void8{9 Route::bind('user', function (string $value) {10 return User::where('name', $value)->firstOrFail();11 });1213 // ...14}
1use App\Models\User;2use Illuminate\Support\Facades\Route;34/**5 * Define your route model bindings, pattern filters, etc.6 */7public function boot(): void8{9 Route::bind('user', function (string $value) {10 return User::where('name', $value)->firstOrFail();11 });1213 // ...14}
或者,我們也可以在 Eloquent Model 上複寫 resolveRouteBinding
方法。這個方法會接收 URI 片段中的值,並應回傳要插入到 Route 中的類別實體:
1/**2 * Retrieve the model for a bound value.3 *4 * @param mixed $value5 * @param string|null $field6 * @return \Illuminate\Database\Eloquent\Model|null7 */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 $value5 * @param string|null $field6 * @return \Illuminate\Database\Eloquent\Model|null7 */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 $childType5 * @param mixed $value6 * @param string|null $field7 * @return \Illuminate\Database\Eloquent\Model|null8 */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 $childType5 * @param mixed $value6 * @param string|null $field7 * @return \Illuminate\Database\Eloquent\Model|null8 */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});
遞補的 Route 應該要保持為專案中最後一個註冊的 Route。
頻率限制
定義 Rate Limiter (頻率限制程式)
Laravel 中包含了強大且可客製化的頻率限制服務,可以用來為給定的 Route 或 Route 群組限制流量。要開始使用頻率限制,我們需要先依照專案需求定義 Rate Limiter (頻率限制程式) 的設定。
一般來說,應在 App\Providers\RouteServiceProvider
類別的 configureRateLimiting
方法中定義頻率限制。事實上,該類別中已包含了一組預先定義的頻率限制,會套用到專案中 routes/api.php
檔案內的 Route:
1use Illuminate\Cache\RateLimiting\Limit;2use Illuminate\Http\Request;3use Illuminate\Support\Facades\RateLimiter;45/**6 * Define your route model bindings, pattern filters, and other route configuration.7 */8protected function boot(): void9{10 RateLimiter::for('api', function (Request $request) {11 return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());12 });1314 // ...15}
1use Illuminate\Cache\RateLimiting\Limit;2use Illuminate\Http\Request;3use Illuminate\Support\Facades\RateLimiter;45/**6 * Define your route model bindings, pattern filters, and other route configuration.7 */8protected function boot(): void9{10 RateLimiter::for('api', function (Request $request) {11 return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());12 });1314 // ...15}
使用 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;45/**6 * Define your route model bindings, pattern filters, and other route configuration.7 */8protected function boot(): void9{10 RateLimiter::for('global', function (Request $request) {11 return Limit::perMinute(1000);12 });1314 // ...15}
1use Illuminate\Cache\RateLimiting\Limit;2use Illuminate\Http\Request;3use Illuminate\Support\Facades\RateLimiter;45/**6 * Define your route model bindings, pattern filters, and other route configuration.7 */8protected function boot(): void9{10 RateLimiter::for('global', function (Request $request) {11 return Limit::perMinute(1000);12 });1314 // ...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 });56 Route::post('/video', function () {7 // ...8 });9});
1Route::middleware(['throttle:uploads'])->group(function () {2 Route::post('/audio', function () {3 // ...4 });56 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 @csrf4</form>
1<form action="/example" method="POST">2 @method('PUT')3 @csrf4</form>
存取目前的 Route
可以使用 Route
Facade 上的 current
, currentRouteName
與 currentRouteAction
方法來存取有關處理本次連入 Request 的 Route 資訊:
1use Illuminate\Support\Facades\Route;23$route = Route::current(); // Illuminate\Routing\Route4$name = Route::currentRouteName(); // string5$action = Route::currentRouteAction(); // string
1use Illuminate\Support\Facades\Route;23$route = Route::current(); // Illuminate\Routing\Route4$name = Route::currentRouteName(); // string5$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
) 中。
更多有關 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