我們的業務程式碼不能都寫在路由檔案(比如:routes/web.php
)裡。路由的形式有兩種:基於閉包和基於控制器的。我們最好把一些相關的路由請求放在一個控制器內管理,對應的,業務程式碼也轉移到了控制器裡。Larave 的控制器位於 app/Http/Controllers
目錄下。
基礎控制器
定義控制器
下面,就是一個基礎控制器。這個控制器繼承了 Laravel 提供的基類控制器。在基類控制器中提供了幾個便捷的方法可以使用,比如 middleware
這個方法,就是用來給 控制器 action 附加中介軟體的。
<?php
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
class UsersController extends Controller
{
/**
* Show the profile for the given user.
*
* @param int $id
* @return Response
*/
public function show($id)
{
return view('users.profile', ['user' => User::findOrFail($id)]);
}
}
指向這個控制器 action 的路由是這樣定義的:
Route::get('users/{id}', 'UserController@show');
當瀏覽器發起 users/xxx
路徑請求的時候,UsersController
的 show
方法就會被呼叫,路由引數也會傳遞給這個方法。
需要注意的是, 我們的控制器不是非要繼承基類控制器的,只不過,如果不繼承基類控制器的話,一些非常好用的方法,比如 middleware
、validate
和 dispatch
就無法使用了。
控制器 & 名稱空間
細心觀察的話會發現,我們在定義控制器路由的時候,並沒有使用控制器的完整名稱空間。這是因為在 RouteServiceProvider
中,已經為 routes/web.php
和 routes/api.php
檔案中的所有路由預設了 App\Http\Controllers
的名稱空間字首,所以我們在定義控制器路由時,只需指定 App\Http\Controllers
後面那部分的內容即可。
如果 App\Http\Controllers
目錄中的控制器有更深的巢狀層級,比如,我們有一個控制器的完整路徑是 App\Http\Controllers\Photos\AdminController
,那麼在註冊控制器路由的時候,就要像這樣:
Route::get('foo', 'Photos\AdminController@method');
單 Action 控制器
有時,一個控制器只有唯一的一個方法,那麼這時只要在控制器中定義一個 __invoke
方法就行:
<?php
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
class ShowProfile extends Controller
{
/**
* Show the profile for the given user.
*
* @param int $id
* @return Response
*/
public function __invoke($id)
{
return view('users.profile', ['user' => User::findOrFail($id)]);
}
}
指向這個單 Action 控制器的路由,定義起來就變成下面這樣了:
Route::get('users/{id}', 'ShowProfile');
控制器中介軟體
在我們的路由檔案裡,可以為控制器路由指定中介軟體:
Route::get('profile', 'UsersController@show')->middleware('auth');
但是,在控制器構造方法裡指定中介軟體更加方便,直接使用 middleware
方法,就可以輕鬆地為我們的控制器 action 分配中介軟體了:
class UsersController extends Controller
{
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('log')->only('index');
$this->middleware('subscribed')->except('store');
}
}
在控制器中也可以使用閉包來註冊一箇中介軟體。這為只在一個控制器中使用的中介軟體帶來便利,無需定義一箇中介軟體類了:
$this->middleware(function ($request, $next) {
// ...
return $next($request);
});
我們有時會對控制器的一個 action 子集做中介軟體處理。但這可能說明你的控制器是臃腫的,嘗試去把這個控制器拆分成多個、職能更加單一的控制器吧。
資源控制器
資源控制器就是指包含 index
、create
、store
、show
、edit
、update
和 destroy
這 7 個方法的控制器。之所以稱為「資源控制器」,是因為這 7 個方法包括了操作某個資源所需的全部基本操作,也就是 CRUD 操作。
在 Laravel 中,在使用 Artisan 命令 make:controller
時,通過傳遞一個額外選項 --resource
就可以快速建立這樣一個資源控制器,我們以操作圖片資源的 PhotosController
為例:
php artisan make:controller PhotosController --resource
這樣就在 app/Http/Controllers/PhotosController.php
的地方建立了一個控制器,接下來在為資源控制器註冊資源路由。
Route::resource('photos', 'PhotosController');
通過這一句程式碼,我們就定義了 7 個指向到 PhotosController
中相應方法的所有路由。我們來看一下:
對應到資源控制器相應方法的 7 個資源路由。
請求型別 | URI | 控制器 Action | 路由別名 |
---|---|---|---|
GET | /photos | index | photos.index |
GET | /photos/create | create | photos.create |
POST | /photos | store | photos.store |
GET | /photos/{photo} | show | photos.show |
GET | /photos/{photo}/edit | edit | photos.edit |
PUT/PATCH | /photos/{photo} | update | photos.update |
DELETE | /photos/{photo} | destroy | photos.destroy |
指定資源模型
在生成資源控制器的時候,還可以通過指定 -model
選項,為控制器中的方法指定模型型別。
php artisan make:controller --resource --model=Photo
表單方法偽造
由於 HTML 表單不能發出 PUT
、PATCH
和 DELETE
型別的請求,所以想要使用這些請求型別的話,就必須得偽造。Laravel 中使用隱藏表單域 _method
來告訴應用程式我們的請求型別。我們可以通過 method_field
輔助函式來生成這個表單域:
{{ method_field('PUT') }}
或者手動新增:
<input name="_method" type="hidden" value="PUT">
都是一樣的。
部分資源路由
我們在使用 Route::resource
定義資源路由的時候,並不想生成全部 7 個路由,比如只需要定義其中的 2 個或者 4 個路由,Route::resource
同樣可以滿足這樣的需求,這時需要為它指定第三個引數:
Route::resource('photos', 'PhotosController', ['only' => [
'index', 'show'
]]);
Route::resource('photos', 'PhotosController', ['except' => [
'create', 'store', 'update', 'destroy'
]]);
命名資源路由
預設,所有的資源路由都有一個名字,當然你也可以按情況覆蓋掉相應方法。這是需要用到 names
這個陣列選項:
Route::resource('photos', 'PhotosController', ['names' => [
'create' => 'photos.build'
]]);
命名資源路由引數
預設,所有的資源路由的路由引數是使用資源名的「單數」形式(如果資源名本身是單數形式,那麼路由引數直接使用單數形式)。
Route::resource('users', 'AdminUserController');
// 生成的 `show` 路由
/users/{user}
Route::resource('user', 'AdminUserController');
// 生成的 `show` 路由
/user/{user}
當然,你也可以覆蓋這個約定。這時要用到 parameters
這個陣列選項:
Route::resource('users', 'AdminUserController', ['parameters' => [
'users' => 'admin_user'
]]);
// 生成的 `show` 路由
/users/{admin_user}
本地化資源 URIs
預設,Route::resource
生成的路由地址中包含 create
和 edit
這兩個英語動詞。如果需要本地化這兩個動詞,需要使用 Route::resourceVerbs
方法,這個方法可以在 AppServiceProvider
的 boot
方法裡設定:
use Illuminate\Support\Facades\Route;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
}
定義好後,使用 Route::resource('fotos', 'PhotoController')
生成的資源路由會產生下面兩個的 URIs:
/fotos/crear
/fotos/{foto}/editar
fotos、crear 和 editar 是西班牙語裡的照片、建立和編輯。
補充資源控制器
如果你還要定義除了預設 7 個資源路由之外的其他路由,那麼你應該在呼叫 Route::resource
之前定義這些路由,否則通過 resource
方法定義的路由可能會意外覆蓋你額外新增的路由:
Route::get('photos/popular', 'PhotoController@method');
Route::resource('photos', 'PhotoController');
記住,要保證你的控制器職能單一,如果發現除了一些在資源控制器中普遍存在的方法之外,還需要定義一些其他的方法,那麼請考慮將控制器拆分成兩個或者多個更小的控制器。
依賴注入 & 控制器
通過構造方法注入
Laravel 中的控制器都是使用服務容器解析的。所以,你可以在控制器的構造方法裡使用依賴注入。宣告的依賴會自動注入到控制器例項中:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UsersController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*
* @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 UsersController extends Controller
{
/**
* Store a new user.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$name = $request->name;
//
}
}
如果路由包含引數,那麼這些引數要列在這些依賴項之後。例如,如果路由是這樣的:
Route::put('users/{id}', 'UsersController@update');
在控制器方法裡,就要在 Illuminate\Http\Request
依賴宣告之後,獲取我們們的使用者 ID:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UsersController extends Controller
{
/**
* Update the given user.
*
* @param Request $request
* @param string $id
* @return Response
*/
public function update(Request $request, $id)
{
//
}
}
路由快取
先宣告!使用閉包形式定義的路由時無法快取的。要想用快取,必須將閉包路由轉換為控制器路由。
如果應用程式中,全部使用的是基於控制器方法的路由,那麼這時候就可以充分利用 Laravel 路由快取的功能了!使用路由快取可以大大地減少應用程式中註冊所有路由花費的時間。有些情況,註冊路由的過程可能會提升 100 倍!生成路由快取,執行 Artisan 命令 route:cache
即可:
php artisan route:cache
執行這條命令後,路由快取檔案會在每次請求時被載入。需要注意的是,應用程式中每當新增了一個新的路由,都需要重新執行 route:cache
命令方可生效。因此,你應該只在專案部署時才執行 route:cache
命令。
清除路由快取使用 route:clear
就 OK 了!
php artisan route:clear