前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Laravel系统3.3】控制器与表单验证

【Laravel系统3.3】控制器与表单验证

作者头像
硬核项目经理
发布于 2023-03-03 05:09:50
发布于 2023-03-03 05:09:50
8.8K00
代码可运行
举报
运行总次数:0
代码可运行

控制器与表单验证

在请求过程中,控制器往往是我们在做业务开发时绕不过的一环。从 MVC 理论的成熟到现代化的开发过程中,控制器一直扮演着重要的角色。可以说,我们可以不要前端(只做接口),可以不要模型(直接读取数据),但控制器却是必不可少的。当然,在正式的 MVC 模型中,视图是可以直接和模型交互的,由此,也引申出了 MVP 模型,其中的这个 P 就是强化控制器的作用,让模型和视图解耦。其实我们大部分正规的开发,都是基于这个 MVP 的,很少会直接让视图和模型去交互。

所以说,只要是遵循 MVC 模式的框架,控制器都是最核心的部分。在传统的框架中,我们的控制器往往也充当路由的功能,比如 TP3.2 系列,定义控制器名称就是我们要请求的 URL 路径名称。之前在讲路由的时候也说过这个问题,但是在 Laravel 中,实现了路由和控制器的解耦,所以我们的控制器是可以随意定义并且命名的,直接通过路由来进行绑定。

基础控制器

我们可以通过命令行来创建一个控制器,当然,您也可以直接自己创建一个控制器类。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
php artisan make:controller TestController

如果是自己创建的控制器类,需要继承 app/Http/Controllers/Controllers 这个基类。如果不继承这个基类,也就无法使用框架的能力,比如说中间件之类的功能。我们这里测试的是直接通过命令行创建的,看看它的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class TestController extends Controller
{
    //
}

非常简单,也没有什么别的特别的地方,接下来,我们就可以在这个控制器中写需要的控制方法了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function test(){
}

最简单的一个控制器就这样实现了,接下来要如何访问它呢?当然就是去配下路由就好啦。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Route::get('test/test', 'App\Http\Controllers\TestController@test');
// http://laravel8/test/test

这时访问的结果是一个空白的页面,因为在这个控制器方法中我们什么都没有做,也没有任何的返回,所以页面上没有任何的显示。但其实,Laravel 中还是为我们做了一些事情。比如返回 HTTP 的头信息,包括响应状态码、基础的头信息之类的内容。

另外,我们还可以定义一个单行为控制器,这是什么意思呢?其实就是一个控制器里面只有一个方法,这样的一个控制器就不需要在路由中指定控制方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class Test2Controller extends Controller
{
    //
    public function __invoke()
    {
        echo 'single action controller';
    }
}

注意到单行为控制器中使用的这个 __invoke() 魔术方法了吗?不记得的小伙伴可以回到我们最早的文章中 PHP的那些魔术方法(二)https://mp.weixin.qq.com/s/8WgQ3eVYKjGaEd2CwnB0Ww 复习一下,在 Laravel 中,我们会用到很多之前学习过的基础知识。所以说,框架的学习其实就是一次对于基础知识的全面巩固复习,同时也需要我们对于 PHP 的基础知识有牢固的掌握。

接下来就是路由和我们的测试了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Route::get('test/test2', 'App\Http\Controllers\Test2Controller');
// http://laravel8/test/test2
// single action controller

参数接收

对于请求参数的接收来说,在控制器中和在路由的回调函数中接收参数没有什么区别。都可以通过依赖注入的方式获取到指定的参数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 控制器
public function test2(Request $request, $id){
    var_dump($request === \request()); // bool(true)
    return 'test2: ' . $id . ', ' . $request->input('name', '') . ', ' . \request()->input('sex', '');
}

// 路由
Route::get('test/test2/{id}', 'App\Http\Controllers\TestController@test2');
// http://laravel8/test/test2/2?name=Bob&sex=male

在这里,我们使用了两种接收 Request 的方式。一个是使用依赖注入的 request 对象,一个是使用 request() 方法返回的 Request 对象。两种方式在本质上没有什么区别,在代码中我们也打印了这两种方式的对象是否是全等的。只不过一个是通过依赖注入到当前方法的参数中,而另一个 request() 方法则是通过全局的服务容器来获取 Request 对象的。关于依赖注入和服务容器的内容都会在后面核心架构相关的文章中学习到。

