# 路由
## 基本路由
構建最基本的路由只需要一個 URI 與一個?`閉包`,這里提供了一個非常簡單優雅的定義路由的方法:
~~~php
Route::get('foo', function () {
return 'Hello World';
});
~~~
#### 默認路由文件
所有的 Laravel 路由都在?`routes`?目錄中的路由文件中定義,這些文件都由框架自動加載。`routes/web.php`?文件用于定義 web 界面的路由。這里面的路由都會被分配給?`web`?中間件組,它提供了會話狀態和 CSRF 保護等功能。定義在?`routes/api.php`?中的路由都是無狀態的,并且被分配了?`api`?中間件組。
大多數的應用構建,都是以在?`routes/web.php`?文件定義路由開始的。可以通過在瀏覽器中輸入定義的路由 URL 來訪問?`routes/web.php`?中定義的路由。例如,你可以在瀏覽器中輸入?`http://your-app.dev/user`?來訪問以下路由:
~~~php
Route::get('/user', 'UserController@index');
~~~
`routes/api.php`?文件中定義的路由通過?`RouteServiceProvider`?被嵌套到一個路由組里面。在這個路由組中,會自動添加 URL 前綴?`/api`?到此文件中的每個路由,這樣你就無需再手動添加了。你可以在?`RouteServiceProvider`?類中修改此前綴以及其他路由組選項。
#### 可用的路由方法
路由器允許你注冊能響應任何 HTTP 請求的路由:
~~~php
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 請求的路由:
~~~php
Route::match(['get', 'post'], '/', function () {
//
});
Route::any('foo', function () {
//
});
~~~
#### CSRF 保護
指向?`web`?路由文件中定義的?`POST`、`PUT`?或?`DELETE`?路由的任何 HTML 表單都應該包含一個 CSRF 令牌字段,否則,這個請求將會被拒絕。可以在?[CSRF 文檔](https://laravel-china.org/docs/laravel/5.7/csrf)?中閱讀有關 CSRF 更多的信息:
~~~php
<form method="POST" action="/profile">
@csrf
...
</form>
~~~
### 重定向路由
如果要定義重定向到另一個 URI 的路由,可以使用?`Route::redirect`方法。這個方法可以快速的實現重定向,而不再需要去定義完整的路由或者控制器:
~~~php
Route::redirect('/here', '/there', 301);
~~~
### 視圖路由
如果你的路由只需要返回一個視圖,可以使用?`Route::view`?方法。它和?`redirect`?一樣方便,不需要定義完整的路由或控制器。`view`?方法有三個參數,其中前兩個是必填參數,分別是 URI 和視圖名稱。第三個參數選填,可以傳入一個數組,數組中的數據會被傳遞給視圖:
~~~php
Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
~~~
## 路由參數
### 必填參數
當然,有時需要在路由中捕獲一些 URL 片段。例如,從 URL 中捕獲用戶的 ID,可以通過定義路由參數來執行此操作:
~~~php
Route::get('user/{id}', function ($id) {
return 'User '.$id;
});
~~~
也可以根據需要在路由中定義多個參數:
~~~php
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
//
});
~~~
路由的參數通常都會被放在?`{}`?內,并且參數名只能為字母,同時路由參數不能包含?`-`?符號,如果需要可以用下劃線 (`_`) 代替。路由參數會按順序依次被注入到路由回調或者控制器中,而不受回調或者控制器的參數名稱的影響。
### 可選參數
有時,你可能需要指定一個路由參數,但你希望這個參數是可選的。你可以在參數后面加上?`?`?標記來實現,但前提是要確保路由的相應變量有默認值:
~~~php
Route::get('user/{name?}', function ($name = null) {
return $name;
});
Route::get('user/{name?}', function ($name = 'John') {
return $name;
});
~~~
### 正則表達式約束
你可以使用路由實例上的?`where`?方法約束路由參數的格式。`where`?方法接受參數名稱和定義參數應如何約束的正則表達式:
~~~php
Route::get('user/{name}', function ($name) {
//
})->where('name', '[A-Za-z]+');
Route::get('user/{id}', function ($id) {
//
})->where('id', '[0-9]+');
Route::get('user/{id}/{name}', function ($id, $name) {
//
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
~~~
#### 全局約束
如果你希望某個具體的路由參數都遵循同一個正則表達式的約束,就使用?`pattern`?方法在?`RouteServiceProvider`?的?`boot`?方法中定義這些模式:
~~~php
/**
* 定義你的路由模型綁定, pattern 過濾器等。
*
* @return void
*/
public function boot()
{
Route::pattern('id', '[0-9]+');
parent::boot();
}
~~~
一旦定義好之后,便會自動應用這些規則到所有使用該參數名稱的路由上:
~~~php
Route::get('user/{id}', function ($id) {
// 只有在 id 為數字時才執行。
});
~~~
## 路由命名
路由命名可以方便地為指定路由生成 URL 或者重定向。通過在路由定義上鏈式調用?`name`?方法可以指定路由名稱:
~~~php
Route::get('user/profile', function () {
//
})->name('profile');
~~~
你還可以指定控制器行為的路由名稱:
~~~php
Route::get('user/profile', 'UserProfileController@show')->name('profile');
~~~
#### 生成指定路由的 URL
為路由指定了名稱后,就可以使用全局輔助函數?`route`?來生成鏈接或者重定向到該路由:
~~~php
// 生成 URL...
$url = route('profile');
// 生成重定向...
return redirect()->route('profile');
~~~
如果是有定義參數的命名路由,可以把參數作為?`route`?函數的第二個參數傳入,指定的參數將會自動插入到 URL 中對應的位置:
~~~php
Route::get('user/{id}/profile', function ($id) {
//
})->name('profile');
$url = route('profile', ['id' => 1]);
~~~
#### 檢查當前路由
如果你想判斷當前請求是否指向了某個路由,你可以調用路由實例上的?`named`?方法。例如,你可以在路由中間件中檢查當前路由名稱:
~~~php
/**
* 處理一次請求。
*
* @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`方法的第一個參數中。
### 中間件
要給路由組中所有的路由分配中間件,可以在 group 之前調用`middleware`方法,中間件會依照它們在數組中列出的順序來運行:
~~~php
Route::middleware(['first', 'second'])->group(function () {
Route::get('/', function () {
// // 使用 first 和 second 中間件
});
Route::get('user/profile', function () {
// // 使用 first 和 second 中間件
});
});
~~~
### 命名空間
另一個常見用例是使用`namespace`方法將相同的 PHP 命名空間分配給路由組的中所有的控制器:
~~~php
Route::namespace('Admin')->group(function () {
// 在 "App\Http\Controllers\Admin" 命名空間下的控制器
});
~~~
請記住,默認情況下,`RouteServiceProvider`會在命名空間組中引入你的路由文件,讓你不用指定完整的`App\Http\Controllers`命名空間前綴就能注冊控制器路由。因此,你只需要指定命名空間`App\Http\Controllers`之后的部分。
### 子域名路由
路由組也可以用來處理子域名。子域名可以像路由 URI 一樣被分配路由參數,允許你獲取一部分子域名作為參數給路由或控制器使用。可以在 group 之前調用`domain`方法來指定子域名:
~~~php
Route::domain('{account}.myapp.com')->group(function () {
Route::get('user/{id}', function ($account, $id) {
//
});
});
~~~
### 路由前綴
可以用`prefix`方法為路由組中給定的 URL 增加前綴。例如,你可以為組中所有路由的 URI 加上 admin 前綴:
~~~php
Route::prefix('admin')->group(function () {
Route::get('users', function () {
// 匹配包含 "/admin/users" 的 URL
});
});
~~~
### 路由名稱前綴
name 方法可以用來給路由組中的每個路由名稱添加一個給定的字符串。 例如,您可能希望以 「admin」為所有分組路由的名稱加前綴。 給定的字符串與指定的路由名稱前綴完全相同,因此我們將確保在前綴中提供尾部的`.`字符:
~~~php
Route::name('admin.')->group(function () {
Route::get('users', function () {
// Route assigned name "admin.users"...
})->name('users');
});
~~~
## 路由模型綁定
當向路由或控制器行為注入模型 ID 時,就需要查詢這個 ID 對應的模型。Laravel 為路由模型綁定提供了一個直接自動將模型實例注入到路由中的方法。例如,你可以注入與給定 ID 匹配的整個`User`模型實例,而不是注入用戶的 ID。
### 隱式綁定
Laravel 會自動解析定義在路由或控制器行為中與類型提示的變量名匹配的路由段名稱的 Eloquent 模型。例如:
~~~php
Route::get('api/users/{user}', function (App\User $user) {
return $user->email;
});
~~~
在這個例子中,由于`$user`變量被類型提示為 Eloquent 模型`App\User`,變量名稱又與 URI 中的`{user}`匹配,因此,Laravel 會自動注入與請求 URI 中傳入的 ID 匹配的用戶模型實例。如果在數據庫中找不到對應的模型實例,將會自動生成 404 異常。
#### 自定義鍵名
如果你想要模型綁定在檢索給定的模型類時使用除`id`之外的數據庫字段,你可以在 Eloquent 模型上重寫`getRouteKeyName`方法:
~~~php
/**
* 獲取該模型的路由的自定義鍵名。
*
* @return string
*/
public function getRouteKeyName()
{
return 'slug';
}
~~~
### 顯式綁定
要注冊顯式綁定,使用路由器的?`model`?方法來為給定參數指定類。在?`RouteServiceProvider`?類中的?`boot`方法內定義這些顯式模型綁定:
~~~php
public function boot()
{
parent::boot();
Route::model('user', App\User::class);
}
~~~
接著,定義一個包含`{user}`參數的路由:
~~~php
Route::get('profile/{user}', function (App\User $user) {
//
});
~~~
因為我們已經將所有?`{user}`?參數綁定至?`App\User`?模型,所以?`User`?實例將被注入該路由。例如,`profile/1`?的請求會注入數據庫中 ID 為 1 的?`User`?實例。
如果在數據庫中找不到匹配的模型實例,就會自動拋出一個 404 異常。
#### 自定義邏輯解析
如果你想要使用自定義的解析邏輯,就使用?`Route::bind`?方法。傳遞到?`bind`?方法的?`閉包`?會接受 URI 中大括號對應的值,并且返回你想要在該路由中注入的類的實例:
~~~php
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return App\User::where('name', $value)->first() ?? abort(404);
});
}
~~~
## 回退路由
使用`Route::fallback`方法,你可以定義在沒有其他路由匹配傳入請求時執行的路由。通常,未處理的請求會通過應用程序的異常處理程序自動呈現 “404” 頁面。但是,因為你可以在`routes/web.php`文件中定義`fallback`路由,`web`中間件的所有中間件都將應用到路由中。當然,你也可以根據需要向這條路由中添加額外的中間件:
~~~php
Route::fallback(function () {
//
});
~~~
## 訪問控制
Laravel 包含了一個?[中間件](https://laravel-china.org/docs/laravel/5.7/middleware)?用于控制應用程序對路由的訪問。如果想要使用,請將?`throttle`?中間件分配給一個路由或一個路由組。`throttle`?中間件會接收兩個參數,這兩個參數決定了在給定的分鐘數內可以進行的最大請求數。 例如,讓我們指定一個經過身份驗證并且用戶每分鐘訪問頻率不超過 60 次的路由:
~~~php
Route::middleware('auth:api', 'throttle:60,1')->group(function () {
Route::get('/user', function () {
//
});
});
~~~
#### 動態訪問控制
你可以根據已驗證的?`User`?模型的屬性指定動態請求的最大值。 例如,如果你的?`User`?模型包含`rate_limit`屬性,則可以將屬性名稱傳遞給?`throttle`?中間件,以便它用于計算最大請求計數:
~~~php
Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
Route::get('/user', function () {
//
});
});
~~~
## 表單方法偽造
HTML 表單不支持?`PUT`、`PATCH`?或?`DELETE`?行為。所以當你要從 HTML 表單中調用定義了?`PUT`、`PATCH`?或?`DELETE`?路由時,你將需要在表單中增加隱藏的?`_method`?輸入標簽。使用?`_method`?字段的值作為 HTTP 的請求方法:
~~~php
<form action="/foo/bar" method="POST">
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
~~~
你也可以使用?`@ method`?Blade 指令生成?`_method`?輸入:
~~~php
<form action="/foo/bar" method="POST">
@method('PUT')
@csrf
</form>
~~~
## 訪問當前路由
你可以使用 Route Facade 上的?`current`、`currentRouteName`?和?`currentRouteAction`?方法來訪問處理傳入請求的路由的信息:
~~~php
$route = Route::current();
$name = Route::currentRouteName();
$action = Route::currentRouteAction();
~~~