通过 JavaScript 和 Laravel 验证表单请求


上一篇教程中,我们结合 Vuex 和 Laravel 实现了表单提交功能,目前看来,工作的很好,但是有个遗憾,就是没有对表单提交数据做任何校验,所以我们将在这篇教程中来弥补这个缺憾,并且在前端和后端都加上校验。

第一步:在 NewCafe.vue 组件中添加前端校验

首先我们在前端给表单提交添加校验功能,打开 NewCafe.vue,在数据模型中新增一个 validations 对象为每个字段设置验证占位符:

data() {
    return {
        name: '',
        address: '',
        city: '',
        state: '',
        zip: '',
        validations: {
            name: {
                is_valid: true,
                text: ''
            },
            address: {
                is_valid: true,
                text: ''
            },
            city: {
                is_valid: true,
                text: ''
            },
            state: {
                is_valid: true,
                text: ''
            },
            zip: {
                is_valid: true,
                text: ''
            }
        }
    }
},

其中 is_valid 字段标识是否验证成功,而 text 字段标识验证文本。

第二步:添加验证失败通知

接下来,需要实现某个字段验证失败给用户发送通知。我们为每个输入字段定义一个 <span> 元素来展示验证失败消息,例如,针对 name 字段对应的 <span> 元素如下:

<span class="validation" v-show="!validations.name.is_valid">{{ validations.name.text }}</span>

其他字段依次类推,这样,我们就可以标记出验证失败的输入字段并自定义验证失败消息:

<div class="grid-x grid-padding-x">
      <div class="large-12 medium-12 small-12 cell">
          <label>名称
              <input type="text" placeholder="咖啡店名" v-model="name">
          </label>
          <span class="validation" v-show="!validations.name.is_valid">{{ validations.name.text }}</span>
      </div>
      <div class="large-12 medium-12 small-12 cell">
          <label>地址
              <input type="text" placeholder="地址" v-model="address">
          </label>
          <span class="validation" v-show="!validations.address.is_valid">{{ validations.address.text }}</span>
      </div>
      <div class="large-12 medium-12 small-12 cell">
          <label>城市
              <input type="text" placeholder="城市" v-model="city">
          </label>
          <span class="validation" v-show="!validations.city.is_valid">{{ validations.city.text }}</span>
      </div>
      <div class="large-12 medium-12 small-12 cell">
          <label>省份
              <input type="text" placeholder="省份" v-model="state">
          </label>
          <span class="validation" v-show="!validations.state.is_valid">{{ validations.state.text }}</span>
      </div>
      <div class="large-12 medium-12 small-12 cell">
          <label>邮编
              <input type="text" placeholder="邮编" v-model="zip">
          </label>
          <span class="validation" v-show="!validations.zip.is_valid">{{ validations.zip.text }}</span>
      </div>
      <div class="large-12 medium-12 small-12 cell">
          <a class="button" v-on:click="submitNewCafe()">提交</a>
      </div>
</div>

此外,还定义了一个样式文件 resources/assets/sass/components/_validations.scss,并在 resources/assets/sass/app.scss 中引入,对应文件请在 https://github.com/nonfu/roastapp 源码中查看。

第三步:构建 JavaScript 验证函数

我们将在这一步定义真正的前端验证实现,对应的字段验证规则描述如下:

  • name:必填字符串
  • address:必填字符串
  • city:必填字符串
  • state:必填字符串
  • zip:必填字符串并且必须是格式正确的邮政编码

接下来在 NewCafe 组件的 methods 中新增一个表单验证函数 validateNewCafe,在该函数中初始化一个判定表单是否验证成功的变量,默认设置为 true,并将其返回:

validateNewCafe: function () {
    let validNewCafeForm = true;
    return validNewCafeForm;
}

然后在 submitNewCafe 方法中调用这个方法,如果表单验证通过,才会提交表单:

submitNewCafe: function () {
    if (this.validateNewCafe()) {
        this.$store.dispatch('addCafe', {
            name: this.name,
            address: this.address,
            city: this.city,
            state: this.state,
            zip: this.zip
        });
    }
},

最后我们来实现 validateNewCafe 方法:

validateNewCafe: function () {
    let validNewCafeForm = true;

    // 确保 name 字段不为空
    if( this.name.trim() === '' ){
        validNewCafeForm = false;
        this.validations.name.is_valid = false;
        this.validations.name.text = '请输入咖啡店的名字';
    }else{
        this.validations.name.is_valid = true;
        this.validations.name.text = '';
    }

    // 确保 address 字段不为空
    if( this.address.trim() === '' ){
        validNewCafeForm = false;
        this.validations.address.is_valid = false;
        this.validations.address.text = '请输入咖啡店的地址!';
    }else{
        this.validations.address.is_valid = true;
        this.validations.address.text = '';
    }

    //  确保 city 字段不为空
    if( this.city.trim() === '' ){
        validNewCafeForm = false;
        this.validations.city.is_valid = false;
        this.validations.city.text = '请输入咖啡店所在城市!';
    }else{
        this.validations.city.is_valid = true;
        this.validations.city.text = '';
    }

    //  确保 state 字段不为空
    if( this.state.trim() === '' ){
        validNewCafeForm = false;
        this.validations.state.is_valid = false;
        this.validations.state.text = '请输入咖啡店所在省份!';
    }else{
        this.validations.state.is_valid = true;
        this.validations.state.text = '';
    }

    // 确保 zip 字段不为空且格式正确
    if( this.zip.trim() === '' || !this.zip.match(/(^\d{6}$)/) ){
        validNewCafeForm = false;
        this.validations.zip.is_valid = false;
        this.validations.zip.text = '请输入有效的邮编地址!';
    }else{
        this.validations.zip.is_valid = true;
        this.validations.zip.text = '';
    }

    return validNewCafeForm;
}