资源型控制器

在上篇路由的文章中就讲过,我们可以定制一个资源型的路由,对应的就是一个资源型的控制器,这俩货是相辅相成的。那么什么是资源型呢?其实就是标准的 RESTful 类型的一套请求链接。对于 REST 有疑问的同学可以自行查阅相关的文档,在这里就不多说了,毕竟我们的主旨还是在于 Laravel 框架如何实现这些功能。

我们可以直接使用命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
php artisan make:controller ResourceTestController --resource

创建一个资源型的控制器,直接来看看代码,这个控制器已经为我们准备好了一系列的方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ResourceTestController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
        return 'get列表';
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
        return 'post添加数据-显示表单';
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
        return 'post保存数据';
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
        return 'get单条数据';
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
        return 'get修改数据-显示表单';
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
        return 'put/patch修改数据';
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
        return 'delete删除数据';
    }
}

当定义完成资源型控制器之后,就可以在路由上非常方便地配置这个资源的路由,一行就搞定。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Route::resource('test/resource', 'App\Http\Controllers\ResourceTestController');

剩下的呢?Laravel 框架会自动帮我们配置以下这些路由,大家只要按照规则访问就好了。

请求方式

链接

说明

GET

/test/resource

索引/列表

GET

/test/resource/create

创建(显示表单)

POST

/test/resource/store

保存你创建的数据

GET

/test/resource/{id}

显示对应id的内容

GET

/test/resource/{id}/edit

编辑(显示表单)

PUT/PATCH

/test/resource/{id}

保存你编辑的数据

DELETE

/test/resource/{id}

删除

是不是感觉很高大上,确实如此,而且这一套路由也是非常符合 RESTFul 规范的,并且最主要的是,这一套路由不需要我们再手动去写了,它直接就帮我们定义好了。在测试的时候直接访问它们就可以了。

控制器的调用

对于路由到控制器的调用,还记得上篇文章中学习过的 laravel/framework/src/Illuminate/Routing/Route.php 这个文件中的 run() 方法吗?如果我们定义的路由是指定的控制器,那么它就会走到 runController() 。在这个 runController() 方法中,会指定分发到的控制器,其实也是从一个控制器的集合中查找指定的控制器信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected function runController()
{
    return $this->controllerDispatcher()->dispatch(
        $this, $this->getController(), $this->getControllerMethod()
    );
}

getController() 和 getControllerMethod() 都是获取的当前文件中的 action 里面的 uses 字段里面的内容,它保存的就是我们在路由中填写的控制器信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$this->action->uses = "App\Http\Controllers\ResourceTestController@index";

在我们实例化所有路由时,都会创建一个 Route 对象。传递过来的数据就是我们在路由文件中定义的数据,也就是调 get()/post() 这些方法的时候添加的数据。而第二个参数,也就是我们指定的回调或者控制器参数就会充当 action 参数,交给 Route.php 中的 parseAction() 方法进行处理,处理之后的结果就会保存在当前这个 Route 对象的 action 属性里面。

整体来说,控制器的调用和回调路由的调用本质上是没有什么区别的。

快速表单验证

在日常的业务开发中,出于安全以及数据格式验证的考虑,我们通常会对接收到的参数进行验证过滤,一般情况下,都是通过一个个的 if...else 来进行这项工作。既然说到这里了,那么在 Laravel 框架中,其实也是有对应的表单验证的功能的,可以方便地让我们进行表单参数的验证。

首先我们需要定义一个页面,这个页面用于提交表单,只需要简单的定义一个模板页就可以。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ValidateController
public function create(){
    return view("validate.create");
}

// validate/create.blade.php
@if ($errors->any())
<div class="alert alert-danger">
    <ul>
        @foreach ($errors->all() as $error)
        <li>{{ $error }}</li>
        @endforeach
    </ul>
</div>
@endif

<h2>表单验证</h2>
<form method="post" action="http://laravel8/validate/store">
    <label>标题</label><input name="title"/><br/>
    <label>作者</label><input name="author"/><br/>
    <label>年龄</label><input name="age"/><br/>
    <label>内容</label><input name="body"/><br/>
    <input type="hidden" name="_token" value="{{ csrf_token() }}"/>
    <button type="submit">提交</button>
</form>

// route/web.php
Route::get('validate/create', 'App\Http\Controllers\ValidateController@create');

