基本概念

到目前为止,我们定义的所有路由都是基于闭包函数实现的,前面已经提到过,随着应用体量的增长,不可能将所有路由都定义在单个文件中,且对于复杂的业务逻辑,闭包函数也不足以支撑,所以和其他 Web 应用框架一样,我们还可以通过控制器来定义路由。

说到这里,我们就不得不提一下 MVC 设计模式,这个模式最早在 Ruby On Rails 中引入,然后被基本上所有的 Web 框架所借鉴和遵循,Laravel 也不例外。在 MVC 模式中,M 代表模型(Model),V 代表视图(View),C 代表控制器(Controller),控制器负责组织路由和业务逻辑(当然,对于更加复杂的业务逻辑还会引入 Service 层),模型类负责底层数据存取与处理,而视图层负责数据渲染与页面交互。对于一些 CRUD 操作(数据库增删改查操作的简写)来说,常见的业务逻辑也就是从模型类获取数据并将其渲染到页面,或者从页面获取用户提交数据并将其存储到模型类:

将所有业务逻辑一股脑放到控制器听起来挺不错,但是控制器更适合承担的角色其实是负责对 HTTP 请求进行路由,因为还有很多其他访问应用的方式,比如 Artisan 命令、队列、调度任务等等,控制器并非唯一入口,所以不适合也不应该将所有业务逻辑封装于此,过度依赖控制器会对以后应用的扩展带来麻烦。所以,你应该具备这样的意识:控制器的主要职责就是获取 HTTP 请求,进行一些简单处理(如验证)后将其传递给真正处理业务逻辑的职能部门,如 Service。

如果是非常简单的应用,比如只是简单的数据库增删改查或数据渲染,放到控制器里面也无妨,但是如果后续需要调用控制器方法才能完成某个功能,那么是时候将这个控制器方法里的业务逻辑拆分到 Service 里面了。

基础控制器

一个基本的控制器应该继承自App\Http\Controllers\Controller控制器类,可以使用 artisan 命令创建一个空的控制器类:

> php artisan make:controller UserController

该命令会在 app/Http/Controllers 目录下创建一个新的名为 UserController.php 的文件,默认生成的控制器代码不包含如何方法,下面示例的 index 方法需要手动添加。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
     public function index($id)
    {
        echo $id;
    }
}

可以通过下面的方式把控制器的方法分配到路由:

// routes/web.php
Route::get('user/{id}', 'UserController@index');

一旦将控制器的方法分配到路由之后,每次客户端请求该路由,都会触发控制器的行为。这里即客户端每次请求user/{id}路由,index方法都会被执行,路由中的参数也会被直接传递到该方法中。

实际开发中,很少有返回字符串的场景,常见的控制器方法代码如下:

public function index()
{
    return view('task.index')
        ->with('tasks', Task::all());
}

这段代码的含义是通过 Task::all() 查询所有任务数据,并将其赋值给 tasks 变量在视图 task.index (resources/views/task/index.blade.php)中渲染出来。

控制器中间件

可以给控制器分配中间件

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

在控制器的构造函数中指定中间件更为方便,使用控制器构造函数中的 middleware 方法,可以轻松地将中间件分配给控制器的操作。你甚至可以将中间件限制为只在控制器中的某些方法生效:

class UserController extends Controller
{
    /**
     * 实例化一个新的控制器实例.
     *
     * @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);
});

RESTful 资源控制器

资源控制器可以使你快速的构建 RESTful 类型的控制器。你可以使用 artisan 命令来快速的创建:

> php artisan make:controller PhotoController --resource

现在,打开 app/Http/Controllers/PhotoController.php 文件,即可看到 PhotoController 代码:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PhotoController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

该命令会生成 app\Http\Controllers\PhotoController.php 文件,资源控制器中将包含每个可用的资源操作相应的方法,可以通过下面的方式来进行资源路由的统一注册

// routes/web.php
Route::resource('photo', 'PhotoController');

这行代码会创造多条路由用来处理 RESTful 式的请求。相应的通过命令生成的资源型控制器也为这些请求设置了对应的处理方法。

请求方式路由地址控制器行为路由命名
GET/photoindexphoto.index
GET/photo/createcreatephoto.create
POST/photostorephoto.store
GET/photo/{photo}showphoto.show
GET/photo/{photo}/editeditphoto.edit
PUT/PATCH/photo/{photo}updatephoto.update
DELETE/photo/{photo}destoryphoto.destory

注意:如果你必须在资源控制器中添加额外的行为方法去注册相应的路由,那么你一定要在使用 Route::resource 之前进行注册,否则该行为很可能会被资源控制器意外的覆盖调。

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

获取用户输入的数据

除了数据渲染之外,还可以在控制器中获取用户输入并进行处理,下面我们来看两个例子:

Route::get('task/create', 'TaskController@create');
Route::post('task', 'TaskController@store');

我们通过 create() 方法来渲染一个任务提交表单, 然后通过 store() 方法来存储提交的任务数据。关于表单渲染我们放到后面去讨论,现在我们直接跳到表单数据处理上,所以编写 store() 方法:

public function store(Request $request)
{
    $task = new Task();
    $task->title = $request->input('title');
    $task->description = $request->input('description');
    $task->save();
    return redirect('task');   // 重定向到 GET task 路由
}

这里我们用到了 Eloquent 模型类 Task 和重定向方法 redirect(),我们将用户提交数据收集起来,保存到 Task 模型类,然后将用户重定向到显示所有任务的页面。这里我们通过 $request 对象来获取用户输入,此外还可以通过 Input 获取用户输入:

$task->title = Input::get('title');

使用 Input 的方式需要引入 Input :use Illuminate\Support\facades\Input,底层调用的还是 $request->input 方法,语法糖而已。

依赖注入

构造器注入

Laravel 的服务容器支持所有的 Laravel 控制器的解析,所以你可以在控制器的构造函数中添加你所需要依赖的相应类型提示,这些依赖会被自动的解析并注入进控制器实例。
也允许添加 Laravel contract的类型提示,只要服务容器能够正确的解析,都可以添加。

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller {
    protected $users;

    public function __construct(UserRepository $users) {
        $this->users = $users;
    }
}

方法注入

除了在构造函数中进行依赖注入,也可以在控制器的行为方法中进行依赖注入,比如,将 Illuminate\Http\Request 实例注入到控制器的 store 方法中:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller {
  public function store (Request $request) {
    $name = $request->input('name');
  }
}

如果控制器方法也接受从路由传递过来的参数,那么他们会在其他依赖解析完毕之后被传递

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

如果路由定义如下,则控制器方法可以的参数可以修改如下:

<?php

namespace App\Http\controllers;

use Illuminate\Http\Request;

class UserController extends Controller {
  public function update (Request $request, $id) {
    // 
  }
}

缓存路由

缓存路由不支持闭包函数定义的路由,如果你想使你的路由被缓存,那么你应该使用控制器来管理你的路由。

如果你所有的路由都是基于控制器的路由,那么你应该使用 Laravel 推荐的缓存路由,你可以简单的通过 artisan 命令来缓存所有路由注册到同一个文件里,它会替代routes/web.php文件被解析,使用这种缓存注册路由的方式在某些情况下注册路由的时间将被大大的减少,从而提高了应用的响应速度。

但是每次添加新的路由或者删除路由时,为了使路由生效,你需要重新生成一次缓存路由:

> php artisan route:cache

可以通过下面的方式去删除路由缓存:

> php artisan route:clear