Laravel入门——控制器

简介

控制器是模型和视图的桥梁!

手动写Controller

在App\Http\Controllers下编写Controller,本身会自带一个Controller,我们可以新建一个Controller继承该Controller。

之前是直接在路由就返回视图,而视图一般应该由Controller来操作。
我们可以像这样定义指向该控制器动作的路由:

//第二个参数@之前的是控制器名称,之后是方法名称
Route::get('user/{id}', 'UserController@showProfile');

还能自动创建,使用artisan命令,在根目录,也就是app文件所在的目录(我也不太清楚其他目录行不行)

php artisan make:controller UserController

如果要改自定义Controller的目录,也就是再建一个文件夹做个分类,那么要记得改namespace,而且继承的Controller要用use引入。而且路由的第二个参数也要加上新建的目录。
‘Admin\UserController@showProfile’

RESTful 资源控制器

Route::resource('photo', 'PhotoController');

控制器用于将相关的 HTTP 请求封装到一个类中进行处理,这些控制器类存放在 app/Http/Controllers 目录下。

控制器入门

定义控制器

可使用 Artisan 命令快速创建一个控制器。

php artisan make:controller UserController

这样,会自动生成 app/Http/Controllers/UserController.php 文件,内容如下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    //
}

所有的控制器一般会继承 Laravel 自带的控制器基类 App\Http\Controllers\Controller。

现在,在该控制器中添加 show 方法。

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 为指定用户显示详情
     *
     * @param int $id
     * @return Response
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

然后,在 routes/web.php 中定义路由:

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

现在,如果一个请求匹配上面的路由 URI,UserController 的 show 方法就会被执行,当然,路由参数也会被传递给这个方法。

此外,这里的 show 方法里面还用到了 view 方法,该方法用于将 user 变量渲染到 user/profile 视图中。

然后在 resources/veiws 目录下创建 user 子目录,然后在 user 目录下新建 profile.blade.php 视图文件,内容如下:

{{ dd($user) }}

现在,在浏览器中访问 http://www.adm.devp/user/1 ,就可看到打印结果:

User {#208 ▼
  #fillable: array:3 [▶]
  #hidden: array:2 [▶]
  #connection: "mysql"
  #table: null
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:7 [▶]
  #original: array:7 [▶]
  #changes: []
  #casts: []
  #dates: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #visible: []
  #guarded: array:1 [▶]
  #rememberTokenName: "remember_token"
}

注:控制器并不是一定要继承基类,不过,那样的话就不能使用一些基类提供的便利方法了,比如 middleware、validate 和 dispatch 等。

命名空间

在定义控制器路由时,我们并没有指定完整的控制器命名空间,而只是指定了 App\Http\Controllers 之后的部分,那为什么可以这么做呢?

这是因为在 RouteServiceProvider 文件中,已经定义了 App\Http\Controllers ,故而我们只需指定后面的相对命名空间即可。

如果你在 App\Http\Controllers 目录下选择使用 PHP 命名空间嵌套或组织控制器,只需要使用相对于 App\Http\Controllers 命名空间的指定类名即可。

因此,如果你的完整控制器类是 App\Http\Controllers\Photos\AdminController,则可以像这样注册路由:

Route::get('foo', 'Photos\AdminController@method');
单方法控制器

如果你的控制器只有一个方法,则可以在这个控制器中定义 __invoke 方法:

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

当你为单方法控制器注册路由时,不需要指定方法:

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

原理是当尝试以调用函数的方式调用一个对象时,__invoke() 魔术方法会被自动调用。

控制器中间件

中间件可以像这样分配给控制器路由:

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