这个就相当于是一个要提交数据的静态表单页面,我们没有做别的任何操作。其中在模板文件中,csrf_token() 这个东西是用于 CSRF 攻击防御的,这个在后面如果学习到了相关的内容再说,大家也可以自行查阅一下相关的资料。如果没有这个 _token 的话,那么表单提交之后就会报 419 的错误。

继续写我们的这个 store 接收页面。来看看我们如何验证这个表单里面提交的数据信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ValidateController
public function store(Request $request){
    $validatedData = $request->validate([
        'title'=>"required|max:20",
        'author'=>['required','min:2', 'max:20'],
        'age'=>"numeric",
        'body'=>"required"
    ]);
}

// route/web.php
Route::post('validate/store', 'App\Http\Controllers\ValidateController@store');

接下来就是去测试一下,在表单页面,我们什么都不填,直接提交,就可以看到页面上输出了如下的错误提示信息。

这个错误信息正是在模板中的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@if ($errors->any())
<div class="alert alert-danger">
    <ul>
        @foreach ($errors->all() as $error)
        <li>{{ $error }}</li>
        @endforeach
    </ul>
</div>
@endif

这段代码输出的。而验证的规则,则是在 request 的 validate() 方法中配置的这些。从英文可以看出,我们让 title 这个字段 required(必填)、max:20(最大不超过20个),让 age 这个字段的内容 numeric(只能是数字)。当然,还有很多可配置的内容,在这里就不一一列举了,大家可以自己查阅相关的文档,毕竟这些东西都是文档中现成的,学习这些配置参数的使用也不是我们这个系列文章的重点。

从这段功能的测试代码中,我们可以看出几个问题。其一,这个验证是直接通过请求对象实现的,也就是这个 Request 对象中的方法,而且我们在控制器中没有返回 Response ,也就是说,这一切框架都自动为我们处理了。其二,错误信息会直接传到模板的一个 $errors 变量中,这个也不是我们控制的,也是框架自动处理的,这个地方也是我们平常在写业务代码的时候需要注意的,因为这个变量名是写死在框架内部的,不能修改的。其三,没有地方设置错误信息的内容,比如说我们要显示中文的错误信息。

太智能太自动的东西有好处,但也有很多的限制,比如这个第三点,如果需要显示中文的错误信息的话,我们需要去下载或者自己配置一个 resource/lang 下的语言包,并且修改框架配置中的 lang 为对应的语言包。不过,我们有别的办法来解决,那就是我们自己配置,手动验证。

手动验证

说实话,上面的自动表单验证平常还真没用过。平常用得最多的反而是这个自定义的手动验证,说是手动验证,其实大部分也是已经框架提供好的内容,我们只需要简单的配置就可以了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function store2(Request $request){
    $validator = Validator::make($request->all(), [
        'title'=>"required|max:20",
        'author'=>['required','min:2', 'max:20'],
        'age'=>"numeric",
        'body'=>"required"
    ], [
        'title.required'=>'请填写标题',
        'title.max'=>'标题最大不超过20个字符',
        'author.required'=>'请填写作者',
        'author.min'=>'作者最少填写2个字符',
        'author.max'=>'作者最大不超过20个字符',
        'age.numeric'=>'年龄必须是数字',
        'body.required'=>'内容必填'
    ]);

    if($validator->fails()){
        return redirect('validate/create')
            ->withErrors($validator)
            ->withInput();
    }
}

在这个控制器中,我们使用的是 Validator 这个门面类 make() 出来的一个验证器。它的第一个参数我们传递的是所有的请求数据,当然,也可以自己传递一个数组进来进行验证。第二个参数就是和上面一样的验证配置信息。不同的,它的第三个参数是我们可以自定义的验证提示信息。有了这个参数,返回的提示需要什么样的内容就方便了很多。

最后,还有一处不同的是,这个 Validator 对象不是用得请求 Request 的方法,所以它不会自动返回,需要自己构造 Response ,在这里,我们跳转回了原来的页面,并且将错误信息通过 withErrors() 添加到了模板的 \errors 变量中。进入 withError() 方法,我们可以看到 errors 是保存在 session 的 flash() 中,这个我们后面讲 session 的时候再说。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// laravel/framework/src/Illuminate/Http/RedirectResponse.php
public function withErrors($provider, $key = 'default')
{
    $value = $this->parseErrors($provider);

    $errors = $this->session->get('errors', new ViewErrorBag);

    if (! $errors instanceof ViewErrorBag) {
        $errors = new ViewErrorBag;
    }

    $this->session->flash(
        'errors', $errors->put($key, $value)
    );

    return $this;
}

