Esquire Theme by Matthew Buchanan
Social icons by Tim van Damme

16

Feb

Create your own Validation Rule

Here are an example to add custom validation rule for model class. This example adding a validation rule for password strength of User model.

Creating the class file

First create a custom validator class and extend CValidator

<?php
class VPasswordStrength extends CValidator {

    ...

}
?>

Now define four constant and one public attribute for class that you want to use your validation rule.

<?php
    ...

    const WEAK = 1;
    const FAIR = 2;
    const STRONG = 3;
    const HARD_TO_REMEMBER = 4;

    public $strength; // Holds strength from outside

    ...
?>

Implementing Server Validation

Now we have to override the parent abstract method validateAttribute

<?php
    ...

    public function validateAttribute($object, $attribute) {
	$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
	$message = "Sorry, Your password is not strong enough!";
	
	if($this->strength==self::WEAK) {
                //
                // If I want to match a line with at least 1 lowercase character then I can use:
                //
		$pattern = "/^.*(?=.*[a-z]).*$/";
		$message = "Sorry, Your password is too weak!";
	} else if($this->strength==self::FAIR) {
		$pattern = "/^(?=.*[a-zA-Z0-9]).{5,}$/";
		$message = "Sorry, Your password is not fair at all!";
	} else if($this->strength==self::HARD_TO_REMEBER) {
                //
                // And if you want to throw in some extra complexity and require at least one digit or one symbol you could make a match like:
                //
		$pattern = "/^.*(?=.{6,})(?=.*[a-z])(?=.*[A-Z])(?=.*[\d\W]).*$/";
		$message = "Sorry, Your password is not Hard to rember!";
	} else {
		// Default to self::STRONG
	}
	
	if(isset($params['message'])) {
		$message = $params['message'];
	}
	
	if(!preg_match($pattern, $object->$attribute)) {
		$this->addError($object, $attribute, $message);
	}
    }

    ...
?>

Implementing Client Validation

<?php
    ...

    public function clientValidationAttribute($object, $attribute) {
	$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
	$message = "Sorry, Your password is not strong enough!";
	
	if($this->strength==self::WEAK) {
		$pattern = "/^(?=.*[a-zA-Z0-9]).{5,}$/";
		$message = "Sorry, Your password is too weak!";
	} else if($this->strength==self::FAIR) {
		$pattern = "/^(?=.*[a-zA-Z0-9]).{8,}$/";
		$message = "Sorry, Your password is not fair at all!";
	} else if($this->strength==self::HARD_TO_REMEBER) {
		$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
		$message = "Sorry, Your password is not Hard to rember!";
	} else {
		// Default to self::STRONG
	}
	
	if(isset($params['message'])) {
		$message = $params['message'];
	}
	
	if(!preg_match($pattern, $object->$attribute)) {
		$this->addError($object, $attribute, $message);
	}
	$message = CJSON::encode($message);
	// Returns JavaScript decision maker condition
	return "if(!value.match($pattern))messages.push($message);";
    }

    ...
?>

So, we have created our validator for password

Now, lets go to implement it to model rules for an attribute

Implementing VPasswordStrength Custom validator

Implement our own VPasswordStrength validator to an User model class to restrict password strength.

Here we are also creating an User model class for example

Implementing rules for password strength

Note: here we are only showing rules() method of model class later you may copy full code of model at last.

<?php
    ...

    public function rules() {
        return array(
            array('password', 'PasswordStrength', 'strength', VPasswordStrength::STRONG),
        );
    }

    ...
?>

Copy Full Code of both class here


User Model class

<?php

/**
 * This is the model class for table "{{user}}".
 *
 * The followings are the available columns in table '{{user}}':
 * @property integer $id
 * @property string $email
 * @property string $username
 * @property string $password
 * @property string $salt
 * @property string $last_login_time
 * @property string $create_time
 * @property integer $create_user_id
 * @property string $update_time
 * @property integer $update_user_id
 * @property string $last_login_remote_address
 * @property string $profile_id
 * @property string $role
 * @property string $active
 * @property string $deleted
 */
class User extends CActiveRecord {

    /**
     * Password comparer
     */
    public $password_repeat;
    /**
     * Captcha value
     */
    public $verifyCode;
    
    /**
     * Returns the static model of the specified AR class.
     * @return User2 the static model class
     */
    public static function model($className = __CLASS__) {
        return parent::model($className);
    }

    /**
     * @return string the associated database table name
     */
    public function tableName() {
        return '{{user}}';
    }

    /**
     * @return array validation rules for model attributes.
     */
    public function rules() {
        // NOTE: you should only define rules for those attributes that
        // will receive user inputs.
        return array(
            array('profile_id, username, password, password_repeat, email, role', 'required'),
            array('email, username', 'unique'),
            array('create_user_id, update_user_id', 'numerical', 'integerOnly' =--> true),
            array('username', 'length', 'min' => 8, 'max' => 12),
            array('email', 'length', 'max' => 64),
            array('email', 'email', 'checkMX' => true),
            array('profile_id', 'numerical', 'integerOnly' => true),
            array('role', 'length', 'max' => 7),
            array('active, deleted', 'boolean'),
            array('last_login_time, create_time, update_time', 'safe'),
            array('password_repeat', 'compare', 'compareAttribute' => 'password'),
            array('password', 'passwordStrength', 'strength' => VPasswordStrength::STRONG),
            array('verifyCode', 'captcha', 'allowEmpty' => !CCaptcha::checkRequirements(), 'on' => 'register'),
            // The following rule is used by search().
            // Please remove those attributes that should not be searched.
            array('id, email, username, password, salt, last_login_time, create_time, create_user_id, update_time, update_user_id, profile_id, last_login_remote_address, role, active, deleted', 'safe', 'on' => 'search'),
        );
    }

