Skip to content

控制器

介绍

与其在路由文件中将所有请求处理逻辑定义为闭包,您可能希望使用控制器类来组织这些行为。控制器可以将相关的请求处理逻辑分组到一个类中。控制器存储在 app/Http/Controllers 目录中。

基本控制器

定义控制器

下面是一个基本控制器类的示例。注意,控制器扩展了 Laravel 附带的基控制器类。基类提供了一些便捷方法,例如 middleware 方法,可用于将中间件附加到控制器动作:

php
<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 显示给定用户的个人资料。
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

您可以像这样为此控制器动作定义路由:

php
Route::get('user/{id}', 'UserController@show');

现在,当请求匹配指定的路由 URI 时,将执行 UserController 类上的 show 方法。当然,路由参数也将传递给该方法。

lightbulb

控制器不是必须扩展基类。然而,您将无法访问便捷功能,例如 middlewarevalidatedispatch 方法。

控制器与命名空间

需要特别注意的是,在定义控制器路由时,我们不需要指定完整的控制器命名空间。由于 RouteServiceProvider 在包含命名空间的路由组中加载您的路由文件,我们只需指定类名中 App\Http\Controllers 部分之后的部分即可。

如果您选择将控制器更深地嵌套到 App\Http\Controllers 目录中,只需使用相对于 App\Http\Controllers 根命名空间的特定类名即可。因此,如果您的完整控制器类是 App\Http\Controllers\Photos\AdminController,您应该像这样注册控制器的路由:

php
Route::get('foo', 'Photos\AdminController@method');

单动作控制器

如果您希望定义一个仅处理单个动作的控制器,可以在控制器上放置一个 __invoke 方法:

php
<?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)]);
    }
}

为单动作控制器注册路由时,您不需要指定方法:

php
Route::get('user/{id}', 'ShowProfile');

控制器中间件

中间件 可以在您的路由文件中分配给控制器的路由:

php
Route::get('profile', 'UserController@show')->middleware('auth');

然而,在控制器的构造函数中指定中间件更为方便。使用控制器构造函数中的 middleware 方法,您可以轻松地将中间件分配给控制器的动作。您甚至可以将中间件限制为仅适用于控制器类的某些方法:

php
class UserController extends Controller
{
    /**
     * 实例化一个新的控制器实例。
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

控制器还允许您使用闭包注册中间件。这为单个控制器定义中间件提供了一种便捷的方法,而无需定义整个中间件类:

php
$this->middleware(function ($request, $next) {
    // ...

    return $next($request);
});
lightbulb

您可以将中间件分配给控制器动作的子集;然而,这可能表明您的控制器过于庞大。相反,考虑将您的控制器拆分为多个较小的控制器。

资源控制器

Laravel 资源路由使用一行代码为控制器分配典型的“CRUD”路由。例如,您可能希望创建一个控制器来处理应用程序中存储的“照片”的所有 HTTP 请求。使用 make:controller Artisan 命令,我们可以快速创建这样的控制器:

php
php artisan make:controller PhotoController --resource

此命令将在 app/Http/Controllers/PhotoController.php 生成一个控制器。该控制器将包含每个可用资源操作的方法。

接下来,您可以为控制器注册一个资源路由:

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

此单个路由声明创建多个路由以处理资源上的各种操作。生成的控制器将已经为每个这些动作预留了方法,包括通知您它们处理的 HTTP 动词和 URI 的注释。

资源控制器处理的动作

动词URI动作路由名称
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.destroy

伪造表单方法

由于 HTML 表单无法发出 PUTPATCHDELETE 请求,您需要添加一个隐藏的 _method 字段来伪造这些 HTTP 动词。method_field 助手可以为您创建此字段:

php
{{ method_field('PUT') }}

部分资源路由

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

php
Route::resource('photo', 'PhotoController', ['only' => [
    'index', 'show'
]]);

Route::resource('photo', 'PhotoController', ['except' => [
    'create', 'store', 'update', 'destroy'
]]);

命名资源路由

默认情况下,所有资源控制器动作都有一个路由名称;然而,您可以通过在选项中传递 names 数组来覆盖这些名称:

php
Route::resource('photo', 'PhotoController', ['names' => [
    'create' => 'photo.build'
]]);

命名资源路由参数

默认情况下,Route::resource 将根据资源名称的“单数化”版本创建资源路由的路由参数。您可以通过在选项数组中传递 parameters 来轻松覆盖此设置。parameters 数组应为资源名称和参数名称的关联数组:

php
Route::resource('user', 'AdminUserController', ['parameters' => [
    'user' => 'admin_user'
]]);

上述示例为资源的 show 路由生成以下 URI:

php
/user/{admin_user}

补充资源控制器

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

php
Route::get('photos/popular', 'PhotoController@method');

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

请记住保持控制器的专注。如果您发现自己经常需要超出典型资源动作集的方法,请考虑将控制器拆分为两个较小的控制器。

依赖注入与控制器

构造函数注入

Laravel 服务容器 用于解析所有 Laravel 控制器。因此,您可以在控制器的构造函数中类型提示控制器可能需要的任何依赖项。声明的依赖项将自动解析并注入到控制器实例中:

php
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * 用户仓库实例。
     */
    protected $users;

    /**
     * 创建一个新的控制器实例。
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

当然,您也可以类型提示任何 Laravel 合约。如果容器可以解析它,您就可以类型提示它。根据您的应用程序,将依赖项注入到控制器中可能会提供更好的可测试性。

方法注入

除了构造函数注入,您还可以在控制器的方法中类型提示依赖项。方法注入的一个常见用例是将 Illuminate\Http\Request 实例注入到控制器方法中:

php
<?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;

        //
    }
}

如果您的控制器方法还期望从路由参数中获取输入,只需在其他依赖项之后列出路由参数。例如,如果您的路由定义如下:

php
Route::put('user/{id}', 'UserController@update');

您仍然可以类型提示 Illuminate\Http\Request 并通过以下方式访问您的 id 参数:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 更新给定用户。
     *
     * @param  Request  $request
     * @param  string  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        //
    }
}

路由缓存

exclamation

基于闭包的路由无法缓存。要使用路由缓存,您必须将任何闭包路由转换为控制器类。

如果您的应用程序完全使用基于控制器的路由,您应该利用 Laravel 的路由缓存。使用路由缓存将大大减少注册应用程序所有路由所需的时间。在某些情况下,您的路由注册速度甚至可能提高 100 倍。要生成路由缓存,只需执行 route:cache Artisan 命令:

php
php artisan route:cache

运行此命令后,您的缓存路由文件将在每个请求时加载。请记住,如果您添加了任何新路由,您将需要生成新的路由缓存。因此,您应该仅在项目部署期间运行 route:cache 命令。

您可以使用 route:clear 命令清除路由缓存:

php
php artisan route:clear