--- tags: laravel --- # Route 路由 ![](https://i.imgur.com/u3zAA32.jpg) ## 基本路由 大部分的基礎路由需要一個 URI 和一個 Closure,提供了一個非常簡單且優雅定義路由的方法,而不需要透過複雜的路由設定檔案: ``` use Illuminate\Support\Facades\Route; Route::get('/greeting', function () { return 'Hello World'; }); ``` ### 預設路由檔案 所有的 Laravel 路由都在 routes 資料夾中定義,這些檔案都會由框架的App\Providers\RouteServiceProvider 自動加載 routes/web.php 文件用於定義 web 界面的路由。這裡面的路由會被分配給 web 中介層群組,它提供了會話(Session)狀態和 CSRF 保護等功能 routes/api.php 文件則用於定義無狀態的路由,並且被分配了 api 中介層群組 大多數的應用都是以在 routes/web.php 檔案去定義路由開始的。可以通過在瀏覽器中輸入定義的路由 URL 來訪問 routes/web.php 中定義的路由。例如,你可以在瀏覽器中輸入 http://your-app.test/user 來訪問以下路由,其中your-app.test就是你應用的網域: ``` use App\Http\Controllers\UserController; Route::get('/users','App\Http\Controllers\UserController@index'); //8.x版本新增的寫法 Route::get('/user', [UserController::class, 'index']); ``` > 規則判定由上至下逐一比對,只要符合路徑就傳遞給控制器 定義在 routes/api.php 檔案中的路由是被 RouteServiceProvider 包在一個路由群組內。在這個路由群組中,將自動應用 /api URI前綴,所以你無需手動將其應用於檔案中的每個路由。 如需修改,可透過 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'], '/', function () { //同時支持GET和POST請求方法 }); Route::any('/', function () { //支持所有請求方法 }); ``` ### 依賴注入 你也可以在你路由的回呼方法中去加入型別提示,所宣告的依賴將自動的被 Laravel 服務容器解析後傳給回呼方法。比如下面這個例子就會將 Illuminate\Http\Request 類別作為當前 HTTP 請求注入到路由回呼方法: ``` use Illuminate\Http\Request; Route::get('/users', function (Request $request) { // ... }); ``` ### CSRF 保護 Remember, any HTML forms pointing to POST, PUT, PATCH, or DELETE routes that are defined in the web routes file should include a CSRF token field. Otherwise, the request will be rejected. You can read more about CSRF protection in the CSRF documentation: 切記,指向 web 路由文件中定義的 POST,PUT, PATCH, 或 DELETE 路由的任何HTML表單都應該包含一個 CSRF 口令,否則這個請求將會被應用拒絕。可以在[CSRF保護](/0ottV7cqRdyVpWYIhw05lw)看到更多的信息,如下例: ``` <form method="POST" action="/profile"> @csrf ... </form> ``` ### 轉址路由 如果要定義轉址到另一個 URI 的路由,可以使用 Route::redirect() 。這個方法提供了一種方便的快捷方式,因此你不必為了要執行一個簡單的轉址而定義完整的路由或控制器: `Route::redirect('/here', '/there');` 預設情況下, Route::redirect() 返回的狀態碼是 302 。但你可以使用第三個可選參數來自定義狀態碼: `Route::redirect('/here', '/there', 301);` 或者你可以乾脆使用 Route::permanentRedirect() 就會回傳 301 狀態碼: `Route::permanentRedirect('/here', '/there');` > 注意 > > 在轉址路由中使用路由參數時,以下參數名稱由 Laravel 保留,請不要使用這兩個名稱:destination 和 status ### 視圖路由 如果你的路由只是要回傳一個視圖, 可以使用 Route::view()。 它和 redirect 一樣,提供了一個簡單的方式,而無需定義完整的路由或控制器。 view 方法接受一個 URI 作為第一個參數,一個視圖名作為第二個參數。此外,還可以通過第三個可選參數來將一個參數陣列傳遞給視圖: ``` Route::view('/welcome', 'welcome'); Route::view('/welcome', 'welcome', ['name' => 'Taylor']); ``` >注意 > >在視圖路由中使用路由參數時,以下參數由 Laravel 保留,請不要使用以下名稱:view,data,status 和 headers ## 路由參數 ### 必填參數 有时候你需要在路由中去抓取一些 URL 區塊。例如,從 URL 中抓取用户的 ID,可以這樣定義路由參數: ``` Route::get('/user/{id}', function ($id) { return 'User '.$id; }); ``` 你可以根據路由的需要來定義更多的路由參數,當路由參數多於1個,路由參數並非透過名稱來對應到後面的參數,而是根據順序: ``` Route::get('/posts/{post}/comments/{comment}', function ($postId, $commentId) { // }); ``` 路由參數總是會被放在 {} 內,並且參數名只能為字母,而不能包含 - 符號。如果真的有需要,可以用下橫線 (_) 代替 - 。路由參數會按路由定義的順序依次注入到路由回呼方法或者控制器中,而不受參數名稱的影響,但為了幫助閱讀,仍然建議使用相同的名稱 ``` use Illuminate\Http\Request; Route::get('/user/{id}', function (Request $request, $id) { return 'User '.$id; }); ``` ### 路由參數與依賴注入 假如你的路由有依賴的話,你仍然可以讓 Laravel 服務容器來將之自動注入到你的路由回呼方法,而你的路由參數則是列在依賴之後 ### 可選參數 有時,你可能需要指定一個路由參數,但你又希望這個參數是非必填的。這時你可以在參數後面加上?標記來達成,但前提是要確保路由的相應參數有預設值: ``` 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]+']); ``` 為了方便起見,Laravel內建的幫助函式可提供你一些常用的正則表達式模式,參考一下這些方法可使你快速的向路由添加正則式約束: ``` Route::get('/user/{id}/{name}', function ($id, $name) { // })->whereNumber('id')->whereAlpha('name'); Route::get('/user/{name}', function ($name) { // })->whereAlphaNumeric('name'); Route::get('/user/{id}', function ($id) { // })->whereUuid('id'); ``` 假如進來的請求不符合路由模式規範,就會回傳 404 回應 ### 全域限制 如果你希望某個具名的路由參數都遵循同一個正則表達式的約束,就使用 pattern() 在 App\Providers\RouteServiceProvider 類別的 boot() 中定義這些: ``` /** * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot() { Route::pattern('id', '[0-9]+'); } ``` 一旦模式定義好之後,便會自動應用這些規則到所有使用該參數名稱的路由上: ``` Route::get('/user/{id}', function ($id) { // Only executed if {id} is numeric... }); ``` ### 編碼正斜槓字元 Laravel 路由組件允許除了 / 之外的所有字元。你必須使用 where 條件正則表達式顯式地允許 / 成為占位字元的一部分: ``` Route::get('/search/{search}', function ($search) { return $search; })->where('search', '.*'); ``` >注意 > >正斜槓字元僅可用在最後一個路由參數中 ## 命名路由 路由命名可以方便地為指定路由生成 URL 或者轉址到特定路由。通過在路由定義上繼續接著呼叫 name() 可以指定路由名稱: ``` Route::get('/user/profile', function () { // })->name('profile'); ``` 你也可以為控制器的 Actions 指定路由名稱: ``` Route::get( '/user/profile', [UserProfileController::class, 'show'] )->name('profile'); ``` > 路由名稱必須是獨一無二的 ### 生成指定具名路由的 URL 一旦為路由指定了名稱,就可以使用全域幫助函式 route 來生成連結或者轉址到該路由: ``` // 生成 URLs... $url = route('profile'); // 生成轉址... return redirect()->route('profile'); ``` 如果該命名路由有定義路由參數,可以把參數作為 route() 的第二個參數傳入,指定的參數將會自動插入到 URL 中對應的位置: ``` Route::get('/user/{id}/profile', function ($id) { // })->name('profile'); $url = route('profile', ['id' => 1]); //將生成url: user/1/profile ``` 如果你在陣列中傳遞額外的參數,這些鍵或值將自動添加到生成的 URL 的查詢字串中,也就是?後面的內容: ``` Route::get('/user/{id}/profile', function ($id) { // })->name('profile'); $url = route('profile', ['id' => 1, 'photos' => 'yes']); // /user/1/profile?photos=yes ``` >技巧: > >有時候,你可能希望為某些 URL 參數的請求範圍指定預設值,例如在本地環境,你可以使用 URL::defaults() ### 檢查當前路由 如果你想判斷當前請求是否指向到某個命名過的路由,你可以呼叫路由實例上的 named() 。例如,你可以在路由中介層中檢查當前路由名稱: /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($request->route()->named('profile')) { //該指向路由名為profile... } return $next($request); } ## 路由群組 路由群組允許你在大量路由之間去共享路由屬性,例如中介層,就不需要為每個路由單獨去定義這些屬性,這讓你的開發更為方便 將之包入的群組將嘗試智能地「合併」其屬性及其父群組。中介層和 where 條件語句在附加名稱和前綴時被合併。在適當的情況下,命名空間的分隔字元和斜線會被自動添加到 URI 前綴中。 ### 中介層 要給路由群組中所有的路由分配中介層,可以在 group 之前呼叫 middleware() ,中介層會依照它們在陣列中列出的順序來運行: ``` Route::middleware(['first', 'second'])->group(function () { Route::get('/', function () { // 採用 first & second 中介層... }); Route::get('/user/profile', function () { // 採用 first & second 中介層... }); }); ``` ### 子域名路由 路由群組也可以用來處理子域名。子域名可以像路由 URI 一樣被分配路由參數,允許你獲取一部分子域名作為參數給路由或控制器使用。可以在定義 group 之前呼叫 domain() 來指定子域名: ``` Route::domain('{account}.example.com')->group(function () { Route::get('user/{id}', function ($account, $id) { // }); }); ``` >注意 > >為了確保您的子域名路由可正常訪問,您應該在註冊根域名路由之前先註冊子域名路由,這能夠防止根域名路由覆蓋具有相同 URI 路徑的子域名路由 ### 路由前綴 prefix() 將會為路由群組中的每一個 URI 添加前綴。例如你可以給該群組中所有的 URI 添加 admin 的前綴: ``` Route::prefix('admin')->group(function () { Route::get('/users', function () { // 符合 "/admin/users" URL }); }); ``` ### 路由命名前綴 name() 方法可以為路由群組中每一個路由名稱添加一個指定的字串作為前綴。例如你可以給已經分組的路徑添加 admin 前綴。所指定的字串與指定的路由名稱前綴完全相同,因此我們將確保在前綴中提供尾部的 . 字元: ``` Route::name('admin.')->group(function () { Route::get('users', function () { // 路由的命名將會是 "admin.users"... })->name('users'); }); ``` ## 路由模型綁定 當向路由或控制器的方法注入模型 ID主鍵 時,您可能總是會需要透過ID來找出該筆資料。Laravel 路由模型綁定提供了一個方便的方案,它可以自動注入模型實例到路由中。例如,您可以注入與主鍵值為給定 ID 的整個 User 模型實例,而不是只注入用戶的 ID ### 隱式榜定 Laravel 會自動處理在路由或控制器方法中,與型別提示的參數名相匹配的路由區段名稱的的 Eloquent 模型。例如: ``` use App\Models\User; Route::get('/users/{user}', function (User $user) { return $user->email; }); ``` 在這個例子中,由於 $user 參數被型別提示為 Eloquent 模型 App\Models\User,參數名稱又與 URI 中的 {user} 相同,因此,Laravel 會自動注入與請求 URI 中傳入的 ID 主鍵相符的用戶模型實例。如果在數據庫中未找到對應的模型實例,將會自動生成 404 異常 當然,在使用控制器方法時也可以使用隱式綁定。同樣,請注意 {user} URI 區段與控制器中的 $user 參數相同,並且該參數包含一個 App\Models\User 型別提示: ``` use App\Http\Controllers\UserController; use App\Models\User; // 路由定義... Route::get('/users/{user}', [UserController::class, 'show']); // 控制器方法定義... public function show(User $user) { return view('user.profile', ['user' => $user]); } ``` #### 自定義鍵名 有時,你可能希望使用 id 以外的欄位來查詢 Eloquent 模型。為此你可以在路由參數定義中指定要查詢的欄位: ``` use App\Models\Post; Route::get('/posts/{post:slug}', function (Post $post) { return $post; }); ``` 假如你希望永遠使用某個除id之外的欄位來進行查詢以取得模型實例,你可以複寫 Eloquent 模型的 getRouteKeyName(): ``` /** * Get the route key for the model. * * @return string */ public function getRouteKeyName() { return 'slug'; } ``` #### 自定義鍵與作用域 有時,當一個路由定義中隱式綁定多個 Eloquent 模型時,您可能會想要限定第二個 Eloquent 模型的作用域,使它是第一個 Eloquent 模型的子模型。例如,考慮這樣一種情況,通過 slug 字段為特定用戶去查詢部落格文章: ``` use App\Models\Post; use App\Models\User; Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) { return $post; }); ``` 當使用自定義鍵值隱式綁定作為巢式路由參數時,Laravel 將自動限定查詢的範圍,通過其父節點使用猜測父節點上關係名稱的約定來檢索巢狀模型。 在本例中,假定 User 模型有一個名為 posts 的關聯 (路由參數名的複數形式),可用於查詢 Post 模型(這功能理解上有點複雜,先作筆記)。 ### 顯示榜定 你並非只能透過 Laravel的隱式榜定才能夠使用模型綁定功能,你也可以清楚的定義到底路由參數與模型間的關係為何 要註冊顯式綁定,請使用路由器的 model() 方法為給定的參數指明參數對應的類別。您應該在 RouteServiceProvider 類別的 boot() 的開頭定義顯式模型綁定: ``` use App\Models\User; use Illuminate\Support\Facades\Route; /** * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot() { Route::model('user', User::class); // ... } ``` 接下來,定義一個包含 {user} 參數路段的路由: ``` use App\Models\User; Route::get('/users/{user}', function (User $user) { // }); ``` 由於我們已將所有 {user} 參數綁定至 App\Models\User 模型,所以 User 實例將被注入該路由。舉個例子,profile/1 的請求會注入數據庫中 id欄位為 1 的 User 實例 如果在資料庫中找不到對應的模型實例,就會自動拋出一個 404 異常。 ### 自定義解析邏輯 如果您希望使用自己的解析邏輯,可以使用 Route::bind() 。傳遞給 bind() 的 Closure 將接收 URI 中大括號對應的值,並返回你想要在該路由中注入類別的實例: 再強調一次,這個自定義應該要寫在應用內的 RouteServiceProvider類別的 boot() ``` use App\Models\User; use Illuminate\Support\Facades\Route; /** * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot() { Route::bind('user', function ($value) { return User::where('name', $value)->firstOrFail(); }); // ... } ``` 或者你也可以重寫 Eloquent 模型上的 resolveRouteBinding() 。 此方法會接受 URI 中大括號對應的值,並且返回你想要在該路由中注入的類別的實例: ``` /** * 根據榜定的值來取得模型實例. * * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveRouteBinding($value, $field = null) { return $this->where('name', $value)->firstOrFail(); } ``` 如果路由使用隱式綁定作用域,resolveChildRouteBinding() 將用於解析父模型的子綁定: ``` /** * Retrieve the child model for a bound value. * * @param string $childType * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Model|null */ public function resolveChildRouteBinding($childType, $value, $field) { return parent::resolveChildRouteBinding($childType, $value, $field); } ``` ## 回呼路由 使用 Route::fallback() , 您可以定義一個在沒有其他路由規則可符合所傳入的請求時才執行的路由。一般來說,未處理的請求會通過應用程序的異常處理程序自動呈現 「404」 頁面。但是由於你可以在 routes/web.php 檔案中定義 fallback 路由,因此 web 中介層群組中的所有中介層都將應用於該路由。當然你也可以根據需要隨意向此路由添加其他中介層: ``` Route::fallback(function () { // }); ``` >注意 > >回呼路由必須是應用註冊的最後一個路由才行 ## 限流 Laravel 包含功能強大且可自定義的限流服務,您可以利用這些服務來限制指定的路由或路由群組的流量。首先,您應該定義滿足應用的限流器配置。通常,這可以寫在應用裡的 RouteServiceProvider 中 限流器可以使用 RateLimiter 這個Facade的 for() 來定義。 for() 接受一個速率限制器名稱和一個 Closure 會返回限流配置,該配置可應用於分配了該限流器的路由: 限流配置是 Illuminate\Cache\RateLimiting\Limit 類別的實例,這個類別包含了有用的「builder」方法讓你快速的定義你的限制,而限流器的名稱可以是任意字串 ``` use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; /** * Configure the rate limiters for the application. * * @return void */ protected function configureRateLimiting() { RateLimiter::for('global', function (Request $request) { return Limit::perMinute(1000); }); } ``` 如果傳入的請求速率超過了設置的請求頻率,Laravel 會自動的回傳 HTTP 429 的狀態碼。如果你想自定義回傳內容可使用 response() : ``` RateLimiter::for('global', function (Request $request) { return Limit::perMinute(1000)->response(function () { return response('Custom response...', 429); }); }); ``` 因為限流器回呼方法有接受傳入請求實例,也就是下例的 $request ,你可以使用它來取得所需的資料,如 user() 用以確認登入者身分從而判斷是否需要進行限流 ``` RateLimiter::for('uploads', function (Request $request) { return $request->user()->vipCustomer() ? Limit::none() : Limit::perMinute(100); }); ``` ### 範圍限流 根據商業需要有時會按某個範圍去限流。比如VIP用戶無限制,而不是VIP的用戶每個IP每分鐘只能請求100次。要達到此功能,你可以在建立限流配置時在後面接著呼叫 by() ``` RateLimiter::for('uploads', function (Request $request) { return $request->user()->vipCustomer() ? Limit::none() : Limit::perMinute(100)->by($request->ip()); }); ``` ### 多組限流 假如需要的話,你可以在限流配置去回傳一個限流陣列。符合該限流配置的路由會根據限流在陣列中的順序來依序執行限流: ``` RateLimiter::for('login', function (Request $request) { return [ Limit::perMinute(500), Limit::perMinute(3)->by($request->input('email')), ]; }); ``` ### 直接給路由配置限流 要將配置好的限流器直接配置到路由上,使用 throttle 中介層關鍵字加上限流器的名字放到中介層 middleware 陣列中即可: ``` Route::middleware(['throttle:uploads'])->group(function () { Route::post('/audio', function () { // }); Route::post('/video', function () { // }); }); ``` ### 用Redis來進行限流 通常 throttle 中介層會映射到 Illuminate\Routing\Middleware\ThrottleRequests 類別。此映射是在應用的 HTTP 內核(App\Http\Kernel)中定義。但是,如果你改用 Redis 作為應用的快取驅動程序,那麼你可能希望將此映射改為使用 Illuminate\Routing\Middleware\ThrottleRequestsWithRedis 類別。該類別在使用 Redis 管理限流方面更有效: `'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,` ## 偽造表單方法 HTML 表單不支持 PUT, PATCH 或 DELETE 請求方法。所以當 HTML 表單呼叫請求方式為 PUT, PATCH 或 DELET 的路由時,您需要在表單中添加一個名為 _method 的隱藏輸入項。_method 輸入項的值將會作為 HTTP 請求的方法 ``` <form action="/example" method="POST"> <input type="hidden" name="_method" value="PUT"> <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> ``` 您也可以使用 @method 這個Blade標籤來生成 _method 隱藏輸入項: ``` <form action="/example" method="POST"> @method('PUT') @csrf </form> ``` ## 訪問當前路由 可以使用 Route Facade的 current() . currentRouteName() 和 currentRouteAction() 處理傳入請求的路由訪問資訊: ``` use Illuminate\Support\Facades\Route; $route = Route::current(); // Illuminate\Routing\Route $name = Route::currentRouteName(); // string $action = Route::currentRouteAction(); // string ``` 有關 Route Facade 和 Route 實例 的基礎類別資訊,請參閱 API 文件,以查看所有可訪問的方法 ## 跨域資源共享(CORS) Laravel 可以使用你設定的值去自動回應 CORS OPTIONS HTTP 請求。所有的 CORS 設定都可以在應用的 config/cors.php 設定檔案中設置, 預設情況下的全域中介層群組中的 HandleCors 中介層會自動處理 OPTIONS 請求。所有的全域中介層群組是設定於應用的 HTTP 內核(App\Http\Kernel) 技巧:要獲取 CORS 和 CORS 頭的更多信息,請參閱 [MDN web 文檔上的 CORS 章節](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/CORS) ## 路由快取 當把你的應用佈署到正式環境之後,你就能啟用 Laravel 路由快取。使用路由快取將極大的降低所有路由的回應時間。要啟用路由快取,執行以下 Artisan 指令: `php artisan route:cache` 執行以上命令後,你的路由快取將在每次請求傳入後載入。記得,每當你加入新路由就需要重新生成全新的路由快取。作法就是在佈署環境重新的呼叫 route:cache 指令 如果有需要的話,你也可以使用 route:clear 指令來清除這些路由快取 `php artisan route:clear`