阿江 2017-10-03 06:56:00 7907次浏览 2条回复 1 0 0

说明

学习Yii Framework 2易2框架的过程是漫长的也是充满乐趣的以下是我学习Yii2框架时对官网英文资料(请参见原文网址)的翻译和代码实现提供了较完整的代码供你参考不妥之处请多多指正

原文网址:

http://www.yiiframework.com/doc-2.0/guide-input-validation.html

本文主题:输入验证(Validating Input)

1、声明规则 2、特殊的验证器 3、创建验证器 4、多属性验证 5、客户端验证

根据经验,永远不能相信终端用户输入的数据,永远在使用前进行有效的验证。

给定一个由用户输入绑定的模型,你可以使用yii\base\Model::validate()方法来验证输入项,此方法会返回一个布尔值,此值说明了此次验证是成功还是失败,如果失败,则从yii\base\Model::$errors 属性中获取错误信息,例如:

$model=new \app\models\ContactForm();
//使用用户输入添充模型属性//块赋值仅对safe属性生效
$model->load(\Yii::$app->request->post());//表单名称默认值是'ContactForm'
//$model->attributes=\Yii::$app->request->post('ContactForm');//与上句等效,指定表单名称是'ContactForm'

//对模型的属性初始化操作,可以使用以下语句:
$model=new \app\models\ContactForm(['name'=>'monster']);

if($model->validate()){
	//所有输入都是有效的
}else{
	//验证失败:$errors是一个包含错误信息的数组
	$errors=$model->errors;
}
1、声明规则(Declaring Rules)

要使用validat()生效,你要给准备验证的属性定义验证规则,可以通过重写yii\base\Model::rules()方法来实现。下例展示了如何为ContactForm模型定义验证规则:

public function rules(){
	return [
		//'name','email','subject','body'这四个属性是必填的
		[['name','email','subject','body'],'required'],	
		//'email'属性必须是一个有效的email地址
		['email','email'],
	];
}

rules()方法将返回一个规则数组,每个规则也是一个子数组,子数组的格式如下:

[
	//必填项,定义哪一个属性将被此条规则验证,如果只有一个属性,可以直接使用它的名称,而无需包含在一个数组中。
	['attribute1','attribute2',...],
	//必填项,定义规则的类型,它可以是一个类名称、验证器的别名,或者是一个验证方法。
	'validator',
	//可选项,定义此规则将适用于哪个场景,如果没有指定,意味着此规则适用于所有场景,如果你想要排除一个场景,你可以配置'except'选项。
	'on'=>['scenario1','scenario2'],
	//可选项,为验证对象定义额外的配置。
	'property1'=>'value1','property2'=>'value2',...
]

对于每一个规则,你至少需要定义适用于哪个属性和规则的类型,你可以定义规则类型为以下形式: 1、核心验证器的别名,例如required、in、date等,请参考核心验证器章节:

http://www.yiiframework.com/doc-2.0/guide-tutorial-core-validators.html

2、模型类中验证器方法的名称,或者是一个匿名函数,请参考行内验证器(Inline Validators)章节:

http://www.yiiframework.com/doc-2.0/guide-input-validation.html#inline-validators

3、一个完整的验证器类名,请参考独立验证器(Standalone Validators)章节:

http://www.yiiframework.com/doc-2.0/guide-input-validation.html#standalone-validators

一个规则可以用于验证一个或多个属性,一个属性也可以被一条或多条规则所验证。一条规则可以使用on选项应用于特定场景,如果没有定义on选项,意味着此规则适用于所有场景。

当validate()方法被调用时,它将执行以下验证步骤: 1、使用yii\base\Model::scenarios()方法获取当前scenario需要验证的属性,这些属性被称为活动属性(active attribute)。 2、通过yii\base\Model::rules()方法获取当前scenario需要验证的规则,这些规则被称为活动规则(active rule)。 3、针对当前规则使用每条活动规则验证每个活动属性。验证规则的按照它们排列的先后顺序执行。

根据以上验证步骤,当且仅当在scnerios()中声明的一个活动属性才会被验证,此属性将被在rules()中定义的与其相关的一条或多条活动规则所验证。

注意:给规则定义一个名称将会比较方便使用。