    /**
     * @return array relational rules.
     */
    public function relations() {
        // NOTE: you may need to adjust the relation name and the related
        // class name for the relations automatically generated below.
        return array(
            'profile' => array(self::BELONGS_TO, 'UserProfiles', 'profile_id'),
        );
    }

    /**
     * @return array customized attribute labels (name=>label)
     */
    public function attributeLabels() {
        return array(
            'id' => 'ID',
            'email' => 'Email',
            'username' => 'Username',
            'password' => 'Password',
            'salt' => 'Salt',
            'last_login_time' => 'Last Login Time',
            'create_time' => 'Create Time',
            'create_user_id' => 'User added by',
            'update_time' => 'Update Date/Time',
            'update_user_id' => 'Record updated by',
            'profile_id' => 'Profile Name',
            'last_login_remote_address' => 'Client Remote(IP) Address',
            'role' => 'User Role',
            'active' => 'Active',
            'deleted' => 'Deleted',
        );
    }

    /**
     * Retrieves a list of models based on the current search/filter conditions.
     * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
     */
    public function search() {
        // Warning: Please modify the following code to remove attributes that
        // should not be searched.

        $criteria = new CDbCriteria;

        $criteria->compare('id', $this->id);
        $criteria->compare('email', $this->email, true);
        $criteria->compare('username', $this->username, true);
        $criteria->compare('password', $this->password, true);
        $criteria->compare('salt', $this->salt, true);
        $criteria->compare('last_login_time', $this->last_login_time, true);
        $criteria->compare('create_time', $this->create_time, true);
        $criteria->compare('create_user_id', $this->create_user_id);
        $criteria->compare('update_time', $this->update_time, true);
        $criteria->compare('update_user_id', $this->update_user_id);
        $criteria->compare('profile_id', $this->profile_id, true);
        $criteria->compare('last_login_remote_address', $this->last_login_remote_address, true);
        $criteria->compare('role', $this->role, true);
        $criteria->compare('active', $this->active, true);
        $criteria->compare('deleted', $this->deleted, true);

        $T = array('criteria' => $criteria);

        if ($this->pageSize) {
            $T['pagination'] = array('pageSize' => $this->pageSize);
        }
        return new CActiveDataProvider($this, $T);
    }

    /**
     * Checks if the given password is correct.
     * @param string the password to be validated
     * @return boolean whether the password is valid
     */
    public function validatePassword($password) {
        return $this->hashPassword($password, $this->salt) === $this->password;
    }

    /**
     * Generates the password hash.
     * @param string password
     * @param string salt
     * @return string hash
     */
    public function hashPassword($password, $salt) {
        return md5($salt . $password);
    }

    /**
     * Generates a salt that can be used to generate a password hash.
     * @return string the salt
     */
    public function generateSalt() {
        return sha1(uniqid('', true));
    }

    public function getUserRoles() {
        $L = array();
        foreach (Yii::app()->authManager->roles as $key => $value) {
            $L[$key] = $key;
        }
        return $L;
    }

}
?>

Class VPasswordStrength for Custom Password Strength Validator

VPasswordStrength validator validates password strength on four different criteria

  1. WEAK
  2. FAIR
  3. STRONG
  4. HARD_TO_REMEMBER
Create a file VPasswordStrength.php and put it to protected/components folder
<?php
class VPasswordStrength extends CValidator {
	const WEAK = 1;
	const FAIR = 2;
	const STRONG = 3;
	const HARD_TO_REMEBER = 4;
	
	public $strength;
	
	public function validateAttribute($object, $attribute) {
		$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
		$message = "Sorry, Your password is not strong enough!";
		
		if($this->strength==self::WEAK) {
			$pattern = "/^(?=.*[a-zA-Z0-9]).{5,}$/";
			$message = "Sorry, Your password is too weak!";
		} else if($this->strength==self::FAIR) {
			$pattern = "/^(?=.*[a-zA-Z0-9]).{8,}$/";
			$message = "Sorry, Your password is not fair at all!";
		} else if($this->strength==self::HARD_TO_REMEBER) {
			$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
			$message = "Sorry, Your password is not Hard to rember!";
		} else {
			// Default to self::STRONG
		}
		
		if(isset($params['message'])) {
			$message = $params['message'];
		}
		
		if(!preg_match($pattern, $object->$attribute)) {
			$this->addError($object, $attribute, $message);
		}
	}
	
	public function clientValidationAttribute($object, $attribute) {
		$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
		$message = "Sorry, Your password is not strong enough!";
		
		if($this->strength==self::WEAK) {
			$pattern = "/^(?=.*[a-zA-Z0-9]).{5,}$/";
			$message = "Sorry, Your password is too weak!";
		} else if($this->strength==self::FAIR) {
			$pattern = "/^(?=.*[a-zA-Z0-9]).{8,}$/";
			$message = "Sorry, Your password is not fair at all!";
		} else if($this->strength==self::HARD_TO_REMEBER) {
			$pattern = "/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/";
			$message = "Sorry, Your password is not Hard to rember!";
		} else {
			// Default to self::STRONG
		}
		
		if(isset($params['message'])) {
			$message = $params['message'];
		}
		
		if(!preg_match($pattern, $object->$attribute)) {
			$this->addError($object, $attribute, $message);
		}
		$message = CJSON::encode($message);
		// Returns JavaScript decision maker condition
		return "if(!value.match($pattern))messages.push($message);";
	}
}
?>