Laravel 文档阅读:控制器

翻译、衍生自:https://learnku.com/docs/laravel/5.5/controllers

我们的业务代码不能都写在路由文件(比如: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 路径请求的时候,UsersControllershow 方法就会被调用,路由参数也会传递给这个方法。

需要注意的是, 我们的控制器不是非要继承基类控制器的,只不过,如果不继承基类控制器的话,一些非常好用的方法,比如 middlewarevalidatedispatch 就无法使用了。

控制器 & 命名空间

细心观察的话会发现,我们在定义控制器路由的时候,并没有使用控制器的完整命名空间。这是因为在 RouteServiceProvider 中,已经为 routes/web.phproutes/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 子集做中间件处理。但这可能说明你的控制器是臃肿的,尝试去把这个控制器拆分成多个、职能更加单一的控制器吧。

资源控制器

资源控制器就是指包含 indexcreatestoreshoweditupdatedestroy 这 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 表单不能发出 PUTPATCHDELETE 类型的请求,所以想要使用这些请求类型的话,就必须得伪造。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 生成的路由地址中包含 createedit 这两个英语动词。如果需要本地化这两个动词,需要使用 Route::resourceVerbs 方法,这个方法可以在 AppServiceProviderboot 方法里设定:

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
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 6年前 加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 3

控制器方法里注入 比较常用

6年前 评论
ThinkQ

不错

5年前 评论

你好,这段时间我也在学laravel,可以加你qq或微信一起学习嘛? 谢谢

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!