public function rules(){
	return [
		'password'	=>[['password'],'string','max'=>60],
	];
}
//你可以在一个子模型中使用它:
public function rules(){
	$rules=parent::rules();
	unset($rules['password']);
	return $rules;
}
自定义错误信息(Customizing Error Messages)

很多验证器有默认错误信息,此信息会被添加到模型中用于验证,当验证失败时会显示出来。例如:"required"验证器会添加一条信息"Username cannot be blank"到模型中,当username属性验证失败时,此信息将显示。 你可以为规则自定义错误信息,在规则中添加一个message选项即可,如:

public function rules(){
	return [
		['username','required','message'=>'请输入用户名'],
	];
}

一些验证器支持附加额外的信息以更精确的描述验证失败,例如:number验证器在验证的值过大或过小时,可支持tooBig和tooSmall以描述验证失败时的错误信息。你可以在规则中象配置其他选项一样配置这些错误信息。

//min最小是10//max最大是20,tooBig、tooSmall仅对数字型的max、min检测生效,string类型报错
['priceA','number','max'=>20,'min'=>10,'tooBig'=>'数字太大啦!','tooSmall'=>'数字太小了!'],
验证事件(Validation Events)

当yii\base\Model::validate()被调用时,它将调用两个方法,这样你可以重写这两个方法以实现自定义的验证过程: yii\base\Model::beforeValidate():默认实现将触发yii\base\Model::EVENT_BEFORE_VALIDATE事件,你可以重写此方法,或在验证开始前响应此事件以处理一些工作(例如:标准化输入数据),此方法将返回一个布尔值以确定此验证是否继续。 yii\base\Model::afterValidate():默认实现将触发yii\base\Model::EVENT_AFTER_VALIDATE事件,你可以重写此方法,或在验证完成之后去执行一些任务。

条件验证(Conditional Validation)

当特定条件时才去验证此属性,例如,一个属性的验证依赖于另一个属性的值,你可以使用when选项定义此条件,例如:

['state','required','when'=>function($model){
	return $model->country=="USA";
}]

when选项的回调函数使用以下格式:

/*
* @param Model $model,将要验证的模型
* @param string @attribute,正在被验证的属性
* @return bool,此验证规则是否被应用
*/
function($model,$attribute)

如果你需要客户端的条件验证,你应配置whenClient选项,它是一个JavaScript函数字符串,此函数将返回一个值,由此值决定是否应用此验证规则,例如:

[
	'state', 'required', 'when' => function ($model) {
        return $model->country == 'USA';
    }, 'whenClient' => "function (attribute, value) {
        return $('#country').val() == 'USA';
    }"
]
数据过滤(Data Filtering)

用户输入经常需要过滤或处理,例如,你想要去掉username输入项的首尾空格,你可以使用验证器规则来实现它。 下例展示了如何使用核心验证器trim和default将输入项的首尾空格去掉,并在空输入时赋值为null。

return [
	[['username','email'],'trim'],	
	[['username','email'],'default'],
];

你也可以使用更通用的filter验证器执行更为复杂的数据过滤。 正如你所看到的,这些验证器规则并没有真正去验证这些输入,实际上,它们仅仅只是将验证的属性加工处理一下,然后再返回给此属性。

处理空输入

当从HTML表单的输入数据被提交时,你常常需要将未输入的选项赋一个默认值,你可以以使用default验证器,例如:

return [
	//当'username','email'为空时,赋值为null
	[['username','email'],'default'],
	//当level为空时,赋值为1
	['level','default','value'=>1],
];

默认情况下,当输入项是一个空字符串、一个空数组或是null时,则被判定为空,你可以通过配置yii\validator\Validator::isEmpty()属性,调用一个回调函数以实现自定义默认的空值逻辑,例如:

[
	'agree','required','isEmpty'=>function($value){
		return empty($value);
	}
]

注意:因yii\validators\Validator::$skipOnEmpty 属性的默认值是true,所以大多数验证器都不处理空值输入。当规则相关联的属性收到一个空输入时,此规则将被简单忽略。在核心验证器中,只有captcha、default、filter、required、trim这五个验证器处理空输入。

2、特殊的验证器(Ad Hoc Validation)

有时你需要为没有绑定到任何模型的输入项执行特殊验证器。 如果你仅需要执行一种类型的验证(例如验证email地址),你可以调用特定验证器的validate()方法,如下所示:

