Validation - Order of Validators or how skipOnError works?

Hi,

I think I’m missing something in order of how validators are executed (are they run in the same order as defined?) or how skipOnError works.

Definition of skipOnError says: "whether this validation rule should be skipped if when there is already a validation error for the current attribute". So, if I get it right, particular validator, that has this param set to true won’t be executed, if there are already other validation errors. Right?

I have this validation rules in one of my forms:


public function rules()

{

    	return array

    	(

            	array('password_current, password_new_one, password_new_two', 'required'),

            	array('password_current', 'authenticate', 'skipOnError'=>true), //Current password needs to be authenticated.

            	array('password_new_one', 'compare', 'compareAttribute'=>'password_new_two'),

            	array('password_current', 'compare', 'compareAttribute'=>'password_new_one', 'operator'=>'!='),

            	array('password_new_one', 'quality', 'skipOnError'=>true), //New password needs to follow quality rules.

    	);

}

In my own quality validator I put echo, that shows me whether this particular validator was or wasn’t fired.

I run test form validation, see message generated by second compare validator and… my echo message.

How can it be that my own validator was fired, if it has skipOnError set to true and if previous validator has already reported validation error?

[size=2]Sequence of defining a validation rule in a model class is very important concept. i was also frustrating so many time with a multiple validation on a attributes.[/size]

  1. But with a use of ‘safe’ attribute i got rid of from this kind of problem.

  2. or using a 'on ’ attribute in array to call a validation on particular action is also very helpful for me.

Might be this will help you or might be i’ll learn something new from you today. :)

Have you implemented skipping on errors in your validator? skipOnError is just a boolean value which has to be implemented. In class validators which extends CValidator it will be already done for you. In member function validators you will have to implement that future by yourself.

That is extremely important fact, that I was not aware of and that I’m strongly missing in documentation (added as comment). Thanks!

One of the best documentation I’ve ever seen is Qt one. Still there are dark spots which are neither obvious nor easy to find. I have a habit of reading relevant bits of code along with documentation. It helps a lot! In case of Yii it’s made easy, because you can see implementation of function right from detailed function description in class reference.

@Trejder

Sorry, but I deleted you comment about skipOnError becasue it was not really clear and could confuse someone.

If you think about it… it makes sense that child classes of CValidator has that attribute (inheritance)… and that methods in the model class does not have them…

And if you create a custom method that you use as a validator… you cannot use there attributes from CValidator…

I think I don’t quite follow you…

Does this means that if I have such validation rule:


array('password_new_one', 'quality', 'skipOnError'=>true), //New password needs to follow quality rules.

where quality is my own, custom validation function (like authenticate in demo app), it will always be fired and I can’t use skipOnError on this?

I was pretty sure that I can. I just have to implement it - as sidewinder wrote. I was pretty sure that, when I dumped params sent to my custom validation function I was able to read skipOnError and make use of it.

But after what you wrote, I’m getting even more confused! :expressionless:

Sorry for the confusion… I myself got confused here a bit…

So to clarify…

When you create a custom validation method (like authenticate in LoginForm)… an CInlineValidator is instantiated from that - http://www.yiiframework.com/doc/api/1.1/CInlineValidator

all values that you add to the rule after the name of the validator are sent to the method as an array to the second parameter ($params)

so if you have:


array('password_new_one', 'quality', 'skipOnError'=>true, 'someothervalue'=>'xx'),

[font=“Arial”]in the method function you can use $params[‘skipOnError’] and $params[‘someothervalue’]

but

The skipOnError value is checked before calling the validator method and if some previous rule did not pass

the method will not be validated…

for this check the source of CValidator->validate() - http://www.yiiframework.com/doc/api/1.1/CValidator#validate-detail

So by following this… your original post say that your "quality" validator is fired always but it should not be called if the first rule did not pass

[/font]


array('password_new_one', 'compare', 'compareAttribute'=>'password_new_two'),

According to sidewinder (that is how I understand his post), when I create my custom validation rule, I have to process (implement) this feature myself!

If this is true and if I get sidewinder right, then you’re wrong (probably for the first time, since I know you! :]). If any of previous validation rules fails, then my custom rule will be fired and I’m responsible to check, whether there already is a validation error or not?

Good question, how to achieve this?

My quick tests proved that my custom validation rule is fired always, no matter what is result of validation done by previous validators. And that would support sidewinder’s opinion. I simply saw standard validation message coming from previous validator and text echoed by my echo in my custom validation rule.

I didn’t check framework source code, but I can do additional tests / screenshots to see, if maybe I’m wrong.

For me it was also obvious that my custom validation rule should not be executed, if any of previous validators has already reported error and if skipOnError is set to true for my custom validation function. But a quick tests proved that it isn’t like that and that is why I started this post.

Well…

you can test this with the "authenticate" for example… put there an echo… and submit the form leaving the password field empty…

Well… Then I have to be a magician!

Here we have a slightly different form. Not login one, but password change. But built over original Yii demo’s login form and using the same autheticate custom validation rule.

And… we have a few validators, of which first one is CRequiredValidator for new password and new password repeated. Then we have authenticate custom validation rule with skipOnFalse enabled and with echo in the code.

2200

yii-validators-code.png

What is the effect of doing, what you said (posting form with empty password)? You can see here:

2201

yii-validators-effect.png

CRequiredValidator fires as supposed saying (in Polish) that both fields can’t be empty and second validator is also fired (echo in left, up corner) thought it shouldn’t be (skipOnError set to TRUE)!

Is this because both validators are set for two different model attributes? If so, then yes - I completely misunderstood, how skipOnFalse works. I supposed that it will halt execution of particular validator (and following ones) if there is already a validation error per current model (form), not necessary for the same field.

Check again the first image… and the rounded box you made…

first rule is required for 2 attributes - password_new_one and password_new_two

second rule that you talk about having skipOnError set to true is for a third attribute - password_current

So if you don’t eneter some/any password… that gets an error for the first two attrbiutes…

but the third attribute is not set as required… so this one does not have any error yet… and that’s why you get the “authenticate” executed


As I always say… if you need to see if something is a Yii bug or not… it’s better to make tests on default Yii code… as the problem can be in our custom code.

I agree with you in general, but not in particular.

Here, there’s no problem with my code, only with understanding, how skipOnError works. For me it was obvious that it is organized on CFormModel level and relates to every form attribute and skips execution of any following validators, no matter, to which attribute they belong). It turned out that it relates only to particular attribute and works like you described. Maybe this is obvious for most developers, but it wasn’t for me until now.

Also notice, that my bug report was about missing element of documentation, not a bug in CValidator itself (code).

I did not understand your thinking :)

Well… in this case the documentation is very clear and it say "current attribute" - http://www.yiiframework.com/doc/api/1.1/CValidator#skipOnError-detail

Yes, you’re right.

The question is, do we need a functionality that would prevent Yii for firing additional validators (showing validation errors) if there is already a validation error for any attribute?

I would for sure need. If, in this particular example, authentication for current password fails, then I don’t care, if user did or didn’t enter new password once or twice. And I don’t want to display additional errors for other attributes. They’re only messing out on screen and confusing less experienced users. Without valid authentication password will not be changed. End.