Yii Framework Forum: Conditional Validation - Yii Framework Forum

Jump to content

  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

Conditional Validation How to validate attributes without scenarios Rate Topic: -----

#1 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 02 August 2011 - 12:07 PM

Currently working on a personal project I was confronting a situation where I had to validate an attribute only if certain conditions where accomplished. The conditions where quite challenging and scenarios weren't suitable, or maybe I didn't know how they could be written in order to fulfill my validation requirements, as I had too many conditions to fulfill depending even on the value of certain attributes.

Background:

The attribute 'A' was required only if the form was of a certain type
The attribute 'A' supposed to be validated only if 'B' existed in the form and if 'B' fulfilled certain validation requirement. For example, if B existed and also if it had a value='EMAIL', then 'A' was to be validated with 'Email' validation.
Conditional statements in Controllers should be avoided to keep their code clean and neat, so validators should handle them

My Solution:
a - ) I included attribute 'A' to be required at all times
b - ) Created a EConditionalValidator that will only execute a rule if certain validations are passed previously
array('account',
	'application.components.validators.EConditionalValidator',
	'conditionalRules' => array('B', 'compare', 'compareValue' => self::TYPE_EMAIL),
	'rule' => array('A', 'email')
),

c - ) Then, depending on the type of FORM then I validate some attributes or anothers and then save the model without validating further its attributes:
if($_GET['FORM_TYPE'] == FORM_TYPE::TYPE_EMAIL)
	$validationParams = array('A','D', 'C');
else
	validationParams = array('account','A', 'D', 'F');

if ($model->validate($validationParams) && $model->save(false))
	// success

else // failure


Final notes
I would like to know if anybody of you has confront a challenge like this with validation and what it was the solution.

EConditionalValidator is also capable of validating more than one condition before executing one. If its of any use for anyone of you, I would be more than happy to share it.
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#2 User is offline   ChessSpider 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 37
  • Joined: 20-July 11
  • Location:Netherlands

Posted 03 August 2011 - 07:06 AM

Not particularly happy with my solution, but..

Whenever a user is created his password is validated with the Length and Compare validation rules.

On update the user can leave his password empty if he does not want to change it. However this caused problems with the Compare validation rule, causing the actual password of the user being validated against the empty password_repeat field, obviously causing an error.

In the end I decided to check if a password has changed (overriding the setAttribute() method). If the password has changed; I add the required validators:

	public function onBeforeValidate(CEvent $event)
	{
    	parent::onBeforeValidate($event);

    	if ($this->password_changed)
    	{  
        	$validator = CValidator::createValidator('length', $this, 'password', array('min' => 8));
        	$this->getValidatorList()->add($validator);
        	$validator = CValidator::createValidator('compare', $this, 'password');
        	$this->getValidatorList()->add($validator);
    	}
	}


I like your solution better.

And someone please check the spam filter.. Its annoying. Had to rewrite this text a few times for it to show up.

This post has been edited by ChessSpider: 03 August 2011 - 07:10 AM

0

#3 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 03 August 2011 - 09:13 AM

View PostChessSpider, on 03 August 2011 - 07:06 AM, said:

Not particularly happy with my solution, but..

Whenever a user is created his password is validated with the Length and Compare validation rules.

On update the user can leave his password empty if he does not want to change it. However this caused problems with the Compare validation rule, causing the actual password of the user being validated against the empty password_repeat field, obviously causing an error.

In the end I decided to check if a password has changed (overriding the setAttribute() method). If the password has changed; I add the required validators:

	public function onBeforeValidate(CEvent $event)
	{
    	parent::onBeforeValidate($event);

    	if ($this->password_changed)
    	{  
        	$validator = CValidator::createValidator('length', $this, 'password', array('min' => 8));
        	$this->getValidatorList()->add($validator);
        	$validator = CValidator::createValidator('compare', $this, 'password');
        	$this->getValidatorList()->add($validator);
    	}
	}


I like your solution better.

And someone please check the spam filter.. Its annoying. Had to rewrite this text a few times for it to show up.


Thanks for sharing your solution for a common problem.