//email,无模型的数据验证:
$email = 'test@example.com';
$validator = new yii\validators\EmailValidator();
if ($validator->validate($email, $error)) {
    echo 'Email is valid.';
} else {
    echo $error;
}

注意:不是所有的验证器都支持此类型的验证,例如:unique验证器只能与模型一起工作。

如果你需要对多个值执行多个验证器,你可以使用yii\base\DynamicModel,它支持即时声明属性和规则,它的示例如下:

public function actionSearch($name,$email){
	$model=DynamicModel::validateData(compact('name','email'),[
		[['name','email'],'string','max'=>128],	
		['email','email'],
	]);
	if($model->hasErrors()){
		//验证失败
	}else{
		//验证成功
	}
}

yii\base\DynamicModel::validateData()方法创建了一个DynamicModel的实例,使用给定的数据(本例中是name和email)定义为属性,然后使用给定的规则调用yii\base\Model::validate()进行验证。

另外,你也可以使用更加“类方式”的格式来执行特定数据验证:

public function actionSearch($name, $email)
{
    $model = new DynamicModel(compact('name', 'email'));
    $model->addRule(['name', 'email'], 'string', ['max' => 128])
        ->addRule('email', 'email')
        ->validate();

    if ($model->hasErrors()) {
        //验证失败
    } else {
        //验证成功
    }
}

验证后,你需要调用hasErrors()方法检查验证是否通过,然后与普通模型的处理一样,从errors属性中获取验证错误信息。你可以使用已定义的模型实例来获取动态属性,如:$model->name,$model->email。

3、创建验证器(Creating Validators)

除了使用Yii内置的核心验证器外,你也可以创建自己的验证器。你可以创建行内验证器或独立验证器。

行内验证器(Inline Validator)

行内验证器定义的方式是使用一个模型方法或匿名函数,方法或函数的格式:

/**
 * @param string $attribute,当前正在被验证的属性
 * @param mixed $params,在规则中给定的名为"params"参数值
 * @param \yii\validators\InlineValidator,相关联的行内验证器实例,此参数自2.0.11版本生效。
 */
function ($attribute, $params, $validator)
//检测priceA不能大于10,$params,就是规则内名为params的一个选项作为参数。
//需要设置ActiveForm:'enableAjaxValidation' => true,
['priceA','required'],//只需要此句即可,function ($attribute, $params) {}会自动验证priceA是否是数字,非数字则报错。
['priceA', function ($attribute, $params) {
	if (($this->$attribute>=10)) {
		$this->addError($attribute, 'The PriceA max is 10.std:'.$params['std']);
	}
}, 'params' => ['std'=>185]],

如果一个属性验证失败,此方法/函数将调用yii\base\Model::addError()将错误信息保存在模型中,以便于调出后显示给用户。 下面是个例子:

use yii\base\Model;
class MyForm extends Model{
    public $country;
    public $token;

    public function rules(){
        return [
			//一个行内验证器,以一个模型方法validateCountry()的形式定义
            ['country', 'validateCountry'],

			//使用一个匿名函数定义一个行内验证器
            ['token', function ($attribute, $params, $validator) {
                if (!ctype_alnum($this->$attribute)) {
                    $this->addError($attribute, 'The token must contain letters or digits.');
                }
            }],
        ];
    }

    public function validateCountry($attribute, $params, $validator)
    {
        if (!in_array($this->$attribute, ['USA', 'Web'])) {
            $this->addError($attribute, 'The country must be either "USA" or "Web".');
        }
    }
}

注意:自2.0.11版本起,你可以使用yii\validators\InlineValidator::addError()去添加错误信息,这样就可以在错误信息中直接使用yii\i18n\I18N::format()。在错误信息中使用{attribute}和{value}指向一个属性标签(无需手动获取)和属性值:

$validator->addError($this,$attribute,'The value"{value}" is not acceptable for {attribute}');

注意:缺省情况下,如果表单项输入的是空值或在其他规则中已验证失败,则行内验证器将不会被调用。如果你需要确定一个规则始终被调用,你可以在规则声明时配置skipOnEmpty和skipOnError选项为false。例如:

[
	['country','validateCountry','skipOnEmpty'=>false,'skipOnError'=>false],	
]
独立验证器(Standalone Validator)

独立验证器是一个继承自yii\validators\Validator或其子类的类,你可以通过重写yii\validators\Validator::validateAttribute()方法来它的验证逻辑,如果一个属性验证失败,与行内验证器一样,将调用yii\base\Model::addError()保存错误信息到模型中去。 例如,上例中的行内验证器可以放到[[components/validators/CountryValidator]]类中。

namespace app\components;
use yii\validators\Validator;
class CountryValidator extends Validator{
	public function validateAttribute($model,$attribute){
		if(!in_array($model->$attribute,['USA','Web'])){
			$this->addError($model,$attribute,'国家必须是"USA"或"Web"');
		}
	}
}

如果你要让验证器支持无模型的验证器,你可以重写yii\validators\Validator::validate()。你也可以重写yii\validators\Validator::validateValue()替换validateAttribute()和validate(),后面这两个方法是通过调用validateValue()方法实现的。 如何在模型中使用验证器类请看下例:

namespace app\models;
use Yii;
use yii\base\Model;
use app\components\validators\CountryValidator;
class EntryForm extends Model{
	public $name;
	public $email;
	public $country;
	public function rules(){
		return [
			[['name','email'],'required'],
			['country',CountryValidator::className()],
			['email','email'],
		];
	}
}
实例:独立验证器

D:\phpwork\advanced\frontend\views\post\validator.php

源代码:
<?php
    $form = ActiveForm::begin([
        'method'=>'post',
        'enableAjaxValidation' => true,//此项必有
    ]);
?>
<?= $form->field($model, 'priceA')->textInput(); ?>

<? D:\phpwork\advanced\frontend\models\Validator.php

源代码:
<?
use frontend\components\validators\CountryValidator;
class  Validator extends Model{
    public $priceA;
	public function rules(){
		return [
			['priceA','required'],
			['priceA',CountryValidator::className()],
			.....
		 ]
	}

D:\phpwork\advanced\frontend\components\validators\CountryValidator.php

源代码:
<?
namespace frontend\components\validators;
use yii\validators\Validator;
class CountryValidator extends Validator{
    public function validateAttribute($model, $attribute) {
        if($model->$attribute<10){
            $this->addError($model,$attribute,"$attribute must great than 10");
        }elseif($model->$attribute>100){
            $this->addError($model,$attribute,"$attribute must less than 100");
        }
    }
}
4、多属性验证(Multiple Attributes Validation)

有时验证器包含多个属性,如下例所示:

class MigrationForm extends \yii\base\Model{
	//最小成人基金金额
	const MIN_ADULT_FUNDS=3000;
	//一个孩子的最小资金
	const MIN_CHILD_FUNDS_1500;
	public $personalSalary;
	public $spouseSalary;
	public $childrenCount;
	public $description;
	public function rules(){
		return [
			[['personalSalary','description'],'required'],	
			[['personalSalary','spouseSalary'],'integer','min'=>self::MIN_ADULT_FUNDS],
			['childCount','integer','min'=>0,'max'=>5],
			[['spouseSalary','childrenCount'],'default','value'=>0],
			['description','string'],
		];
	}
}
创建验证器

让我们看个例子:检测一下家庭收入是否能够满足孩子们。我们可以创建一个行内验证器validateChildrenFunds,这个验证器将在childrenCount在大于0时进行检查。注意:附加验证器时我们不能使用所有的验证属性(['childCount','personalSalary','spouseSalary']),因为同样的验证器将对每个属性验证一次(总共是3次),但我们仅仅只是需要为整个属性集运行一次即可。

//上面几句话的意思是使用:

'childrenCount','validateChildrenFunds'

//而不是使用:

['childCount','personalSalary','spouseSalary'],'validateChildrenFunds'

你可以使用下面这个属性来替换(或使用你认为最相关的一个属性):

[
	'childrenCount','validateChildrenFunds'	,'when'=>function($model){
		return $model->childrenCount>0;
	}
],

validateChildrenFunds()如下:

public function validateChilerenFunds($attribute,$params){
	$totalSalary=$this->personalSalary+$this->spouseSalary;
	//如果配偶收入被定义,则最小成人基金将翻倍
	$minAdultFunds=$this->spouseSalary?self::MIN_ADULT_FUNDS*2:self::MIN_ADULT_FUNDS;
	$childFunds=$totalSalary-$minAdultFunds;
	if($childFunds/$this->childrenCount<self::MIN_CHILD_FUNDS){
		$this->addError('childrenCount','你的工资不够这些孩子们花的!');

	}
}

当验证属性多于一个时,你可以忽略$attribute 参数。

设置多个验证器的默认值:
[['fromDate', 'toDate'], 'default', 'value' => function ($model, $attribute) {
	//$model,当前验证的模型;$attribute,当前验证的字段名称
	return date('Y-m-d', strtotime($attribute === 'toDate' ? '+3 days' : '+6 days')).$model->priceA;
}],
添加错误(Adding errors)

为多个属性添加错误可以通过表单设计来实现: 1、选择关联度最大的属性添加错误:

$this->addError('childCount','你的工资不够这些孩子们花的!');

2、选择多个重要的关联属性或所有属性添加错误,在传送到addError之前,我们可以存储信息到变量中以保持代码整洁。

$message='你的工资不够这些孩子们花的!';
$this->addError('personalSalary',$message);
$this->addError('wifeSalary',$message);
$this->addError('childrenCount',$message);

或使用循环:

$attributes=['personalSalary', 'wifeSalary', 'childrenCount'];
foreach($attributes as $attribute){
	$this->addError($attribute,'你的工资不够这些孩子们花的!');
}

3、添加一个公共错误(不与特定属性相关联),我们可以使用不存在的属性来添加错误,例如:*,此时不进行属性检测。

$this->addError('*','你的工资不够这些孩子们花的!');

这样一来,我们可能就无法在表单元素位置看到错误信息了,要显示它,我们可以在视图中添加一个错误汇总:

<?=$form->errorSummary($model)?>

注意:创建多属性验证器可以参考community cookbook: https://github.com/samdark/yii2-cookbook/blob/master/book/forms-validator-multiple-attributes.md

5、客户端验证(Client-Side Validation)

客户端验证是为用户输入提供基于JavaScript的验证方式,因为它允许客户发现输入的错误并提供了更好的客户体验,所以可以提升客户的满意度。你可以在添加服务端验证器时使其支持客户端验证器。 信息:客户端验证器可提高满意度,但它不是必须的。它的主要作用是为用户提供一个更好的体验。与从终端用户处获取的输入数据一样,你永远不要相信客户端的验证,正因为如此,你需要始终调用yii\base\Model::validate()执行服务器端的验证。

使用客户端验证(Using Client-Side Validation)

很多核心验证器支持即拆即用(out-of-the-box)的客户端验证,需要你做的就是使用yii\widgets\ActiveForm去构建你的表单,例如,下例中的LoginForm声明了两个规则:一个使用核心验证器required,它同时支持客户端和服务器端的验证;另一个使用validatePassword行内验证器,它只支持服务器端的验证。

namespace app\models;
use yii\base\Model;
use app\models\User;
class LoginForm extends Model{
    public $username;
    public $password;
    public function rules(){
        return [
			//用户名和密码都是必填项(required)
            [['username', 'password'], 'required'],

			//密码项password由validatePassword()验证
            ['password', 'validatePassword'],
        ];
    }
    public function validatePassword(){
        $user = User::findByUsername($this->username);

        if (!$user || !$user->validatePassword($this->password)) {
            $this->addError('password', '用户名或密码错误!');
        }
    }
}

使用以下代码构建HTML表单,包括两个输入字段username和password,如果提交的表单没有任何输入,你会发现没有任何与服务器端的通信就会收到错误信息提示你需要输入内容。

<?php $form=yii\widgets\ActiveForm::begin();?>
	<?=$form->field($model,'username')?>
	<?=$form->field($model,'password')->passwordInput()?>
	<?=Html::submitButton('Login')?>
<?php yii\widgets\ActiveForm::end();?>

在这种情况下,yii\widgets\ActiveForm将会读取模型中声明的验证规则并为之生成相应的JavaScript代码,以支持客户端验证。当一个用户更改了输入字段的值或提交了表单,客户端的JavaScript验证将被触发。 如果你想完全关闭客户端验证,你可以配置yii\widgets\ActiveForm::$enableClientValidation 属性为false,你也可以设置单个输入项的yii\widgets\ActiveField::$enableClientValidation 为false关闭它的客户端验证。当enableClientValidation同时在输入项和form级别都做了设置,则前者优先生效。

