Early call of rules() in model lifecycle prevents custom validation logic

Today i thought about a smart solution for dependend validation. Since we define rules in a method instead of e.g. a public array i thought, we could do something like this:




public function rules()

{

    $isOther = $this->type=='other';

    return array(

        // field 'other_type' is only required if user selected 'type' of 'other'

        array('other_type','required'=>$isOther),

...

This does not work as expected. $this->type in the above example is null at the time the expression is evaluated, no matter what was selected in the form.

The reason is, that the validator objects are created (and thus rules() gets called) before the attributes are assigned. This is required in getSafeAttributeNames to find the attributes that are safe to assign. Later, when validate() is called rules() will not get called anymore because the validator objects where already created.

Even though i see, why this is done, this somehow limits the potential flexibility we have with rules() being a method. It would be cool if things like the above would be possible.

Any ideas?

I think that is a circular problem.

Rules has to be evaluated before attribute assignment in order to determinate wich attribute are safe, that is why you cannot refer to attribute itself.

I always use a validation function for such porpouse.

Yes, i know that a custom validator can circumvent this problem. Still in my vision it would be great if we could do the above. Even if that means, rules() has to get called twice, once to find the safe attributes and once to find the rules. Or maybe there’s even a better solution.

(OT: Frankly i’m not convinced anymore wether it was a good idea to couple validation rules with defining safe attributes in 1.1.x. You may remember my other topic where we discussed how this complicates the situation if you have complex rules with many attributes in many scenarios. Actually my solution to override getSafeAttributeNames() would also solve this problem here.)

just for reference, the search took a minute

Thanks! Sorry, that i forgot to add that link above.

Did anybody find any solution for this occurrence?

I have figured out a workaround for this. but there is a small issue with [size="2"]


CModel > private $_validators;

[/size]

Is there any special reason for having private ? if we can make it protected from the framework side my work around will work. I also had a look in to yii2 code base and found its still the same.

https://github.com/y.../base/Model.php

[size="2"]This is how i am suggesting to get rid of this problem.[/size]

[size="2"]Option 1[/size]

This is not a safe way to do though.


$model->setAttributes($values,false) 

$model->save();

Option 2 ($[size="2"]_validators[/size][size="2"] must be protected from the framework[/size][size="2"])[/size]

Overriding the beforeValidate method.




public function beforeValidate() {

        $this->_validators = $this->createValidators();

        return parent::beforeValidate();

}




$model->attributes = $data;

$model->save();

This can be a performance hit as i am already going to reassign the validators every time before saving the model.

isn’t there any other better way to do this ? except writing a custom function and assigning to rules.




array('username', 'validateUniqueUsername'),





public function validateUniqueUsername($attribute, $params)

{

	$validator = new CUniqueValidator();

	$validator->attributes = array($attribute);

	$validator->validate($this, array($attribute));

	if ($this->hasErrors($attribute)) {

		$this->clearErrors($attribute);

		$this->addError('username', Yii::t('auth', 'Username "'.$this->username.'" has already been taken.'));

	}

}