Laravel 控制器
本章目录
简介
我们不希望将所有的请求逻辑都定义为路由文件中的Closures,而是希望使用Controller类来实现这些逻辑。 Controller可以将相关的请求处理逻辑封装为一个类。 控制器存储在app/Http/Controllers
目录中。
控制器基础
定义控制器
下面是一个基本控制器的示例。 请注意,该控制器继承了Laravel的基本控制器类Controller
。 基类提供了一些便利的方法,例如中间件方法,该方法可用于将中间件附加到控制器的action上:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\User;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*
* @param int $id
* @return View
*/
public function show($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
我们可以使用下面代码定义一个路由到控制器的映射
Route::get('user/{id}', 'UserController@show');
现在,当有一个和上面的路由匹配的请求到来时,UserController
控制器中的show
方法就会执行。同时,路由的参数也会传给show
方法
我们定义的控制器类不是必须要继承Controller这个控制器基类的。只是如果我们不继承的话,就不能很方便的使用
middleware
,validate
和dispatch
这些方法。
控制器和命名空间
在定义控制器路由时,我们不需要指定完整的控制器的命名空间。 由于RouteServiceProvider
将我们的路由文件加载到包含命名空间的路由组中,因此我们仅指定了类名称中位于命名空间的App\Http\Controllers
部分之后的部分。
如果选择将控制器更深地嵌套到App\Http\Controllers
目录中,请使用相对于App\Http\Controllers
根命名空间的特定类名称。 因此,如果完整的控制器类为App\Http\Controllers\Photos\AdminController
,则应像以下那样注册控制器的路由:
Route::get('foo', 'Photos\AdminController@method');
单一操作的控制器
如果我们想定义只有一种操作的控制器,可以在控制器中定义__invoke
方法
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\User;
class ShowProfile extends Controller
{
/**
* Show the profile for the given user.
*
* @param int $id
* @return View
*/
public function __invoke($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
当为这种单一操作的控制器分配路由的时候,不需要指定控制器中的方法名,只需要指定控制器类名即可
Route::get('user/{id}', 'ShowProfile');
我们可以在Artisan命令make:controller
后面加上 --invokable
参数来生成这种具有单一操作的控制器
$ php artisan make:controller ShowProfile --invokable
控制器中间件
中间件也可以分配给定义的控制器的路由
Route::get('profile', 'UserController@show')->middleware('auth');
除此之外,还可以在控制器的构造方法__construct()
中使用middleware
函数指定该控制器的中间件。甚至我们也可以使用only
方法为控制器中特定的某些方法加上中间件
class UserController 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');
}
}
控制器还允许我们使用Closure注册中间件。 这提供了一种为单个控制器定义中间件的便捷方法,而无需定义整个中间件类:
$this->middleware(function ($request, $next) {
// ...
return $next($request);
});
您可以将中间件分配给控制器操作的子集; 但是,这在另一方面表明我们的控制器过大。 要考虑将控制器分成多个较小的控制器。
资源控制器
Laravel资源路由通过单行代码将经典的“ CRUD”路由分配给控制器。 例如,我们可能希望创建这样的一个控制器,该控制器处理应用程序存储的所有针对“photos”的HTTP请求。 使用Artisan命令make:controller
,我们可以快速的创建这样的控制器:
$ php artisan make:controller PhotoController --resource
上面的命令将在app/Http/Controllers/PhotoController.php
中创建一个PhotoController
控制器,控制器将为每个可用资源的操作包含一个方法。
下面我们将为这个控制器注册一个路由
Route::resource('photos', 'PhotoController');
上面单个路由声明创建了多个路由来处理资源上的各种操作。 生成的控制器已经为每个操作添加了方法,包括向我们通知它们所处理的HTTP动词和URI。
我们可以通过数组的方式一次性注册多个资源控制器路由
Route::resources([
'photos' => 'PhotoController',
'posts' => 'PostController',
]);
Verb | URI | Action | Route Name |
---|---|---|---|
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
如果使用路由模型绑定,并且希望资源控制器的方法指定一个模型实例,则可以在生成控制器时使用--model
选项:
$ php artisan make:controller PhotoController --resource --model=Photo
隐藏Form的提交方式
由于HTML表单无法发出PUT,PATCH或DELETE请求,因此我们需要添加一个隐藏的_method
字段来伪造这些HTTP动词。 @method Blade
指令可以创建此字段:
<form action="/foo/bar" method="POST">
@method('PUT')
</form>
分解资源路由
当我们定义资源路由的时候,我们可能希望对该资源控制器的一部分操作控制器可以单独的执行,不用执行一遍该控制器的一系列的action。可以通过下面的方式实现
Route::resource('photos', 'PhotoController')->only([
'index', 'show'
]);
Route::resource('photos', 'PhotoController')->except([
'create', 'store', 'update', 'destroy'
]);
Api 资源路由
声明将由API使用的资源路由时,通常会希望排除呈现HTML模板(例如create
和edit
)的路由。 为了方便起见,我们可以使用apiResource
方法自动排除这两个路由:
Route::apiResource('photos', 'PhotoController');
同样,我们可以通过数组一次性注册多个Api资源路由
Route::apiResources([
'photos' => 'PhotoController',
'posts' => 'PostController',
]);
如果想快速的生成一个Api资源控制器,该控制器不带create
和edit
方法,可以在Artisan命令make:controller
后面添加--api
参数
$ php artisan make:controller API/PhotoController --api
嵌套的资源
有时我们可能需要定义到嵌套资源的路由。 例如,照片资源具有可以附加到照片的多个注释。 要嵌套资源控制器,请在路由声明中使用“.
”表示法:
Route::resource('photos.comments', 'PhotoCommentController');
该路由将注册一个嵌套资源,该资源可以通过如下所示的URI进行访问:
/photos/{photo}/comments/{comment}
浅层嵌套
通常,在URI中同时包含父ID和子ID并不是完全必要的,因为子ID已经是唯一的标识符。 当使用诸如自动递增主键之类的唯一标识符来标识URI段中的模型时,我们可以选择使用“浅层嵌套”:
Route::resource('photos.comments', 'CommentController')->shallow();
上面的路由定义,可以生成下面这些路由
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /photos/{photo}/comments |
index | photos.comments.index |
GET | /photos/{photo}/comments/create |
create | photos.comments.create |
POST | /photos/{photo}/comments |
store | photos.comments.store |
GET | /comments/{comment} |
show | comments.show |
GET | /comments/{comment}/edit |
edit | comments.edit |
PUT/PATCH | /comments/{comment} |
update | comments.update |
DELETE | /comments/{comment} |
destroy | comments.destroy |
给资源路由的参数命名
默认情况下,Route::resource
将基于资源名称的“单数”模式为我们的资源路由创建路由参数。 我们可以使用parameters
方法轻松地在每个资源的基础上重写此方法。 传递给parameters
方法的数组应该是资源名称和参数名称的关联数组:
Route::resource('users', 'AdminUserController')->parameters([
'users' => 'admin_user'
]);
上面的示例为资源的显示路线生成以下URI
/users/{admin_user}
资源路由的作用范围
有时,当在资源路由定义中隐式绑定多个Eloquent模型时,您可能希望对第二个Eloquent模型进行范围划分,使其必须是第一个Eloquent模型的子作用域。 例如,考虑这种情况,该情况是通过Slug为特定用户检索博客文章的:
use App\Http\Controllers\PostsController;
Route::resource('users.posts', PostsController::class)->scoped();
我们可以通过将数组传递给scoped
方法,以此来覆盖默认的模型路由的键名:
use App\Http\Controllers\PostsController;
Route::resource('users.posts', PostsController::class)->scoped([
'post' => 'slug',
]);
当使用自定义键隐式绑定作为嵌套的路由参数时,Laravel将自动确定范围,以使用一些约定的规则来猜测其父级上的关系名称,从而由其父级检索嵌套模型。 在这种情况下,将假定用户模型具有一个名为post(路由参数名称的复数)的关系,该关系可用于检索Post模型。
本地化资源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' => 'crear',
'edit' => 'editar',
]);
}
一旦对动词进行了自定义,诸如Route::resource('fotos','PhotoController')
之类的资源路由注册将产生以下URI:
/fotos/crear
/fotos/{foto}/editar
补充资源控制器
如果您需要向资源控制器添加默认路由以外的其他路由,则应在调用Route::resource
之前定义这些路由。 否则,由resource方法定义的路由可能会无意中优先于我们要添加的路由:
Route::get('photos/popular', 'PhotoController@method');
Route::resource('photos', 'PhotoController');
记住要使我们的控制器尽量功能单一。 如果发现自己通常需要一组典型的资源操作之外的方法,请考虑将控制器分为两个较小的控制器。
依赖注入和控制器
构造器注入
Laravel Service Container用于解析所有Laravel控制器。 因此,我们可以在控制器的构造方法中对任何依赖进行类型限定。 声明的依赖项将自动解析并注入到控制器实例中:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController 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;
}
}
如果容器中可以解析接口,我们也可以使用接口进行类型限定,只要是实现了该接口的实例都可以注入到控制器的构造方法中。
方法中注入
除了可以在控制器的构造方法中进行注入之外,还可以在控制器的自定义方法中进行注入。一个典型的应用示例就是在方法中注入Illuminate\Http\Request
的实例。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Store a new user.
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$name = $request->name;
//
}
}
如果我们的控制器方法也希望从route传入参数,可以在其他依赖项之后列出route参数。 例如,如果路由是这样定义的:
Route::put('user/{id}', 'UserController@update');
我们仍然可以通过如下定义控制器方法,键入Illuminate\Http\Request
并访问id参数:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController 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:clear
命令清除路由缓存
php artisan route:clear