通过 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
中可以解析出来:
9 Comments
真应该养肥再看的。。。
四五两步的请求验证 ,我是直接写在方法里的,想问下单独创建StoreCafeRequest请求类 和直接写$request->validate哪种好
自然是写到请求验证类里面好
前端验证用个前端框架验证比较好些吧?直接这样写要写多少代码呀!
{message: "The given data was invalid.", errors: {zip: ["无效的邮政编码"]}}
请问,后端的这个422错误验证怎么给到前端显示出来
js 从响应数据中提取啊
.catch(function()){console.log(response)};输出的是: Error: "Request failed with status code 422" createError http://blumer.cn/js/app.js:2200 settle http://blumer.cn/js/app.js:2363 handleLoad http://blumer.cn/js/app.js:1733 无法获取 后端的422错误验证,请问怎么从 js 的响应中中获取到?
试试
.catch(function(e)){console.log(e.message)};
这里的 validations 这个验证方式不可用了吧。。我这边一直报错,
提示数据时 undefined