【Laravel】3. 控制模块基础知识

275 阅读4分钟

作者:拾年之璐   公众号:知行研究院

3.1 响应设置

路由和控制器处理完业务都会返回一个发送到浏览器的响应:return。几种常用的返回格式如下所示:

如果是字符串,会直接输出。

如果是数组,则会输出json 格式,本身是 Response 对象。示例:

    public function hello3()
    {
        return [1, 2, 3];                   //输出json 格式
       // return response([1, 2, 3]);         //同上
       // return response()->json([1, 2, 3]); //同上
    }

结果:

image-20210105153036556

如果使用 response() 输出的话,可以设置状态码和响应头信息:

    public function hello3()
    {
        return response([1, 2, 3], 400);   //状态码:400
    }

结果:

image-20210105153242050

也可以给 HTTP 添加或修改标头,比如将html 解析模式改成文本plain 模式:

public function hello3()
{
   return response('<br>这里是文本<br>', 201)->header('Content-Type', 'text/plain');  
}

输出结果:

image-20210105153617002

结合上面的响应操作,再结合 view() 视图功能,显示纯HTML 代码页面:

    public function hello3()
    {
        return response()
            ->view('hello', ['name' => '张三', 'id' => '1001'])
            ->header('Content-Type', 'text/plain');
    }

结果:

image-20210105153817559

3.2 资源控制器

资源控制器是某个特定场景下的产物。比如在博客系统中,默认有帖子留言等类型的接口,该资源控制器可以默认生成所有的接口。这是一种专门处理CURD(增删改查)的控制器,方法很多且方法名基本固定。

可以手动创建,不过比较复杂,推荐使用命令直接创建:

php artisan make:controller BlogController --resource

这时,该命令会创建一个名为 BlogController 的控制器,其初始状态如下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

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

如上,生成的控制器产生了7个方法。

然后,使用下面的一条命令,即可生成所有的路由:

Route::resource('blogs', 'BlogController'); //单个资源路由

如果有多个资源控制器,可以使用下面的命令批量定义:

Route::resources([
    'blogs'     => 'BlogController',
    'comments'  => 'CommentController'
]);

怎么看是否已生成路由?使用下面的命令可以查看:

php artisan route:list

结果:

image-20210105161635281

如果我们注册了资源路由,那么如上图的资源路由URI 和名称均自动创建生效。

这里注意,默认生成的控制器,注释中的返回类型为 @return \Illuminate\Http\Response ,测试的时候,可以修改为string

然后在每个路由的控制器中,写入一些基本的测试信息,如:

/**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
        return response('index');
    }

我们也可以限制资源路由只开放部分方法或排除部分方法。

示例:只有index() , show() 可访问

//单个资源路由
Route::resource('blogs', 'BlogController')
    ->only(['index','show']);

结果:

image-20210105163937879

示例:排除 index() , show() 的其它方法可访问

//单个资源路由
Route::resource('blogs', 'BlogController')
    ->except(['index','show']);

结果:

image-20210105162635617

资源控制器还有一种不需要 HTML 页面方法的API 路由,只提供数据接口。

这种类型的资源控制器,可以使用如下命令创建:

php artisan make:controller CommentController --api

这时,创建的控制类,初始内容如下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

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

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

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

API接口,并不需要HTML 页面(create,edit)。

然后路由的定义如下:

Route::apiResource('comments', 'CommentController');

结果:

image-20210105164314311

3.3 资源嵌套

资源嵌套,是指两个控制类,通过路由绑定在一起。

首先创建两个资源控制器:

php artisan make:controller BlogController --resource
php artisan make:controller CommentController --resource

然后,使用嵌套资源路由,即可实现以上两个控制器的嵌套:

Route::resource('blogs.comments', 'CommentController');

结果:

image-20210105164704090

以上需求,可以通过嵌套资源路由来实现这个功能。

编辑 CommentController 类中的 edit() 方法以及传参如下:

    /**
     * Show the form for editing the specified resource.
     *
     * @param $blog_id
     * @param $comment_id
     * @return string
     */
    public function edit($blog_id, $comment_id)
    {
        //
        return '编辑博文下的评论,博文id:'.$blog_id.',评论id:'.$comment_id;
    }

结果:

image-20210105164955609

在实际项目中,每个 id 都是独立唯一的,并不需要父id 和子id 同时存在。

所以为了优化资源嵌套,通过 路由方法->shallow() 实现浅层嵌套方法。格式:

Route::resource('blogs.comments', 'CommentController')->shallow();

结果:

image-20210105165200398

然后修改前往的 edit方法

    public function edit($id)
    {
        //
        return '编辑博文下的评论id:'.$id;
    }

结果:

image-20210105165330374

3.4 自定义路由

如果觉得默认的资源控制器的资源路由命名过长,可以自己自定义,有两种方式。

一种是修改单一的方法路由:

Route::resource('blogs.comments', 'CommentController')
    ->name('index','b.c.i');

结果:

image-20210105165617108

另一种是修改多个路由

Route::resource('blogs.comments', 'CommentController')
    ->names([
        'index'=>'b.c.i',
        'store'=>'b.c.s'
    ]);

结果:

image-20210105165732011

然后,就可以在控制类、路由中,调用Name比较短的名字。

如果觉得资源路由的参数不符合你的心意,也可以改变:

Route::resource('blogs.comments', 'CommentController')
    ->name('index','b.c.i')
    ->parameter('blogs','id');

未改变,访问的方式是:

route('b.c.i', ['blogs' => 10]);

改变后,访问的方式是:

route('b.c.i', ['id' => 10]);

当然,也可以对多个参数进行修改,如:

Route::resource('blogs.comments', 'CommentController')
    ->parameters([
        'blogs' => 'blog_id',
        'comments' => 'comment_id'
    ]);

这时,访问 blogs.comments.edit 的路由,访问方式是:

return route('blogs.comments.edit', ['blog_id' => 10, 'comment_id' => 1000]);

3.5 表单伪造和CSRF保护

之前的数据,都是通过GET请求获取的。而表单的数据提交,是通过POST请求实现。

首先在TaskController下创建两个方法。

一个是表单页:

    public function form()
    {
        return view('form');
    }

表单页路由:

Route::get('task/form', 'TaskController@form');

然后创建表单页面,内容为:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>表单</title>
</head>
<body>

<form action="/task/getform" method="post">
    <input type="hidden" name="_token" value="{{csrf_token()}}">
    用户名: <input type="text" name="user">
    <button type="submit">提交</button>
</form>
</body>
</html>

另一个是接受表单的数据路由:

Route::post('task/getform', function (){
    echo '接收数据:'.Request::input('user');
    echo '<br>';
    echo '请求方法:'.Request::method();
});

执行结果为:

image-20210105174754974

image-20210105174804753

在这个简短的实例中,为了避免被跨站请求伪造攻击,框架提供了CSRF 令牌保护,请求时验证:

<input type="hidden" name="_token" value="{{csrf_token()}}">

表单可以实现POST 提交方式,那其它提交方式该如何实现呢?可以采用伪造技术:

<input type="hidden" name="_method" value="PUT">

在form里增加这一行,然后把请求的方法,由post修改为any,再次请求,结果为:

image-20210105175041409

对于CSRF 令牌保护和表单伪造提交方式,也支持快捷方式的声明,如下:

@csrf
@method('PUT')

完整:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>表单</title>
</head>
<body>

<form action="/task/getform" method="post">
    @csrf
    @method('PUT')
    用户名: <input type="text" name="user">
    <button type="submit">提交</button>
</form>
</body>
</html>

我们可以设置全局 取消CSRF验证,把下图所示注释掉即可:

image-20210105175522364

但是,不推荐这样。

我们可以只将某些URL 关闭csrf 验证,通过设置csrf 白名单的方式实现。

白名单具体设置位置在:中间件目录下的VerifyCsrfToken.php 文件,可以写单个的路由,也可以使用通配符,设置一系列路由:

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        // 在这里添加关闭CSRF的路由
        'api/*',
    ];
}

以上。