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, validatedispatch 这些方法。

控制器和命名空间

在定义控制器路由时,我们不需要指定完整的控制器的命名空间。 由于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模板(例如createedit)的路由。 为了方便起见,我们可以使用apiResource方法自动排除这两个路由:

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

同样,我们可以通过数组一次性注册多个Api资源路由

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

如果想快速的生成一个Api资源控制器,该控制器不带createedit 方法,可以在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。 如果需要本地化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')之类的资源路由注册将产生以下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

查看笔记

扫码一下
查看教程更方便