数据验证的源码处理

不管是请求对象的验证函数,还是我们通过门面 make() 后获得的验证对象,它的核心都是 laravel/framework/src/Illuminate/Validation/Validator.php 这个文件中的 Validator 对象。在初始化的时候,会将数据 data 、 规则 initialRules 、提示消息 customMessages 存放到这个对象的相关变量中,然后通过对象里面的 validateAttribute() 方法进行参数和规则的匹配,并通过 addFailure() 方法匹配对应的提示消息信息,最后将这些信息放在 messages 属性中。上面 withErrors() 的代码中的 parseErrors() 最终的调用其实就是走到了 Validator 对象的 validateAttribute() 这个方法中。

基本上整个处理过程都是在这个 Validator 对象里面,所以这里我也就不贴代码了,大家自己调试一下。

总结

这篇文章的内容不少吧,我们学习了控制器和验证器相关的内容,之所以把这两个放在一起,也是因为验证这个功能一般都会在控制器的最开始使用。当然,我们在讲数据库模型的时候,还有数据库验证相关的内容,和这边又不太一样了,这个我们等学习到的时候再说。

控制器的内容其实并不多,但里面的很多东西我们并没有都讲解到,毕竟现成的文档都在,也没必要全部再复制一遍,还是以调用路径的源码分析分主。下一个要讲的内容相信也是很多同学非常感兴趣的,那就是中间件的应用以及源码的分析。

参考文档:

https://learnku.com/docs/laravel/8.x/controllers/9368

https://learnku.com/docs/laravel/8.x/validation/937

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农老张 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
一起来学Go --- (go的枚举以及数据类型)
枚举指一系列的相关的常量,比如下面关于一个星期的中每天的定义,通过上篇博文,我们可以用在const后跟一对圆括号的方式定义一组常量,这种定义法在go语言中通常用于定义枚举值。go语言并不支持众多其他语言明确支持的enum关键字。
Wyc
2018/09/11
5710
Golang语言社区--Go语言基础第三节常量
大家好,我是彬哥;今天继续我们的基础课程的讲解,本篇给大家讲解的是关于Go语言常量的知识。那么在编程语言中何为常量?常量解释如下:
李海彬
2018/03/07
9270
Golang语言社区--Go语言基础第三节常量
Go语言编程设计学习Day1:helloworld 变量 常量
简单了解一下go语言的历史, 二十世纪八十年代,肯·汤普逊在内的Plan 9项目原班人马加入了谷歌在Google,他们创造了Go语言。早在2007年9月,Go语言还是这帮大牛的20%自由时间 的实验项目。幸运的是,到了2008年5月,Google发现了Go语言的巨大潜力,从而开始全力支持 这个项目,让这批人可以全身心投入Go语言的设计和开发工作中。Go语言的第一个版本在2009 年11月正式对外发布,并在此后的两年内快速迭代,发展迅猛。第一个正式版本的Go语言于2012 年3月28日正式发布,让Go语言迎来了第一个引人瞩目的里程碑。
冷环渊
2022/12/16
5140
Go语言编程设计学习Day1:helloworld 变量 常量
go语言基本数据类型和变量
使⽤关键字 var 定义变量,⾃动初始化为零值。如果提供初始化值,可省略变量类型,由编译器⾃动推断。
onenewcode
2024/02/09
1510
【Go语言】【3】GO语言常量
  通常情况下语言入门前会讲一堆背景和特性,目的是为了能让学习者对该语言有一个大概的认识,但这里我想直接进入细节,不是因为任性,而是因为常量是编程语言最基本的组成元素,这与语言背景无关。