Nevertheless, for your challenge, I decided to work creating a new set of attributes: newPassword, and passwordConfirm. Then I set their validation rules as shown below, is easier...

        // attributes
        /**
	 * @var string attribute used for new passwords on user's edition
	 */
	public $newPassword;

	/**
	 * @var string attribute used to confirmation fields
	 */
	public $passwordConfirm;

        // rules
        array('passwordConfirm', 'compare', 'compareAttribute' => 'newPassword', 'message' => 'Entered passwords don\'t match'),
        array('password, newPassword', 'length', 'max' => 50, 'min' => 6, 'tooShort' => 'Password must have at least 6 chars'),

¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#4 User is offline   Say_Ten 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 109
  • Joined: 17-September 10

Posted 03 August 2011 - 09:32 AM

I've tended to roll inline validators for situation similar to this but it's rarely been entirely if rule A then run rule B. I think your solution is a good one.
0

#5 User is offline   ChessSpider 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 37
  • Joined: 20-July 11
  • Location:Netherlands

Posted 04 August 2011 - 12:50 AM

View PostAntonio Ramirez, on 03 August 2011 - 09:13 AM, said:

Thanks for sharing your solution for a common problem.

Nevertheless, for your challenge, I decided to work creating a new set of attributes: newPassword, and passwordConfirm. Then I set their validation rules as shown below, is easier...

        // attributes
        /**
     * @var string attribute used for new passwords on user's edition
     */
	public $newPassword;

	/**
     * @var string attribute used to confirmation fields
     */
	public $passwordConfirm;

        // rules
        array('passwordConfirm', 'compare', 'compareAttribute' => 'newPassword', 'message' => 'Entered passwords don\'t match'),
        array('password, newPassword', 'length', 'max' => 50, 'min' => 6, 'tooShort' => 'Password must have at least 6 chars'),




can you elaborate a bit further? When does Password gets updated with Newpassword? Also note that Password of course is a hash, not cleartext.
0

#6 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 04 August 2011 - 11:13 AM

View PostChessSpider, on 04 August 2011 - 12:50 AM, said:

can you elaborate a bit further? When does Password gets updated with Newpassword? Also note that Password of course is a hash, not cleartext.


Is like this:

User already has his password on creation right? Now, we wish to update / hash the password 'only' when somebody updates the blank text field right? Then what we do is to create two new attributes (ie newPassword and passwordConfirm) , then we put the already mentioned rules and check onBeforeSave event:

        /**
	 * Hash password if needed
	 */
	public function beforeSave()
	{
		if (parent::beforeSave()) {
			if($this->isNewRecord)
			{
				$this->password = self::hashPassword($this->password);
				$this->validation_key = md5(mt_rand().mt_rand().mt_rand());
			}
			if(!empty($this->newPassword))
			{
				$this->password = self::hashPassword($this->newPassword);
				$this->newPassword = '';
				$this->passwordConfirm = '';
			}
			return true;
		}
		return false;
	}


done
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#7 User is offline   mbi 

  • Master Member
  • PipPipPipPip
  • Yii
  • Group: Members
  • Posts: 602
  • Joined: 08-May 09

Posted 04 August 2011 - 11:21 AM

I created a validator in beforeValidate
http://blog.mbischof...amisch-erzeugen
1

#8 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 04 August 2011 - 06:52 PM

View Postmbi, on 04 August 2011 - 11:21 AM, said:

I created a validator in beforeValidate
http://blog.mbischof...amisch-erzeugen


That is also a fantastic alternative to the same issue. The reason why I didn't go that way is that I may have to use the same technique in some, not all, models.

Thanks for sharing mbi...
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#9 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 02 November 2011 - 12:15 PM

Antonio, is you validator in the extension section od Yii portal?

Many thanks.

Mauro
0

#10 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 02 November 2011 - 06:41 PM

View PostToolMayNARD, on 02 November 2011 - 12:15 PM, said:

Antonio, is you validator in the extension section od Yii portal?

Many thanks.

Mauro

I did not... wanted to know if people would like to have it... if you think is useful, then I will post it...