	//关于客户端验证是否能够生效的问题enableAjaxValidation和enableClientValidation:
	1enableAjaxValidation的优先级高于enableClientValidation
	2ActiveField的设置高于ActiveForm的设置
	3ActiveField和模型中的enableClientValidation都为true时才进行客户端验证
//在视图中为form设置客户端验证
D:\phpwork\advanced\frontend\views\post\validator.php
    $form = ActiveForm::begin([
		//enableAjaxValidation的优先级高于enableClientValidation
	    'enableAjaxValidation' => false,
		//在form中设置enableClientValidation为false,即禁用客户端验证
        'enableClientValidation'=>false,
    ]);
	//单独设置'selectedDefault'输入项的enableClientValidation为true,即进行客户端的验证(ActiveField和模型中的enableClientValidation都为true时才进行客户端验证)
	echo $form->field($model, 'selectedDefault',['enableClientValidation'=>true])->textInput();
//在模型中为字段设置客户端验证,此项设置较form优先生效
D:\phpwork\advanced\frontend\models\Validator.php
	//必填项required()检查是可以运行的,因为其'enableClientValidation'=true(默认值)。
	['selectedDefault','required'],
	//范围in()检查是不会被运行的,因为'enableClientValidation'=false,相当于没有生成客户端的验证程序。(ActiveField和模型中的enableClientValidation都为true时才进行客户端验证)
	['selectedDefault','in','range'=>['ok','active','offline','online'],'enableClientValidation'=>false],
	//此句报错,规则中没有enableAjaxValidation这个选项
	//['selectedDefault','in','range'=>['ok','active','offline','online'],'enableClientValidation'=>false,'enableAjaxValidation' => false],

信息:自2.0.11版开始,所有扩展自yii\validators\Validator的验证器将从独立的方法yii\validators\Validator::getClientOptions()接受客户端选项。你可以使用它: 1、如果你想要去实现自定义的客户端验证,但保留同步的服务器端验证选项; 2、扩展或自定义,以适应你的特殊需求。

public function getClientOptions($model,$attribute){
	$options=parent::getClientOptions($model,$attribute);
	//在此更改$options
	return $options;
}
实现客户端验证(Implementing Client-Side Validation)

要创建一个支持客户端的验证器,你需要实现yii\validators\Validator::clientValidateAttribute()方法,它将返回JavaScript片段以执行客户端验证。在JavaScript代码内,你可以使用以下预定义的变量: attribute:正在被验证的属性名称 value:正在被验证的值 messages:一个数组,记录了与输入项对应的验证错误信息 deferred:一个数组,可以推入的推迟数组

在下例中,我们将创建一个StatusValidator,它将根据已有的状态数据验证一个输入项是有效状态,该验证器支持服务器端和客户端的验证。

namespace app\components;
use yii\validators\Validator;
use app\models\Status;
class StatusValidator extends Validator{
    public function init(){
        parent::init();
        $this->message = '无效状态';
    }
    public function validateAttribute($model, $attribute){
        $value = $model->$attribute;
        if (!Status::find()->where(['id' => $value])->exists()) {
            $model->addError($attribute, $this->message);
        }
    }
    public function clientValidateAttribute($model, $attribute, $view){
        $statuses = json_encode(Status::find()->select('id')->asArray()->column());
        $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        return <<<JS
if ($.inArray(value, $statuses) === -1) {
    messages.push($message);
}
JS;
    }
}

小贴士:上述代码是实现客户端验证的主要代码,在实践中,你可以使用in核心验证器实现同样的目标,in的代码如下:

[
	['status','in','range'=>Status::find()->select('id')->asArray()->column],
]

小贴士:如果需要手动编写客户端验证,例如动态添加字段或处理一些自定义的UI逻辑,请参考Yii2.0 Cookbook的Working with ActiveForm via JavaScript部分: https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md

推迟验证(Deferred Validation)

如果需要执行一个异步的客户端验证,你需要创建推迟对象(Deferred objects),例如,要执行一个自定义的AJAX验证,你可以使用以下代码:

public function clientValidateAttribute($model,$attribute,$view){
	return <<<JS
		deferred.push($.get("/check"),{value:value}.done(function(data){
			if(data!==''){
				message.push(data);
			}
		}));
JS
}

在上例中,Yii提供了一个变量deferred,它是一个Deferred对象的数组,$.get()是jQuery方法,它创建了一个Deferred对象将被推入到defferred数组中。 你也可以明确的创建一个Deferred对象,当异步调用被使用时,可以调用其resolve()方法。下例将展示如何在客户端验证一个上传图片的尺寸:

public function clientValidateAttribute($model, $attribute, $view){
    return <<<JS
        var def = $.Deferred();
        var img = new Image();
        img.onload = function() {
            if (this.width > 150) {
                messages.push('图片太宽了!');
            }
            def.resolve();
        }
        var reader = new FileReader();
        reader.onloadend = function() {
            img.src = reader.result;
        }
        reader.readAsDataURL(file);
        deferred.push(def);
JS;
}

注意:resolve()方法只能在属性验证通过后再调用,否则表单时验证将无法完成。

为了简化使用,deferred数组提供了一个快捷方法add(),它可以自动一个Deferred对象,并添加到deferred数组中。使用这个方法,你可以简化上例的写法:

public function clientValidateAttribute($model, $attribute, $view)
{
    return <<<JS
        deferred.add(function(def) {
            var img = new Image();
            img.onload = function() {
                if (this.width > 150) {
                    messages.push('Image too wide!!');
                }
                def.resolve();
            }
            var reader = new FileReader();
            reader.onloadend = function() {
                img.src = reader.result;
            }
            reader.readAsDataURL(file);
        });
JS;
}
AJAX验证(AJAX Validation)

一些验证器可以仅在服务器端实现,因为只有服务器中才有一些必需要的信息。例如,要验证用户名是否唯一,就有必要去检查服务器端的用户表,此时你可以使用基于AJAX的验证。它将触发一个AJAX请求在后台去完成验证工作,具有如同客户端验证一样的用户体验。 要使某个输入项具有AJAX功能,配置它的enableAjaxValidation属性为true,并定义一个唯一的表单id。

use yii\widgets\ActiveForm;
$form=ActiveForm::begin([
	'id'=>'registration-form',
]);
echo $form->field($model,'username',['enableAjaxValidation'=>true]);
ActiveForm::end();
要将整个表单设置AJAX有效,可以将表单的enableAjaxValidation设为true:
$form = ActiveForm::begin([
    'id' => 'contact-form',
    'enableAjaxValidation' => true,
]);

注意:当输入项和表单的enableAjaxValidation属性都设置时,前者将优先生效。

你还需要在服务器端做些准备以处理AJAX验证请求,在控制器中添加以下代码片段即可实现:

if(Yii::$app->request->isAjax&&$model->load(Yii::$app->request->post())){
	Yii::$app->response->format=Response::FORMAT_JSON;
	return ActiveForm::validate($model);
}

以上代码将检查当前是否是一个AJAX请求,如果是,它将响应请求,运行验证并返回JSON格式的错误信息。

信息:你也可以使用Deferred验证去执行AJAX验证,但不管怎样,此处描述的AJAX验证特性更系统化,使用的代码也更少。

当enableClientValidation和enableAjaxValidation都设为true时,只有当客户端的验证通过后才会触发AJAX验证。

(全文完)

