[TOC]
# 簡介
我們之前的演示示例都是將所有的請求處理邏輯放在路由文件的閉包函數中,這顯然是不合理的,我們需要使用控制器類組織管理相對復雜的業務邏輯處理。控制器用于將相關的 HTTP 請求封裝到一個類中進行處理,這些控制器類存放在 `app/Http/Controllers` 目錄下。
# 控制器入門
## 定義控制器
下面是一個基本控制器類的例子。首先我們使用 Artisan 命令快速創建一個控制器:
~~~
php artisan make:controller UserController
~~~
所有的 Laravel 控制器應該繼承自 Laravel 自帶的控制器基類 `App\Http\Controllers\Controller`,我們為該控制器添加一個 `show` 方法:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 為指定用戶顯示詳情
*
* @param int $id
* @return Response
* @author LaravelAcademy.org
*/
public function show($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
~~~
我們可以像這樣定義指向該控制器動作的路由:
~~~
Route::get('user/{id}', 'UserController@show');
~~~
現在,如果一個請求匹配上面的路由 URI,`UserController` 的 `show` 方法就會被執行,當然,路由參數也會被傳遞給這個方法。此外,這里 `show` 方法里面還用到了 `view` 方法,該方法用于將 `user` 變量渲染到 `user/profile` 視圖中,后面我們講視圖的時候會繼續討論該方法的使用,現在我們只是做簡單演示,在 `resources/veiws` 目錄下創建 `user` 子目錄,然后在 `user` 目錄下新建 `profile.php` 文件,編輯文件內容如下:
:-: 
這樣我們在瀏覽器中訪問 `http://blog.test/user/1`,就會看到打印結果了:
:-: 
> 注:控制器并不是一定要繼承自基類,不過,那樣的話就不能使用一些基類提供的便利方法了,比如 middleware、validate 和 dispatch 等,后面我們慢慢接觸和了解到這些方法的使用。
## 命名空間
你應該注意到我們在定義控制器路由的時候沒有指定完整的控制器命名空間,而只是定義了 `App\Http\Controllers` 之后的部分,那為什么可以這么做呢?這是因為默認情況下,`RouteServiceProvider` 將會在一個指定了控制器所在命名空間的路由分組中載入路由文件,故而我們只需指定后面相對命名空間即可:
:-: 
這里的 `$this->namespace` 就是 `App\Http\Controllers`。
如果你在 `App\Http\Controllers` 目錄下選擇使用 PHP 命名空間嵌套或組織控制器,只需要使用相對于 `App\Http\Controllers` 命名空間的指定類名即可。因此,如果你的完整控制器類是 `App\Http\Controllers\Photos\AdminController`,則可以像這樣注冊路由:
~~~
Route::get('foo', 'Photos\AdminController@method');
~~~
## 單動作控制器
如果你想要定義一個只處理一個動作的控制器,可以在這個控制器中定義 `__invoke` 方法:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
class ShowProfile extends Controller
{
/**
* 展示給定用戶的個人主頁
*
* @param int $id
* @return Response
*/
public function __invoke($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
~~~
當你為這個單動作控制器注冊路由的時候,不需要指定方法:
~~~
Route::get('user/{id}', 'ShowProfile');
~~~
這背后的原理是在 PHP 中當嘗試以調用函數的方式調用一個對象時,`__invoke()` 方法會被自動調用。
# 控制器中間件
`中間件`可以像這樣分配給控制器路由:
~~~
Route::get('profile', 'UserController@show')->middleware('auth');
~~~
不過,將中間件放在控制器構造函數中更方便,在控制器的構造函數中使用 `middleware` 方法你可以很輕松地分配中間件給該控制器(該方法繼承自控制器基類),這樣該中間件對所有控制器方法都生效:
~~~
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function __construct()
{
$this->middleware('token');
}
/**
* @param $id
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @author LaravelAcademy.org
*/
public function show($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
~~~
這里我們在構造函數中聲明使用 `token` 中間件(關于該中間件定義參考`中間件`這篇文檔),這樣當我們訪問 `http://blog.test/user/1` 的時候,就會跳轉到 Laravel 學院,只有當訪問 `http://blog.test/user/1?token=laravelacademy.org` 時,才能訪問到正確的頁面。
除此之外,我們還可以指定中間件對指定方法生效或者排除指定方法的校驗:
~~~
$this->middleware('auth')->only('show'); // 只對該方法生效
$this->middleware('auth')->except('show'); // 對該方法以外的方法生效
~~~
如果要指定多個控制器方法可以以數組的方式傳參:
~~~
$this->middleware('auth')->only(['show', 'index']); // 只對指定方法生效
$this->middleware('auth')->except(['show', 'index']); // 對指定方法以外的方法生效
~~~
在控制器中還可以使用閉包注冊中間件,這為我們定義只在某個控制器使用的中間件提供了方便,無需定義完整的中間件類:
~~~
$this->middleware(function ($request, $next) {
// ...
return $next($request);
});
~~~
還是以 `UserController` 為例,我們為其定義一個匿名中間件:
:-: 
這樣當我們訪問 `http://blog.test/user/1` 會拋出 404 異常,只有當訪問 `http://blog.test/user/1?id=1` 時才能正常展示。
> 注:你還可以將中間件分配給多個控制器動作,不過,這意味著你的控制器會變得越來越臃腫,這種情況下,需要考慮將控制器分割成多個更小的控制器。
# 資源控制器
Laravel 的資源控制器可以讓我們很便捷地構建基于資源的 RESTful 控制器,例如,你可能想要在應用中創建一個控制器,用于處理關于文章存儲的 HTTP 請求,使用 Artisan 命令 `make:controller`,我們可以快速創建這樣的控制器:
~~~
php artisan make:controller PostController --resource
~~~
該 Artisan 命令將會生成一個控制器文件 `app/Http/Controllers/PostController.php`,這個控制器包含了每一個資源操作對應的方法:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
~~~
接下來,可以通過 `resource` 方法為該控制器注冊一個資源路由:
~~~
Route::resource('posts', 'PostController');
~~~
這個路由聲明包含了處理文章資源對應動作的多個路由,相應地,Artisan
生成的控制器也已經為這些動作設置了對應的處理方法。
你可以通過傳遞數組到 `resources` 方法從而一次注冊多個資源控制器:
~~~
Route::resources([
'photos' => 'PhotoController',
'posts' => 'PostController'
]);
~~~
**資源控制器處理的動作**
| 請求方式 | URI路徑 | 控制器方法 | 路由名稱 |
| --- | --- | --- | --- |
| GET | /posts | index | posts.index |
| GET | /posts/create | create | posts.create |
| POST | /posts | store | posts.store |
| GET | /posts/{post} | show | posts.show |
| GET | /posts/{post}/edit | edit | posts.edit |
| PUT/PATCH | /posts/{post} | update | posts.update |
| DELETE | /posts/{post} | destroy | posts.destroy |
**指定資源模型**
如果你使用了路由模型綁定,并且想要在資源控制器的方法中對模型實例進行依賴注入,可以在生成控制器的使用使用 `--model` 選項:
~~~
php artisan make:controller PostController --resource --model=Post
~~~
不過學院君個人不推薦使用這種模型綁定,因為這里會涉及到對模型數據的緩存邏輯,為性能考慮,我們不想總是從數據庫取數據,所以,盡量保持單個功能的簡單和單一職責,讓開發者自己去組裝需要的功能,這是 Unix 奉行的設計哲學,也是我們在系統設計的時候需要考量的重要因素。
**偽造表單方法**
由于 HTML 表單不支持發起 `PUT`、`PATCH` 和 `DELETE` 請求,需要添加一個隱藏的 `_method` 字段來偽造 HTTP 請求方式,Blade 指令 `@method` 可以幫我們做這件事:
~~~
<form action="/foo/bar" method="POST">
@method('PUT')
</form>
~~~
## 部分資源路由
聲明資源路由時可以指定該路由處理的動作子集:
~~~
Route::resource('post', 'PostController', ['only' =>
['index', 'show']
]);
Route::resource('post', 'PostController', ['except' =>
['create', 'store', 'update', 'destroy']
]);
~~~
**API 資源路由**
聲明被 API 消費的資源路由時,你可能需要排除展示 HTML 模板的路由,如 `create` 和 `edit`,為了方便起見,Laravel 提供了 `apiResource` 方法自動排除這兩個路由:
~~~
Route::apiResource('post', 'PostController');
~~~
同樣,你可以傳遞數組到 `apiResources` 方法從而一次注冊多個 API 資源控制器:
~~~
Route::apiResources([
'posts' => 'PostController',
'photos' => 'PhotoController'
]);
~~~
要想快速生成不包含 `create` 或 `edit` 方法的 API 資源控制器,可以在執行 `make:controller` 命令時使用 `--api` 開關:
~~~
php artisan make:controller API/PostController --api
~~~
## 命名資源路由
默認情況下,所有資源控制器動作都有一個路由名稱,不過,我們可以通過傳入 `names` 數組來覆蓋這些默認的名稱:
~~~
Route::resource('post', 'PostController', ['names' =>
['create' => 'post.build']
]);
~~~
## 命名資源路由參數
默認情況下,`Route::resource` 將會基于資源名稱的單數格式為資源路由創建路由參數,你可以通過在選項數組中傳遞 `parameters` 來覆蓋這一默認設置。 `parameters` 是資源名稱和參數名稱的關聯數組:
~~~
Route::resource('user', 'AdminUserController', ['parameters' => [
'user' => 'admin_user'
]]);
~~~
上面的示例代碼會為資源的 `show` 路由生成如下 URL:
~~~
/user/{admin_user}
~~~
## 本地化資源 URI
默認情況下,`Route::resource` 創建的資源 URI 是英文風格的,如果你需要本地化 `create` 和 `edit` 請求路由,可以使用 `Route::resourceVerbs` 方法。該功能可以在 `AppServiceProvider` 的 `boot` 方法中實現:
~~~
use Illuminate\Support\Facades\Route;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Route::resourceVerbs([
'create' => 'xinzeng',
'edit' => 'bianji',
]);
}
~~~
定制化請求方式完成后,注冊資源路由如 `Route::resource('wenzhang', 'PostController') `將會生成如下 URI:
~~~
/wenzhang/xinzeng
/wenzhang/{wenzhang}/bianji
~~~
好吧,你可以看出來,我是用拼音的方式對資源路由進行了本地化設置。
## 補充資源控制器
如果需要在默認資源路由之外添加額外的路由到資源控制器,應該在調用 `Route::resource` 之前定義這些路由,否則,通過 `resource` 方法定義的路由可能無意中覆蓋掉補充的額外路由:
~~~
Route::get('posts/popular', 'PostController@method');
Route::resource('posts', 'PostController');
~~~
> 注:注意保持控制器的單一職責,如果你發現指向控制器動作的路由超過默認提供的資源控制器動作集合了,考慮將你的控制器分割成多個更小的控制器。
# 依賴注入
## 構造函數注入
Laravel 使用`服務容器`解析所有的 Laravel 控制器,因此,可以在控制器的構造函數中注入任何依賴,這些依賴會被自動解析并注入到控制器實例中:
~~~
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* 創建新的控制器實例
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
}
~~~
當然,你還可以注入任何 `Laravel 契約`,如果容器可以解析,就可以進行依賴注入。注入依賴到控制器可以讓應用更加易于測試,同時也更加方便使用。
## 方法注入
除了構造函數注入之外,還可以在控制器的動作方法中進行依賴注入,例如,我們可以在某個方法中注入 `Illuminate\Http\Request` 實例:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 存儲新用戶
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$name = $request->name;
//
}
}
~~~
如果控制器方法期望輸入路由參數,只需要將路由參數放到其他依賴之后,例如,如果你的路由定義如下:
~~~
Route::put('user/{id}', 'UserController@update');
~~~
則需要以如下方式定義控制器方法來注入 `Illuminate\Http\Request` 依賴并訪問路由參數 `id`:
~~~
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 更新指定用戶
*
* @param Request $request
* @param int $id
* @return Response
* @translator http://laravelacademy.org
*/
public function update(Request $request, $id)
{
//
}
}
~~~
關于服務容器和依賴注入,我們后面在服務容器部分會詳細闡述,這里我們只需要了解可以這么使用就可以了。
# 路由緩存
> 注:路由緩存不會作用于基于閉包的路由。要使用路由緩存,必須將閉包路由轉化為控制器路由。
如果你的應用完全基于控制器路由,可以使用 Laravel 的路由緩存,使用路由緩存將會極大降低注冊所有應用路由所花費的時間開銷,在某些案例中,路由注冊速度甚至能提高100倍!想要生成路由緩存,只需執行 Artisan 命令 `route:cache`:
~~~
php artisan route:cache
~~~
運行完成后,每次請求都會從緩存中讀取路由,所以如果你添加了新的路由需要重新生成路由緩存。因此,只有在項目部署階段才需要運行 `route:cache` 命令,本地開發環境完全無此必要。
想要移除緩存路由文件,使用 `route:clear` 命令即可:
~~~
php artisan route:clear
~~~
- 序言
- 新版特性
- 快速入門
- 升級指南
- 貢獻指南
- 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 一鍵安裝包