不过,将中间件放在控制器的构造函数中更方便,在控制器的构造函数中使用 middleware 方法可以轻松地分配中间件给该控制器(该方法继承自控制器基类)。

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function __construct()
    {
        $this->middleware('token');
    }

    /**
     * 为指定用户显示详情
     *
     * @param int $id
     * @return Response
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

在这个示例中,我们在控制器的构造方法中使用了 token 中间件,但是我们还没有定义和注册 token 中间件。

创建 token 中间件:
php artisan make:middleware Token

这样,就会自动生成 app/Http/Middleware/Token.php 文件,稍作修改后,内容如下:

<?php

namespace App\Http\Middleware;

use Closure;

class Token
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // 如果 token 的值不是 abcdef,则跳转到首页。
        if ($request->token != 'abcdef') {
            return redirect('/');
        }

        // 如果 token 正确,就正常访问
        return $next($request);
    }
}
注册 token 中间件:

修改 app/Http/Kernel.php 文件,为 $routeMiddleware 属性添加一个新的键值对即可。

'token' => \App\Http\Middleware\Token::class,

这样,当我们访问 http://www.adm.devp/user/1 时,会跳转到首页;访问 http://www.adm.devp/user/1?token=abcdef 时,才能访问到正确的页面。

除此之外,我们还可以指定中间件对指定方法生效或者排除指定方法的校验:

$this->middleware('auth')->only('show'); // 只对该方法生效

$this->middleware('auth')->except('show');  // 对该方法以外的方法生效

如果要指定多个控制器方法可以以数组的方式传参:

$this->middleware('auth')->only(['show', 'index']); // 只对指定方法生效

$this->middleware('auth')->except(['show', 'index']);  // 对指定方法以外的方法生效

在控制器中还可以使用闭包注册中间件,这为我们定义只在某个控制器中使用的中间件提供了方便,而无需定义完整的中间件类:

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

    return $next($request);
});

还是以 UserController 为例,我们为其定义一个匿名中间件:

use App\User;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class UserController extends Controller
{
    public function __construct()
    {
        $this->middleware(function($request, $next){
            if($request->input('token')!='abcdef'){
                throw new NotFoundHttpException();
            }
            return $next($request);
        });
    }
}

这样当我们访问 http://www.adm.devp/user/1 时会抛出 404 异常;只有当访问 http://www.adm.devp/user/1?token=abcdef 时才能正常展示。

资源控制器

Laravel 的资源控制器可以很便捷地构建基于资源的 RESTful 控制器。

例如,你可能想要在应用中创建一个控制器,用于处理关于文章存储的 HTTP 请求,使用 Artisan 命令 make:controller,我们可以快速创建这样的控制器:

php artisan make:controller PostController --resource

这样,会自动生成 app/Http/Controllers/PostController.php 文件,内容如下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController 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)
    {
        //
    }
}

接下来,可以通过 resource 方法为该控制器注册一个资源路由:

Route::resource('posts', 'PostController');

这个路由声明包含了处理文章资源对应方法的多个路由。具体如下表:

请求方式     URI路径     控制器方法     路由名称     说明
GET     /posts     index     posts.index     文章列表(首页)
GET     /posts/create     create     posts.create     新增文章的表单界面
POST     /posts     store     posts.store     执行存储新文章
GET     /posts/{post}     show     posts.show     查看指定的文章
GET     /posts/{post}/edit     edit     posts.edit     更新文章的表单界面
PUT/PATCH     /posts/{post}     update     posts.update     执行更新文章
DELETE     /posts/{post}     destroy     posts.destroy     执行删除文章
指定资源模型

如果你使用了路由模型绑定,并且想要在资源控制器的方法中对模型实例进行依赖注入,可以在生成控制器时使用 –model 选项:

php artisan make:controller PostController --resource --model=Post

不过,不推荐使用这种模型绑定,因为这里会涉及到对模型数据的缓存逻辑,为性能考虑,我们不想总是从数据库取数据,所以,尽量保持单个功能的简单和单一职责,让开发者自己去组装需要的功能,这是 Unix 奉行的设计哲学,也是我们在系统设计的时候需要考量的重要因素。

伪造表单方法

由于 HTML 表单不支持发起 PUT、PATCH 和 DELETE 请求,需要添加一个隐藏的 _method 字段来伪造 HTTP 请求方式,辅助函数 method_field 可以帮我们做这件事:

{{ method_field('PUT') }}
部分资源路由

声明资源路由时可以指定该路由处理的动作子集:

Route::resource('post', 'PostController', ['only' => 
    ['index', 'show']
]);

Route::resource('post', 'PostController', ['except' => 
    ['create', 'store', 'update', 'destroy']
]);
API资源路由

声明被 API 消费的资源路由时,你可能需要排除展示 HTML 模板的路由,如 create 和 edit,为了方便起见,Laravel 提供了 apiResource 方法自动排除这两个路由:

Route::apiResource('post', 'PostController');
命名资源路由

默认情况下,所有资源控制器的动作都有一个路由名称,不过,我们可以通过传入 names 数组来覆盖这些默认的名称:

Route::resource('post', 'PostController', ['names' => 
    ['create' => 'post.build']
]);
命名资源路由参数

默认情况下,Route::resource 将会基于资源名称的单数格式为资源路由创建路由参数,你可以通过在选项数组中传递 parameters 来覆盖这一默认设置。 parameters 是资源名称和参数名称的关联数组:

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

上面的示例代码会为资源的 show 路由生成如下 URL:

/user/{admin_user}
补充资源控制器

如果需要在默认资源路由之外添加额外的路由到资源控制器,应该在调用 Route::resource 之前定义这些路由,否则,通过 resource 方法定义的路由可能无意中覆盖掉补充的额外路由:

Route::get('posts/popular', 'PostController@method');

Route::resource('posts', 'PostController');

注:注意保持控制器的单一职责,如果你发现指向控制器动作的路由超过默认提供的资源控制器动作集合了,考虑将你的控制器分割成多个更小的控制器。

依赖注入

构造函数注入

Laravel 使用服务容器解析所有的 Laravel 控制器,因此,可以在控制器的构造函数中注入任何依赖,这些依赖会被自动解析并注入到控制器实例中:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * 创建新的控制器实例
     *
     * @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 UserController extends Controller
{
    /**
     * 存储新用户
     *
     * @param Request $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->input('name');

        //
    }
}

如果控制器方法期望输入路由参数,只需要将路由参数放到其他依赖之后,例如,如果你的路由定义如下:

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

则需要以如下方式定义控制器方法来注入 Illuminate\Http\Request 依赖并访问路由参数 id:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 更新指定用户
     *
     * @param Request $request
     * @param int $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        //
    }
}
坚持原创技术分享,您的支持将鼓励我继续创作!