控制器
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
控制器
- 简介
- 编写控制器
- 基本控制器
- 单动作控制器
- 控制器中间件
- 资源控制器
- 部分资源路由
- 嵌套资源
- 命名资源路由
- 命名资源路由参数
- 限定资源路由范围
- 本地化资源URI
- 扩展资源控制器
- 单例资源控制器
- 依赖注入与控制器
简介
您可能希望使用「controller」类来组织请求处理逻辑,而不是将所有请求处理逻辑都定义为路由文件中的闭包。控制器可以将相关的请求处理逻辑分组到一个类中。 例如,一个 UserController
类可能会处理所有与用户相关的请求传入, 包括显示,创建, 更新和删除用户。默认情况下,控制器存储在 app/Http/Controllers
目录中。
编写控制器
基本控制器
如果要快速生成新控制器,可以使用 make:controller
Artisan 命令。默认情况下,应用程序的所有控制器都存储在 app/Http/Controllers
目录中:
php artisan make:controller UserController
让我们来看一个基本控制器的示例。控制器可以有任意数量的公共方法来响应传入的 HTTP 请求:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* 显示指定用户的配置文件
*/
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
编写控制器类和方法后,可以定义到控制器方法的路由,如下所示:
use App\Http\Controllers\UserController;
Route::get('/user/{id}', [UserController::class, 'show']);
当传入请求与指定的路由 URI 匹配时,将调用 App\Http\Controllers\UserController
类上的 show
方法,并将路由参数传递给该方法。
[!注意事项]
控制器不必继承基础类。然而,在所有控制器中共享某些方法时,继承一个基础控制器类会非常方便。
单动作控制器
如果某个控制器操作特别复杂,可以考虑为该单一操作创建一个独立的控制器类。实现这一目的的方法是在控制器内定义一个 __invoke
方法
<?php
namespace App\Http\Controllers;
class ProvisionServer extends Controller
{
/**
* 配置一个新的web服务器
*/
public function __invoke()
{
// ...
}
}
为单动作控制器注册路由时,无需指定控制器方法,只需将控制器名称传递给路由器:
use App\Http\Controllers\ProvisionServer;
Route::post('/server', ProvisionServer::class);
你可以使用 make:controller
Artisan 命令的 --invokable
选项生成可调用控制器
php artisan make:controller ProvisionServer --invokable
[!注意事项]
可通过 stub 定制来修改控制器模板
控制器中间件
中间件 可以在你的路由文件中分配给控制器的路由:
Route::get('/profile', [UserController::class, 'show'])->middleware('auth');
或者,你可能会发现直接在控制器类中指定中间件更为方便。为此,你的控制器应该实现 HasMiddleware
接口,该接口规定控制器应该有一个静态的 middleware 方法
。从这个方法中,你可以返回一个中间件数组,这些中间件应该应用于控制器的动作:
<?php
namespace App\Http\Controllers;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class UserController extends Controller implements HasMiddleware
{
/**
* 获取应分配给控制器的中间件。
*/
public static function middleware(): array
{
return [
'auth',
new Middleware('log', only: ['index']),
new Middleware('subscribed', except: ['store']),
];
}
// ...
}
控制器还允许你使用闭包注册中间件。这提供了一种方便的方法来为单个控制器定义内联中间件,而无需定义整个中间件类:
use Closure;
use Illuminate\Http\Request;
/**
* 获取应分配给控制器的中间件。
*/
public static function middleware(): array
{
return [
function (Request $request, Closure $next) {
return $next($request);
},
];
}
[!警告]]
实现Illuminate\Routing\Controllers\HasMiddleware
接口的控制器不应继承Illuminate\Routing\Controller
基类。
资源控制器
如果您将应用中的每个 Eloquent 模型视为一种「资源」, 那么通常会对每个资源执行相同的操作集合。例如,假设您的应用包含 Photo
模型和 Movie
模型,用户很可能需要对这两种资源执行创建、读取、更新或删除操作。
由于这种常见的使用场景,Laravel 的资源路由通过单行代码即可将典型的增删改查(「CURD」)路由分配给控制器。首先,我们可以使用 Artisan 命令 make:controller
的 --resource
选项来快速创建一个控制器:
php artisan make:controller PhotoController --resource
This command will generate a controller at app/Http/Controllers/PhotoController.php
. The controller will contain a method for each of the available resource operations. Next, you may register a resource route that points to the controller:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);
This single route declaration creates multiple routes to handle a variety of actions on the resource. The generated controller will already have methods stubbed for each of these actions. Remember, you can always get a quick overview of your application's routes by running the route:list
Artisan command.
You may even register many resource controllers at once by passing an array to the resources
method:
Route::resources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
Actions Handled by Resource Controllers
| 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 |
Customizing Missing Model Behavior
Typically, a 404 HTTP response will be generated if an implicitly bound resource model is not found. However, you may customize this behavior by calling the missing
method when defining your resource route. The missing
method accepts a closure that will be invoked if an implicitly bound model cannot be found for any of the resource's routes:
use App\Http\Controllers\PhotoController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
Route::resource('photos', PhotoController::class)
->missing(function (Request $request) {
return Redirect::route('photos.index');
});
Soft Deleted Models
Typically, implicit model binding will not retrieve models that have been soft deleted, and will instead return a 404 HTTP response. However, you can instruct the framework to allow soft deleted models by invoking the withTrashed
method when defining your resource route:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->withTrashed();
Calling withTrashed
with no arguments will allow soft deleted models for the show
, edit
, and update
resource routes. You may specify a subset of these routes by passing an array to the withTrashed
method:
Route::resource('photos', PhotoController::class)->withTrashed(['show']);
Specifying the Resource Model
If you are using route model binding and would like the resource controller's methods to type-hint a model instance, you may use the --model
option when generating the controller:
php artisan make:controller PhotoController --model=Photo --resource
Generating Form Requests
You may provide the --requests
option when generating a resource controller to instruct Artisan to generate form request classes for the controller's storage and update methods:
php artisan make:controller PhotoController --model=Photo --resource --requests
Partial Resource Routes
When declaring a resource route, you may specify a subset of actions the controller should handle instead of the full set of default actions:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->only([
'index', 'show'
]);
Route::resource('photos', PhotoController::class)->except([
'create', 'store', 'update', 'destroy'
]);
API Resource Routes
When declaring resource routes that will be consumed by APIs, you will commonly want to exclude routes that present HTML templates such as create
and edit
. For convenience, you may use the apiResource
method to automatically exclude these two routes:
use App\Http\Controllers\PhotoController;
Route::apiResource('photos', PhotoController::class);
You may register many API resource controllers at once by passing an array to the apiResources
method:
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;
Route::apiResources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);
To quickly generate an API resource controller that does not include the create
or edit
methods, use the --api
switch when executing the make:controller
command:
php artisan make:controller PhotoController --api
Nested Resources
Sometimes you may need to define routes to a nested resource. For example, a photo resource may have multiple comments that may be attached to the photo. To nest the resource controllers, you may use "dot" notation in your route declaration:
use App\Http\Controllers\PhotoCommentController;
Route::resource('photos.comments', PhotoCommentController::class);
This route will register a nested resource that may be accessed with URIs like the following:
/photos/{photo}/comments/{comment}
Scoping Nested Resources
Laravel's implicit model binding feature can automatically scope nested bindings such that the resolved child model is confirmed to belong to the parent model. By using the scoped
method when defining your nested resource, you may enable automatic scoping as well as instruct Laravel which field the child resource should be retrieved by. For more information on how to accomplish this, please see the documentation on scoping resource routes.
Shallow Nesting
Often, it is not entirely necessary to have both the parent and the child IDs within a URI since the child ID is already a unique identifier. When using unique identifiers such as auto-incrementing primary keys to identify your models in URI segments, you may choose to use "shallow nesting":
use App\Http\Controllers\CommentController;
Route::resource('photos.comments', CommentController::class)->shallow();
This route definition will define the following routes:
| 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 |
Naming Resource Routes
By default, all resource controller actions have a route name; however, you can override these names by passing a names
array with your desired route names:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class)->names([
'create' => 'photos.build'
]);
Naming Resource Route Parameters
By default, Route::resource
will create the route parameters for your resource routes based on the "singularized" version of the resource name. You can easily override this on a per resource basis using the parameters
method. The array passed into the parameters
method should be an associative array of resource names and parameter names:
use App\Http\Controllers\AdminUserController;
Route::resource('users', AdminUserController::class)->parameters([
'users' => 'admin_user'
]);
The example above generates the following URI for the resource's show
route:
/users/{admin_user}
Scoping Resource Routes
Laravel's scoped implicit model binding feature can automatically scope nested bindings such that the resolved child model is confirmed to belong to the parent model. By using the scoped
method when defining your nested resource, you may enable automatic scoping as well as instruct Laravel which field the child resource should be retrieved by:
use App\Http\Controllers\PhotoCommentController;
Route::resource('photos.comments', PhotoCommentController::class)->scoped([
'comment' => 'slug',
]);
This route will register a scoped nested resource that may be accessed with URIs like the following:
/photos/{photo}/comments/{comment:slug}
When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. In this case, it will be assumed that the Photo
model has a relationship named comments
(the plural of the route parameter name) which can be used to retrieve the Comment
model.
Localizing Resource URIs
By default, Route::resource
will create resource URIs using English verbs and plural rules. If you need to localize the create
and edit
action verbs, you may use the Route::resourceVerbs
method. This may be done at the beginning of the boot
method within your application's App\Providers\AppServiceProvider
:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
}
Laravel's pluralizer supports several different languages which you may configure based on your needs. Once the verbs and pluralization language have been customized, a resource route registration such as Route::resource('publicacion', PublicacionController::class)
will produce the following URIs:
/publicacion/crear
/publicacion/{publicaciones}/editar
Supplementing Resource Controllers
If you need to add additional routes to a resource controller beyond the default set of resource routes, you should define those routes before your call to the Route::resource
method; otherwise, the routes defined by the resource
method may unintentionally take precedence over your supplemental routes:
use App\Http\Controller\PhotoController;
Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);
[!NOTE]
Remember to keep your controllers focused. If you find yourself routinely needing methods outside of the typical set of resource actions, consider splitting your controller into two, smaller controllers.
Singleton Resource Controllers
Sometimes, your application will have resources that may only have a single instance. For example, a user's "profile" can be edited or updated, but a user may not have more than one "profile". Likewise, an image may have a single "thumbnail". These resources are called "singleton resources", meaning one and only one instance of the resource may exist. In these scenarios, you may register a "singleton" resource controller:
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::singleton('profile', ProfileController::class);
The singleton resource definition above will register the following routes. As you can see, "creation" routes are not registered for singleton resources, and the registered routes do not accept an identifier since only one instance of the resource may exist:
| Verb | URI | Action | Route Name | | --------- | --------------- | ------ | -------------- | | GET | `/profile` | show | profile.show | | GET | `/profile/edit` | edit | profile.edit | | PUT/PATCH | `/profile` | update | profile.update |
Singleton resources may also be nested within a standard resource:
Route::singleton('photos.thumbnail', ThumbnailController::class);
In this example, the photos
resource would receive all of the standard resource routes; however, the thumbnail
resource would be a singleton resource with the following routes:
| Verb | URI | Action | Route Name | | --------- | -------------------------------- | ------ | ----------------------- | | GET | `/photos/{photo}/thumbnail` | show | photos.thumbnail.show | | GET | `/photos/{photo}/thumbnail/edit` | edit | photos.thumbnail.edit | | PUT/PATCH | `/photos/{photo}/thumbnail` | update | photos.thumbnail.update |
Creatable Singleton Resources
Occasionally, you may want to define creation and storage routes for a singleton resource. To accomplish this, you may invoke the creatable
method when registering the singleton resource route:
Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable();
In this example, the following routes will be registered. As you can see, a DELETE
route will also be registered for creatable singleton resources:
| Verb | URI | Action | Route Name | | --------- | ---------------------------------- | ------- | ------------------------ | | GET | `/photos/{photo}/thumbnail/create` | create | photos.thumbnail.create | | POST | `/photos/{photo}/thumbnail` | store | photos.thumbnail.store | | GET | `/photos/{photo}/thumbnail` | show | photos.thumbnail.show | | GET | `/photos/{photo}/thumbnail/edit` | edit | photos.thumbnail.edit | | PUT/PATCH | `/photos/{photo}/thumbnail` | update | photos.thumbnail.update | | DELETE | `/photos/{photo}/thumbnail` | destroy | photos.thumbnail.destroy |
If you would like Laravel to register the DELETE
route for a singleton resource but not register the creation or storage routes, you may utilize the destroyable
method:
Route::singleton(...)->destroyable();
API 单例资源
apiSingleton
方法可用于注册将通过 API 操作的单例资源,从而不需要 create
和 edit
路由:
Route::apiSingleton('profile', ProfileController::class);
当然,API 单例资源也可以是 可创建的
,它将注册 `store 和 destroy 资源路由:
Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();
依赖注入和控制器
构造函数注入
Laravel 服务容器 用于解析所有 Laravel 控制器。因此,可以在其构造函数中对控制器可能需要的任何依赖项进行类型提示。声明的依赖项将自动解析并注入到控制器实例中:
<?php
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
/**
* 创建新控制器实例。
*/
public function __construct(
protected UserRepository $users,
) {}
}
方法注入
除了构造函数注入,还可以在控制器的方法上键入提示依赖项。方法注入的一个常见用例是将 Illuminate\Http\Request
实例注入到控制器方法中:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 存储新用户。
*/
public function store(Request $request): RedirectResponse
{
$name = $request->name;
// 存储用户...
return redirect('/users');
}
}
如果控制器方法也需要路由参数,那就在其他依赖项之后列出路由参数。例如,路由是这样定义的:
use App\Http\Controllers\UserController;
Route::put('/user/{id}', [UserController::class, 'update']);
如下所示,你依然可以类型提示 Illuminate\Http\Request
并通过定义您的控制器方法访问 id
参数:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* 更新给定用户。
*/
public function update(Request $request, string $id): RedirectResponse
{
// 更新用户...
return redirect('/users');
}
}
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: