翻译进度
11
分块数量
6
参与人数

控制器

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


控制器

  • 简介
  • 编写控制器
    • 基本控制器
    • 单动作控制器
  • 控制器中间件
  • 资源控制器
    • 部分资源路由
    • 嵌套资源
    • 命名资源路由
    • 命名资源路由参数
    • 限定资源路由范围
    • 本地化资源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)
        ]);
    }
}
vaexm 翻译于 1个月前

编写控制器类和方法后,可以定义到控制器方法的路由,如下所示:

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');
vaexm 翻译于 1个月前

或者,你可能会发现直接在控制器类中指定中间件更为方便。为此,你的控制器应该实现 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
vaexm 翻译于 4周前

此命令将在 app/Http/Controllers/PhotoController.php 处生成一个控制器。该控制器将包含每个可用资源操作的方法。接下来,你可以注册指向该控制器的资源路由:

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class);

此单一路由声明会创建多个路由来处理资源上的各种操作。生成的控制器已经为这些操作中的每一个操作存根了方法。请记住,你始终可以通过运行 route:list Artisan 命令来快速浏览应用程序的路由。

你甚至可以通过将数组传递给 resources 方法来一次注册多个资源控制器:

Route::resources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);

资源控制器处理的操作

| 请求方式 | URI链接 | 动作 | 路由名称 | | --------- | ---------------------- | ------- | -------------- | | 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 |

自定义缺失模型行为

通常,如果未找到隐式绑定的资源模型,则将生成 404 HTTP 响应。但是,你可以在定义资源路由时通过调用 missing 方法来自定义义此行为。 missing 方法接受一个闭包,如果无法为任何资源的路由找到隐式绑定的模型,则会调用该闭包:

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');
    });
vaexm 翻译于 2周前

软删除模型

通常,隐式模型绑定不会检索已被 软删除 , 并将返回 404 HTTP 响应。但是,你可以通过在定义资源路由时调用 withTrashed 方法来指示框架允许软删除模型:

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->withTrashed();

调用不带参数的 withTrashed 将允许 showeditupdate 资源路由软删除模型。你可以通过将数组传递给 withTrashed 方法来指定这些路由的子集:

Route::resource('photos', PhotoController::class)->withTrashed(['show']);

指定资源模型

如果你正在使用 路由绑定模型 并且希望资源控制器的方法类型提示模型实例,则可以在生成控制器时使用 --model 选项:

php artisan make:controller PhotoController --model=Photo --resource

生成表单请求

你可以在生成资源控制器时提供 --requests 选项,以指示 Artisan 为控制器的存储和更新方法生成 表单请求类

php artisan make:controller PhotoController --model=Photo --resource --requests

部分资源路由

当声明资源路由时,你可以指定控制器应该处理的操作子集,而不是完整的默认操作集:

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->only([
    'index', 'show'
]);

Route::resource('photos', PhotoController::class)->except([
    'create', 'store', 'update', 'destroy'
]);
vaexm 翻译于 1周前

API 资源路由

当声明用于 API 的资源路由时,通常需要排除显示 HTML 模板的路由,例如 createedit。为了方便,你可以使用 apiResource 方法来排除这两个路由:

use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);

你也可以传递一个数组给 apiResources 方法来同时注册多个 API 资源控制器:

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;

Route::apiResources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);

要快速生成不包含 createedit 方法的 API 资源控制器,你可以在执行 make:controller 命令时使用 --api 参数:

php artisan make:controller PhotoController --api

嵌套资源

S有时可能需要定义一个嵌套的资源型路由。例如,照片资源可能被添加了多个评论。那么可以在路由中使用 . 符号来声明资源型控制器:

use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class);

该路由会注册一个嵌套资源,可以使用如下 URI 访问:

/photos/{photo}/comments/{comment}

限定嵌套资源的范围

Laravel 的 隐式模型绑定 特性可以自动限定嵌套绑定的范围,以便确认已解析的子模型会自动属于父模型。定义嵌套路由时,使用 scoped方法,可以开启自动范围限定,也可以指定 Laravel 应该按照哪个字段检索子模型资源,有关如何完成此操作的更多信息,请参见有关 范围资源路由的文档。

vaexm 翻译于 1周前

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 的复数化功能支持 多种语言,您可以根据需求进行配置。当自定义了动词和复数化语言后,像 Route::resource('publicacion', PublicacionController::class) 这样的资源路由注册将生成以下 URI:

/publicacion/crear

/publicacion/{publicaciones}/editar

补充资源控制器

如果需要在默认资源路由集之外向资源控制器添加其他路由,则应在调用 Route::resource 方法之前定义这些路由;否则,由 resource 方法定义的路由可能会无意中优先于您的补充路由:

use App\Http\Controller\PhotoController;

Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);

[!注意]
请保持控制器的专注性。如果经常需要超出标准资源操作集的方法,建议将控制器拆分为两个更小的控制器。

单例资源控制器

有时,您的应用程序的资源可能只有一个实例。例如,可以编辑或更新用户的“个人资料”,但用户可能没有多个“个人资料“。同样,一个图像可能只有一个“缩略图”。这些资源被称为“单例资源”,这意味着可能存在一个且只有一个资源实例。在这些场景中,您可以注册一个“单例”资源控制器:

use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

Route::singleton('profile', ProfileController::class);

上述单例资源定义将注册以下路由。如您所见,单例资源不会注册"创建"路由,且注册的路由不接受标识符,因为资源只能有一个实例存在:

| 请求方式 | URI | 操作 | 路由名称 | | --------- | --------------- | ------ | -------------- | | GET | `/profile` | show | profile.show | | GET | `/profile/edit` | edit | profile.edit | | PUT/PATCH | `/profile` | update | profile.update |

陈卿诺语 翻译于 5天前

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 操作的单例资源,从而不需要 createedit 路由:

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');
    }
}
dkp 翻译于 1个月前

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:6
讨论数量: 0
发起讨论 只看当前版本


暂无话题~