  • 回复于 2017-10-06 12:13 举报

    请问下有没有关于如何重写YII自身类的教程?我想重写Yii验证码类的一个方法。谢谢

    2 条回复
    回复于 2017-10-06 13:07 回复

    继承Yii2验证码类之后覆盖原有方法即可。

    回复于 2017-10-06 14:25 回复

    是的,我就是这么写的,在basic目录创建了common目录,然后创建了Test类继承了yii\captcha\CaptchaAction,重写了validate方法

  • 回复于 2017-10-06 14:25 举报
    class Test extends CaptchaAction{
        public function validate($input, $caseSensitive)
        {
            $code = $this->getVerifyCode();
            $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0;
            $session = Yii::$app->getSession();
            $session->open();
            $name = $this->getSessionKey() . 'count';
            $session[$name] = $session[$name] + 1;
            return $valid;
        }
    }
    

    这么写的,控制器的actions也写对了,因为如果改名了test就会报错不能显示图片。

    'captcha' => [
        'class' => 'app\common\Test',
    

    最后先ajax验证了验证码成功了,但是提交表单会进行第二次验证了就会报错。

    if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) {
        $this->getVerifyCode(true);
    }
    

    这个if代码我没有添加在我重写的validate中,如果删除了源码的就不会重新验证了。
    望您指教一二,是我重写错了吗?

您需要登录后才可以回复。登录 | 立即注册