[TOC]
# 基本路由
構建最基本的路由只需要一個 URI 與一個 `閉包`,這里提供了一個非常簡單優雅的定義路由的方法:
~~~
Route::get('foo', function () {
return 'Hello World';
});
~~~
我們以在安裝配置文檔中新建的 blog 應用為例,在 routes/web.php 中定義該路由:
:-: ?
在瀏覽器中通過 `http://blog.test/hello` (我使用 Valet 作為開發環境,故而對應域名是 blog.test,實際域名以自己配置的為準)即可訪問我們剛剛定義的路由,頁面輸出內容如下:
~~~
Hello, welcome to LaravelAcademy.org
~~~
**默認路由文件**
所有的 Laravel 路由都在 `routes` 目錄中的路由文件中定義,這些文件都由框架自動加載。`routes/web.php` 文件用于定義 web 界面的路由。這里面的路由都會被分配給 `web` 中間件組,它提供了會話狀態和 CSRF 保護等功能。定義在 `routes/api.php` 中的路由都是無狀態的,并且被分配了 `api` 中間件組。
大多數的應用構建,都是以在 `routes/web.php` 文件定義路由開始的。可以通過在瀏覽器中輸入定義的路由 URL 來訪問 `routes/web.php` 中定義的路由。例如,你可以在瀏覽器中輸入 `http://your-app.dev/user` 來訪問以下路由:
~~~
Route::get('/user', 'UserController@index');
~~~
`routes/api.php` 文件中定義的路由通過 `RouteServiceProvider` 被嵌套到一個路由組里面。在這個路由組中,會自動添加 URL 前綴 `/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'], '/', function () {
//
});
Route::any('foo', function () {
//
});
~~~
測試 GET 請求的時候直接在瀏覽器中輸入請求地址即可,測試 POST 請求可以通過客戶端工具,比如 Advanced REST Client,該工具可以在 Chrome 應用商店下載到,此外如果上面的路由是定義在 `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` 的 POST 請求:
:-: 
正如我們所預測的,完全沒有任何問題,背后的原因是因為 web 路由文件中定義的路由都位于 `web` 中間件群組,該群組默認啟用 CSRF 保護檢查,而 api 路由文件位于 api 路由群組,該群組下的路由主要用于 第三方 API 請求,沒辦法進行 CSRF 檢查,所以不需要做任何處理。
**CSRF 保護**
指向 `web` 路由文件中定義的 `POST`、`PUT` 或 `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', 301);
~~~
其中 `here` 表示原路由,`there` 表示重定向之后的路由,`301` 是一個 HTTP 狀態碼,用于標識重定向。
路由視圖
如果你的路由需要返回一個視圖,可以使用 `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} 是數字時才會被調用
});
~~~
很顯然這種方式讓代碼更簡潔,也為我們實現同一參數統一約束帶來了方便。
## 命名路由
命名路由為生成 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` 方法。
# 中間件
要給某個路由分組中定義的所有路由分配中間件,可以在定義分組之前使用 `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);
});
}
~~~
有了這些方法,基本上可以滿足你對路由模型綁定的各種需求了。
# 頻率限制
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 表單不支持 `PUT`、`PATCH` 或者 `DELETE` 請求方法,因此,在 HTML 表單中調用 `PUT`、`PATCH` 或 `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` 門面上的 `current`、`currentRouteName` 和 `currentRouteAction` 方法來訪問處理當前輸入請求的路由信息:
~~~
// 獲取當前路由實例
$route = Route::current();
// 獲取當前路由名稱
$name = Route::currentRouteName();
// 獲取當前路由action屬性
$action = Route::currentRouteAction();
~~~
參考API文檔了解路由門面底層類以及Route實例的更多可用方法。
- 序言
- 新版特性
- 快速入門
- 升級指南
- 貢獻指南
- API文檔
- 安裝配置
- 目錄結構
- Homestead
- Valet
- 部署
- 核心概念
- 請求生命周期
- 服務容器
- 服務提供者
- 門面(Facades)
- 契約(Contracts)
- 框架基礎
- 路由
- 中間件
- CSRF 保護
- 控制器
- 請求
- 響應
- 視圖
- 生成 URL
- Session
- 驗證
- 錯誤處理
- 日志
- 前端開發
- Blade 模板
- 本地化
- 前端腳手架
- 編譯前端資源(Laravel Mix)
- 安全系列
- 登錄認證
- API 認證
- 授權
- 加密
- 哈希
- 重置密碼
- 進階系列
- Artisan 控制臺
- 集合
- 廣播
- 緩存
- 事件
- 文件存儲
- 輔助函數
- 郵件
- 通知
- 擴展包開發
- 隊列
- 任務調度
- 數據庫操作
- 快速入門
- 查詢構建器
- 分頁
- 遷移
- 數據填充
- Redis
- Eloquent ORM
- 快速入門
- 關聯關系
- 集合
- 訪問器 & 修改器
- API 資源類
- 序列化
- 應用測試
- 快速入門
- HTTP 測試
- 瀏覽器測試
- 數據庫測試
- 模擬
- 官方擴展包
- Cashier(訂閱支付解決方案)
- Envoy(遠程操作解決方案)
- Horizon(隊列系統解決方案)
- Passport(API 認證解決方案)
- Scout(全文搜索解決方案)
- Socialite(第三方登錄解決方案)
- 相關下載
- Laravel 5.6 中文文檔離線版
- Laravel 5.6 一鍵安裝包