我们会对输入字段按照相应的验证规则进行验证,如果验证失败,则设置对应字段的 is_valid 值为 false,同时将表单验证变量 validNewCafeForm 设置为 false,并验证失败消息字段 text 以便在之前定义的 <span> 元素中显示。

至此,我们的前端表单验证功能就完成了,运行 npm run dev 重新构建项目,就可以在 http://roast.test/#/cafes/new 页面中测试了:

完成了前端表单验证还不够,接下来还要在 Laravel API 端对请求数据进行验证,以避免非法用户通过 API 方式调用接口,绕开浏览器 JavaScript 验证逻辑。

第四步:为新增咖啡店构建 Laravel 请求验证类

现在让我们开始编写服务端验证代码吧,在 Laravel 后端编写代码比较轻松,因为 Laravel 已经自带了验证器功能,我们只需要组织代码调用相应的 API 就可以了,我们将通过表单请求验证来实现我们新增咖啡店的请求验证。

首先需要创建请求验证类:

php artisan make:request StoreCafeRequest

该命令会在 /app/Http/Requests 目录下创建一个 StoreCafeRequest 请求验证类,我们将通过这个验证类验证新增咖啡店请求。

打开这个 StoreCafeRequest.php 文件,这个请求验证类默认包含两个方法:一个是 authorize() 方法,用于判断请求者是否有权限访问这个请求,由于我们已经在路由定义的时候定义过通过相应中间件 auth:api 进行权限过滤,所以这里将返回值设置为 true 即可;另一个是 rules() 方法,用于设置各个请求字段的验证规则,我们主要关注这个方法(Laravel 支持的完整验证规则可参考官方文档),编写字段验证规则如下:

public function rules()
{
    return [
        'name'    => 'required',
        'address' => 'required',
        'city'    => 'required',
        'state'   => 'required',
        'zip'     => 'required|regex:/\b\d{6}\b/'
    ];
}

这样,我们就完成了服务器端验证规则编写,对应验证失败请求,Laravel 将会返回 400 响应( Ajax 请求返回状态码为 422),并返回验证失败信息,我们可以在客户端通过捕获响应状态码及失败消息进行处理即可。

当然这还不算完,我们还可以为验证失败请求自定义每个字段的失败消息,并且需要将这个请求验证类添加到控制器路由中才能生效。

第五步:自定义验证失败消息

对于验证失败字段,会有默认的失败消息,但往往因为可读性问题并不能满足我们的需要,我们可以在请求验证类中对验证字段失败消息进行自定义,只需在这个类中重写 messages() 方法即可,该方法和 rules 方法类似,也返回键值对数组,对应的消息自定义格式如下:

{key}.{validation} => {message}

我们可以为每个字段的每个验证规则自定义验证失败消息,也可以自定义一个验证失败消息子集。以 StoreCafeRequest 为例,我们可以定义 messages() 方法如下:

public function messages()
{
    return [
        'name.required'     => '咖啡店名字不能为空',
        'address.required'  => '咖啡店地址不能为空',
        'city.required'     => '咖啡店所在城市不能为空',
        'state.required'    => '咖啡店所在省份不能为空',
        'zip.required'      => '咖啡店邮编不能为空',
        'zip.regex'         => '无效的邮政编码'
    ];
}

至此,我们已经完成新增咖啡店请求验证类的编写,StoreCafeRequest.php 的完整代码如下:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreCafeRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name'    => 'required|max:10',
            'address' => 'required',
            'city'    => 'required',
            'state'   => 'required',
            'zip'     => 'required|regex:/\b\d{6}\b/'
        ];
    }

    public function messages()
    {
        return [
            'name.required'     => '咖啡店名字不能为空',
            'name.max'          => '咖啡店名不能超过10个字符',
            'address.required'  => '咖啡店地址不能为空',
            'city.required'     => '咖啡店所在城市不能为空',
            'state.required'    => '咖啡店所在省份不能为空',
            'zip.required'      => '咖啡店邮编不能为空',
            'zip.regex'         => '无效的邮政编码'
        ];
    }

}

接下来,还需要将其添加到控制器路由中,以便实现请求验证。

第六步:添加请求验证类到控制器路由

打开 App\Http\Controllers\API\CafesController.php,将 StoreCafeRequest 注入到 postNewCafe() 方法:

public function postNewCafe(StoreCafeRequest $request)
{
    $cafe = new Cafe();

    $cafe->name     = $request->input('name');
    $cafe->address  = $request->input('address');
    $cafe->city     = $request->input('city');
    $cafe->state    = $request->input('state');
    $cafe->zip      = $request->input('zip');

    $cafe->save();

    return response()->json($cafe, 201);
}

注:如果你使用的不是 PHPStorm 这种集成 IDE,还需要在控制器顶部引入这个类:use App\Http\Requests\StoreCafeRequest;

这样,在这个方法运行之前会自动自行请求验证类 StoreCafeRequest,如果验证失败会直接返回,验证成功才会继续方法内代码执行。

至此,我们已经完成了新增咖啡店请求前后端验证,我们测试一个咖啡店名小于两个字符的表单提交,前端验证成功,但后端验证失败,返回状态码为 422,错误消息在 errors 中可以解析出来:


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 通过 Vue 组件、Vue Router、Vuex 和 Laravel 实现表单提交

>> 下一篇: 通过高德地图 Web 服务 API 对咖啡店地址进行地理编码