Cheers
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#11 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 03 November 2011 - 06:25 AM

View PostAntonio Ramirez, on 02 November 2011 - 06:41 PM, said:

I did not... wanted to know if people would like to have it... if you think is useful, then I will post it...

Cheers


Yes I'mk interested on it.

But first I explain the problem I'm trying to deal out, but... no way :-[

I have some dependencies in my form and the default validations aren't able to manage them.

This is my scenario:
- <province> is required only if <country> has value = 86;
- <tax_code> is required and should match a regexp only if <country> has value = 86 and only if <user_type> has value = 1;
- <VAT> is required and should match a regexp only if <country> has value = 86 and only if <user_type> has value = 2;

I'm trying to manage this validation scenario with custom validating function but I do not know how to get the current value of dependency parameters after form submit.

My knowledge of Yii framework is rather low.

Bye.

Mauro
0

#12 User is offline   sidewinder 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 207
  • Joined: 08-July 09
  • Location:Poland

Posted 03 November 2011 - 07:15 AM

Ahhh finally someone with similar problem.
I had similar problems while parsing a form with many checkboxes which revealed additional fields. My solution is a little bit different then proposed here.
I created behaviour which allows to run many scenarios at time. Let's say we have some generic validators for fields which are common for all cases and these are without any scenarios assigned. Then we have some different rules which should be used only when "Option A" checkbox is checked - let's assign it a scenario "A". Now, it's not uncommon to have 2 or more checkboxes in a form which reveals more input fields. For each set let's create validation rules and assign them a scenarios names "B" and "C". Now in controller we can check for conditions, and if they are met (in this case if particular checkbox is checked) we can append a scenario to active scenario list. Aforementioned behaviour will make sure all relevant rules are applied.
Example:
ContactForm.php (model)
public function behaviors() {
    	return array(
        	'MetaScenario' => array(
            	'class' => 'application.components.MetaScenario',
            	'mark' => 'META'
        	),
    	);
	}

	/**
 	* Declares the validation rules.
 	*
 	*/
	public function rules() {
    	$rules = array(
        	array('name, surname, mail', 'required'),
        	array('mail', 'email'),
        	array('verifyCode', 'captcha'),
        	// accommodation
        	array('dateFrom, dateTo, people', 'required', 'on' => 'accommodation'),
        	array('people', 'numerical', 'integerOnly' => true, 'on' => 'accommodation'),
        	// equipment
        	array('rentFrom, rentTo', 'required', 'on' => 'equipment'),
    	);
    	$this->createMetaRules(&$rules, $this->getScenario());
    	return $rules;
	}


controller