py3study
2020/01/07
2K0
Go语言中的变量和常量
在Go语言中,变量的声明方式多种多样,可以通过var关键字、短变量声明和批量声明等方式实现。
数字扫地僧
2024/06/11
1270
Go常量 【Go语言圣经笔记】
常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数值型。
Steve Wang
2021/12/06
3380
Go 语言基础入门教程 —— 常量和枚举
在 Go 语言中,常量是指编译期间就已知且不可改变的值,常量只可以是数值类型(包括整型、 浮点型和复数类型)、布尔类型、字符串类型等标量类型。和 PHP 一样,在 Go 语言中,我们可以通过 const 关键字来定义常量(遵循 C 语言的约定)。
学院君
2019/08/08
5940
Go语言数据类型
无论在什么语言中,数据类型主要用于声明或定义不同类型的变量、常量、函数等数据结构,当然在Go语言中也不例外。变量的类型主要目的是为了区分数据在内存中的存储大小。Go语言中的类型主要有以下几种分类:
leoay 技术
2023/06/12
1830
Go语言数据类型
Go语言之旅:变量、常量及类型
因为面向dotnet(C#)的程序员有很多基础语言的知识已经具备,在这一节,我会把那些Golang特别的或者需要注意的部分摘出来,加以演示可能会更快上手和了解。还是从最简单的变量定义、类型、操作符、控制语句等等入手
杨子翔 josonyang
2019/08/31
5640
Go语言之旅:变量、常量及类型
Go语言的基本概念与语法 - Java技术债务
按照约定,包名与导入路径的最后一个元素相同。例如,"math/rand" 包中的源码均以packagerand` 语句开始.
Java技术债务
2024/06/21
920
【Go语言基础】基本数据类型
Go语言中内置了大量的数据类型,包括基本数据类型和符合数据类型,其中基本数据类型有如下七种:
felixzhao
2022/01/05
3530
【Go语言基础】基本数据类型
深入理解C语言中的枚举
在上面的例子中,我们手动指定了枚举常量的值,RED为1,GREEN为2,BLUE为3。
GG Bond1
2024/06/14
1470
Go语言的iota关键字有什么用途?
在Go语言中,iota是一个特殊的常量生成器,它用于在const声明中创建一系列递增的常量值。iota在每次遇到新的const关键字时都会重置为0,然后在每个有常量声明的行上递增1。这个特性使得iota非常适合用于定义枚举类型或生成一组相关的常量。
科技新语
2024/07/01
1010
Go语言的iota关键字有什么用途?
05.Go语言-基本数据类型
在静态类型语言(C++/Java/Golang 等)中规定在创建一个变量或者常量时,必须要指定出相应的数据类型,否则无法给变量分配内存。
面向加薪学习
2022/09/04
3510
【Go语言精进之路】构建高效Go程序:掌握变量、常量声明法则与iota在枚举中的奥秘
在Go语言体系中,变量是存储数据的基本单元,其核心功能在于保存程序运行过程中的信息。每个变量都被赋予了特定的数据类型,这些类型涵盖了诸如整数(int)、浮点数(float)、字符串(string) 等多种基本类型以及其他复合类型。数据类型定义了变量能够存储值的范围和类型,确保了数据的准确性和一致性。
空白诗
2024/06/14
1640
【Go语言精进之路】构建高效Go程序:掌握变量、常量声明法则与iota在枚举中的奥秘
Go 语言中没有枚举类型,但是我们可以这样做
枚举类型是一种常用的数据类型,用于表示一组有限的、预定义的、具名的常量值。在枚举类型中,每个常量都是一个枚举值,它们之间的值相等且唯一。
陈明勇
2023/04/26
6950
Go 语言中没有枚举类型,但是我们可以这样做
go语言中的数据类型
注意,在 Go 语言中有些类型是预定义的别名类型,如 byte 和 rune,它们 实际上是 uint8 和 int32 的别名。还有一种特殊的类型 nil 表示空值,可以表示指针、切片、映射、通道、函数和接口类型的零值。
GeekLiHua
2025/01/21
880
Go 专栏|变量和常量的声明与赋值
上篇文章介绍了环境搭建,并完成了学习 Go 的第一个程序 Hello World。这篇文章继续学习 Go 的基础知识,来看看变量,常量的声明与赋值。
AlwaysBeta
2021/09/02
1.1K0
Go 专栏|变量和常量的声明与赋值
go语言慢速入门——基本内置类型
表中特地强调了类型是否支持类型转换,这是因为go语言对类型要求是非常严格的,是真正的强类型语言。一个具体的例子如下所示:
zy010101
2022/07/27
4600
推荐阅读
相关推荐
一起来学Go --- (go的枚举以及数据类型)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档