public function actionContact()
	{
		$contact=new ContactForm;
		if(isset($_POST['ContactForm']))
		{
			$scenario = 'META';
			$scenario .= $_POST['ContactForm']['accommodation']=='Hotel'?'_accommodation':'';
			$scenario .= ($_POST['ContactForm']['equipment']=='Rented')?'_equipment':'';
			$contact->setScenario($scenario);
			$contact->attributes=$_POST['ContactForm'];
			if($contact->validate())
	...
	}


and metaScenario itself:

<?php
class MetaScenario extends CBehavior {
	/**
     *
     * @var string Trigger for meta scenario
     */
	public $mark;
	/**
     * Generate meta scenario based on scenario name
     * @param array $rules Reference to rules array should be passed.
     * @param string $scenario Scenario string. In model use $this->getScenario()
     * @return boolean
     */
	public function createMetaRules($rules,$scenario){
		if (strpos($scenario, $this->mark)===FALSE){
			return false;
		}
		$metaList = $this->createMetaList($scenario);
		//check for rules from scenario list and add them to main rule array if found
		foreach ($rules as &$rule)
		{
			if (array_key_exists('on', $rule))
			{
				if(is_array($rule['on'])){
					$on=$rule['on'];
				}else{
					$on=preg_split('/[\s,]+/',$rule['on'],-1,PREG_SPLIT_NO_EMPTY);
				}
				$tmp = array_intersect($metaList, $on);
				if(!empty($tmp)){
					array_push($on, $scenario);
					$rule['on'] = $on;
				}
			};
		}
		return true;
	}
	
	/**
     * Create scenario list and gets rid of mark
     * @param string $scenario Scenario string.
     * @return array scenarios list.
     */
	private function createMetaList($scenario)
	{
		$metaList = explode('_',$scenario);
		$metaList = array_slice($metaList, 1);
		return $metaList;
	}
}
?>


of course this is a limited example and only relevant bits of model and controller are shown. I wrote that some time ago and now I can see lot's of room for improvement :). In case anyone is interested I'll be happy to improve it a bit.
---------------------------------------------------------------------
"Never memorize what you can look up in books."
Albert Einstein
0

#13 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 03 November 2011 - 07:33 AM

View PostToolMayNARD, on 03 November 2011 - 06:25 AM, said:

Yes I'mk interested on it.

But first I explain the problem I'm trying to deal out, but... no way :-[

I have some dependencies in my form and the default validations aren't able to manage them.

This is my scenario:
- <province> is required only if <country> has value = 86;
- <tax_code> is required and should match a regexp only if <country> has value = 86 and only if <user_type> has value = 1;
- <VAT> is required and should match a regexp only if <country> has value = 86 and only if <user_type> has value = 2;

I'm trying to manage this validation scenario with custom validating function but I do not know how to get the current value of dependency parameters after form submit.

My knowledge of Yii framework is rather low.

Bye.

Mauro


Even though I think EConditionalValidator would help you out as it seems to me that with CCompareValidator plus the CRequiredValidator and CRegularExpressionValidator will be solved easily, I believe that for your issue is better to have a solution like MBI exposed: create a validator in beforeValidate
http://blog.mbischof...amisch-erzeugen and you will implement it easier...

You create a beforeValidate procedure on your model and check for those values to do the regex :)

If you have problems doing that, let me know, I will polish the code of the validator, publish it and use your problem to use it in the wiki :)

Cheers
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#14 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 03 November 2011 - 08:56 AM

View PostAntonio Ramirez, on 03 November 2011 - 07:33 AM, said:

If you have problems doing that, let me know, I will polish the code of the validator, publish it and use your problem to use it in the wiki :)


:( Uhmm, yes I have some problem. Moreover the MBI solutions now seems to be offline (database connection error).

Mauro
0

#15 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 03 November 2011 - 09:20 AM

View PostToolMayNARD, on 03 November 2011 - 08:56 AM, said:

:( Uhmm, yes I have some problem. Moreover the MBI solutions now seems to be offline (database connection error).

Mauro


I will publish it as an extension then...
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#16 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 03 November 2011 - 11:46 AM

I am about to publish the EConditionalValidator, please check if the following will fit your requirements.

// assuming you have the validator on your protected/components/validators/ folder
public function rules(){
   $cv = 'application.components.validators.EConditionalValidator';
   return array(
         array(
             'province', $cv,
             'conditionalRules' => array('country', 'compare', 'compareValue' => 86),
             'rule' => array('required')
         ),
         array(
             'tax_code', $cv,
             'conditionalRules' => array(
                 'group'=>array(
                     array('country', 'compare', 'compareValue' => 86),
                     array('user_type', 'compare', 'compareValue' => 1)
                  ),
              ),
              'rule'=>array('match', 'pattern'=>'/^([a-z0-9_])+$/'),
         ),
         array(
             'VAT', $cv,
             'conditionalRules' => array(
                 'group'=>array(
                     array('country', 'compare', 'compareValue' => 86),
                     array('user_type', 'compare', 'compareValue' => 2)
                  ),
              ),
              'rule'=>array('match', 'pattern'=>'/^([a-z0-9_])+$/'),
         ),
   );
}


PS: Change the pattern for the one you need.
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#17 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 03 November 2011 - 11:58 AM

Yes, it is right. Obvioulsy I will put the right patterns where needed.

Meanwhile I have found a "partial" solutions using a fast implentation certainly not well formed. But my limited knowledge of Yii framework stop me every line of code :unsure:

The idea is to use a validation function passing as parameters the default validation metod (required, email, and so on) and a structure the defines the dependencies (e.g. (country:86;user_type:2) that will be parsed into the validating function.

But your solution seems to be very clean and useful, not only for my projects I mean :)
0

#18 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 03 November 2011 - 12:16 PM

View PostToolMayNARD, on 03 November 2011 - 11:58 AM, said:

Yes, it is right. Obvioulsy I will put the right patterns where needed.

Meanwhile I have found a "partial" solutions using a fast implentation certainly not well formed. But my limited knowledge of Yii framework stop me every line of code :unsure:

The idea is to use a validation function passing as parameters the default validation metod (required, email, and so on) and a structure the defines the dependencies (e.g. (country:86;user_type:2) that will be parsed into the validating function.

But your solution seems to be very clean and useful, not only for my projects I mean :)


Yeah... you can use a custom validation function... as with beforeValidate but, like this you can have more well formed way of validating attributes...
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

#19 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 04 November 2011 - 09:24 AM

I was trying the extension. When there are no nested group rules is all ok, but when I declare the group array something goes wrong.

This is my rules array:

Array
(
    [0] => Array
        (
            [0] => province
            [1] => EConditionalValidator
            [conditionalRules] => Array
                (
                    [0] => country
                    [1] => compare
                    [compareValue] => 86
                )

            [rule] => Array
                (
                    [0] => required
                )

        )

    [1] => Array
        (
            [0] => taxcode
            [1] => EConditionalValidator
            [conditionalRules] => Array
                (
                    [group] => Array
                        (
                            [0] => Array
                                (
                                    [0] => country
                                    [1] => compare
                                    [compareValue] => 86
                                )

                            [1] => Array
                                (
                                    [0] => usertype
                                    [1] => compare
                                    [compareValue] => 1
                                )

                        )

                )

            [rule] => Array
                (
                    [0] => required
                )

        )

    [2] => Array
        (
            [0] => company
            [1] => EConditionalValidator
            [conditionalRules] => Array
                (
                    [group] => Array
                        (
                            [0] => Array
                                (
                                    [0] => country
                                    [1] => compare
                                    [compareValue] => 86
                                )

                            [1] => Array
                                (
                                    [0] => usertype
                                    [1] => compare
                                    [compareValue] => 2
                                )

                        )

                )

            [rule] => Array
                (
                    [0] => required
                )

        )

    [3] => Array
        (
            [0] => vat
            [1] => EConditionalValidator
            [conditionalRules] => Array
                (
                    [group] => Array
                        (
                            [0] => Array
                                (
                                    [0] => country
                                    [1] => compare
                                    [compareValue] => 86
                                )

                            [1] => Array
                                (
                                    [0] => usertype
                                    [1] => compare
                                    [compareValue] => 2
                                )

                        )

                )

            [rule] => Array
                (
                    [0] => required
                )

        )

)


For simplicity I declared them as required.
I get the following error during validation:

PHP Error

Undefined offset: 1

/data/www/meade/protected/components/validators/EConditionalValidator.php(111)

This is the line of code that return PHP error:
list($attributes, $conditionalValidator) = $rule;


I think that you should modify validateConditional function like this:

protected function validateConditional(&$object, $rule) {            
            if (isset($rule['group'])) {
             ...
            } else {
                list($attributes, $conditionalValidator) = $rule;
                ...
            }
	}


otherwise list function throws an error after recursion.

Mauro
0

#20 User is offline   Antonio Ramirez 

  • Elite Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 1,447
  • Joined: 04-October 10

Posted 04 November 2011 - 10:03 AM

I think you got the wrong version... that line was deleted... please, let me check

You were right :), nevertheless, could you paste also your rules here? Not the array, the actual rules


PS: I have updated the version and fixed... thanks Mauro...
¿How long would it take for you to understand that you own nothing in this world?

www.ramirezcobos.com
www.2amigos.us
www.github.com/tonydspaniard
www.github.com/2amigos


Posted Image
0

Share this topic:


  • (2 Pages)
  • +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

2 User(s) are reading this topic
0 members, 2 guests, 0 anonymous users