Small diferences in Models and Forms

Hello,

I’m developing the user registration page, my user TABLE contains 3 fields: username, password and salt to hash with md5 the password and it (the salt will be generated randomly).

Obviously the rules() in the model class indicates that salt is not null, but in the form the salt field doesn´t appear because it’s generated randomly. So if I fill the username and password in the register user form it return me an error indicating that salt shouldn’t be null (when in the controller I’m filling it).

Is there any mechanism to indicate yii diferent validation rules depending on the context, it means one rules if it comes from a form where some fields are internal and are not filled and other rules if the model is saved to the database. I would like to use the same model class and don’t create a CFormModel class.

Regards:

Kike

Scenario would be what you’re looking for

http://www.yiiframework.com/doc/guide/form.model#triggering-validation

E.g I think you could use ‘allowEmpty’ in the ‘register’ scenario.

/Tommy

Sorry my English… I’m from brazil and don’t practice my writing a lot.

Hey dude. I just did the same thing this week. So the content still fresh on my mind.

Follow these instructions. If you have any questions mail me at thiagovidal@gmail.com or add me on msn within the same email address.

Remember that you should have 3 things basically:

M - Model (../models/RegistrationForm.php)

V - View (../views/site/registration.php)

C - Controller (../controllers/SiteController.php) you can use another controller instead like (../controllers/RegistrationController.php). Remember that the URL should point to correct controller.

Ex:

(first scenario) http://localhost/yourapp/index.php?r=site/registration (this will look at SiteController)

(other scenario) http://localhost/yourapp/index.php?r=registration (this will look at RegistrationController)

Something like that.

I build my model using yiic tool, just to get the skeleton. After some changes my model became:

Remeber that your registration form should extends CActiveRecord insted CFormModel. On my case my extends model Users that already is a CActiveRecord.

Why? Because you are dealing with your database I think its best than CFormModel that doesn’t provide some functions.




class RegistrationForm extends Users

{

	// don't forget to declare variables outside from your database table otherwise you will get anoing errors.

	public $bDay;

	public $bMonth;

	public $bYear;

	public $passwordConfirm;

	public $verifyCode;

	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		return array(

			//required fields

			array('firstName, lastName, birthday, bDay, bMonth, bYear, username, password, passwordConfirm, secretQuestion, secretAnswer, email, verifyCode', 'required'),

			//lenght

			array('username', 'length', 'max'=>25, 'min' => 3,'message' => Yii::t('default', 'Incorrect username (length between 3 and 20 characters).')),

			array('password', 'length', 'max'=>128, 'min' => 6,'message' => Yii::t('default', 'Incorrect password (minimal length 4 symbols).')),

			array('secretQuestion','length','max'=>255),

			array('secretAnswer','length','max'=>128, 'min' => 3),

			//unique

			array('username', 'unique', 'message' => Yii::t('default', "This user's name already exists.")),

			array('email', 'unique', 'message' => Yii::t('default', "This users's email adress already exists.")),

			//validation '/^[A-Za-z]+$/u'

			array('firstName', 'match', 'pattern' => "/^[a-zá-úA-ZÁ-Úºª ]+$/", 'message' => Yii::t('default', 'Names can not contain numbers ou special characters')),

			array('lastName', 'match', 'pattern' =>  "/^[a-zá-úA-ZÁ-Úºª ]+$/", 'message' => Yii::t('default', 'Names can not contain numbers ou special characters')),

			array('username', 'match', 'pattern' => '/^[A-Za-z0-9]+$/u', 'message' => Yii::t('default', 'Username only can contain letters and numbers.')),

			array('password', 'compare', 'compareAttribute'=>'passwordConfirm', 'message' => Yii::t('default', 'Retype Password is incorrect.')),

			array('email', 'email'),

			array('verifyCode', 'captcha', 'allowEmpty'=>!extension_loaded('gd')),

		);

	}


	// this amazing feature let you make changes to data after validation	

	public function beforeSave()

	{

		if(parent::beforeSave())

		{

			$this->setAttribute('firstName', strtoupper($this->firstName));

			$this->setAttribute('lastName', strtoupper($this->lastName));

			$this->setAttribute('birthday', date('Y-m-d', strtotime($this->bYear . '-' . $this->bMonth . '-' . $this->bDay)));

			$this->setAttribute('password',Yii::app()->user->_encrypt($this->password));

			$this->setAttribute('activationKey',Yii::app()->user->_encrypt(microtime() . $this->password));

			$this->setAttribute('secretAnswer',helpers::_encrypt(strtolower($this->secretAnswer)));

			$this->setAttribute('dateCreation',date('Y-m-d H:i:s'));

			$this->setAttribute('dateLastVisit',date('Y-m-d H:i:s'));

			$this->setAttribute(status,1);

		}

        return true;

	}

	

	public function attributeLabels()

	{

		return array(

			// here you can specify attribute labels and if you wish use Yii::t() to translations.

            // only return labels

            'username'=>'Username',

            // or return labels with translations

            'username'=>Yii::t('default','Username'),

		);

	}

}



View




<?php

$this->pageTitle=Yii::app()->name . ' - ' . Yii::t('default','Registration');

$this->breadcrumbs=array(

	Yii::t('default','Registration'),

);

?>

<h1><?php echo Yii::t('default','Create new account'); ?></h1>


<?php if(Yii::app()->user->hasFlash('registration')): ?>


<div class="flash-success">

	<?php echo Yii::app()->user->getFlash('registration'); ?>

</div>


<?php else: ?>


<p><?php echo Yii::t('default', 'Please fill out the following form to create a new account.'); ?></p>


<div class="form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'registration-form',

	'enableAjaxValidation'=>true,

)); ?>


	<p class="note"><?php echo Yii::t('default', 'Fields with <span class="required">*</span> are required.'); ?></p>


	<?php echo $form->errorSummary($model); ?>


	<div class="row">

		<?php echo $form->labelEx($model,'firstName'); ?>

		<?php echo $form->textField($model,'firstName'); ?>

        <?php echo $form->labelEx($model,'lastName'); ?>

		<?php echo $form->textField($model,'lastName'); ?>

	</div>

    

    <div class="row">

		<?php echo $form->labelEx($model,'birthday'); ?>

		<?php echo $form->dropDownList($model,'bDay',helpers::getDays(),array('prompt'=>Yii::t('default','Day'))); ?>

        <?php echo $form->dropDownList($model,'bMonth',helpers::getMonths(),array('prompt'=>Yii::t('default','Month'))); ?>

        <?php echo $form->dropDownList($model,'bYear',helpers::getYears(),array('prompt'=>Yii::t('default','Year'))); ?>

    </div>


	<div class="row">

		<?php echo $form->labelEx($model,'username'); ?>

		<?php echo $form->textField($model,'username'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'password'); ?>

		<?php echo $form->passwordField($model,'password'); ?>

	</div>


	<div class="row">

		<?php echo $form->labelEx($model,'passwordConfirm'); ?>

		<?php echo $form->passwordField($model,'passwordConfirm'); ?>

	</div>

    

    <div class="row">

		<?php echo $form->labelEx($model,'secretQuestion'); ?>

		<?php echo $form->dropDownList($model,'secretQuestion',helpers::getSecretQuestions(),array('prompt'=>Yii::t('default','Select one please'))); ?>

	</div>


    <div class="row">

		<?php echo $form->labelEx($model,'secretAnswer'); ?>

		<?php echo $form->textField($model,'secretAnswer', array('size'=>45, 'max'=>255) ); ?>

	</div>

    

	<div class="row">

		<?php echo $form->labelEx($model,'email'); ?>

		<?php echo $form->textField($model,'email', array('size'=>45, 'max'=>255) ); ?>

	</div>


	<?php if(extension_loaded('gd')): ?>

	<div class="row">

		<?php echo $form->labelEx($model,'verifyCode'); ?>

		<div class="hint"><?php echo Yii::t('default', 'Please enter the letters as they are shown in the image.'); ?></div>

        <div>

		<?php $this->widget('CCaptcha',array('showRefreshButton'=>false,'clickableImage'=>true,'imageOptions'=>array('title'=>'Click','style'=>'float: left;'))); ?>

		<?php echo $form->textField($model,'verifyCode',array('size'=>10, 'class'=>'verifyCode')); ?>

		</div>

	</div>

	<?php endif; ?>


	<div class="row buttons">

		<?php $this->widget('application.extensions.ui.uiButton', array('formID' => 'registration-form', 'title'=>Yii::t('default','Send'), 'linkTitle'=>Yii::t('default','Send message'), 'buttonType'=>'submit', 'color'=>'green')); ?>

		<?php $this->widget('application.extensions.ui.uiButton', array('formID' => 'registration-form', 'title'=>Yii::t('default','Clear'), 'linkTitle'=>Yii::t('default','Clear form'),'buttonType'=>'reset', 'color'=>'gray', 'confirm'=>Yii::t('default','Clear form anyway?'))); ?>

	</div>


<?php $this->endWidget(); ?>


</div><!-- form -->


<?php endif; ?>



Controller




public function actionRegistration()

	{

		if(Yii::app()->user->isGuest)

		{

			$model=new RegistrationForm;

			if(isset($_POST['ajax']) && $_POST['ajax']==='registration-form')

			{

				echo CActiveForm::validate($model);

				Yii::app()->end();

			}			

			if(isset($_POST['RegistrationForm']))

			{

				$model->attributes=$_POST['RegistrationForm'];

				$model->setAttribute('birthday', date('Y-m-d', strtotime($model->bYear . '-' . $model->bMonth . '-' . $model->bDay)));

				if($model->validate() && $model->save())

				{

					Yii::app()->user->setFlash('registration', 'Thank you for creating your account. You may now go to login form to enter site.');

				}

			}

			$this->render('registration',array('model'=>$model));

		} else {

			Yii::app()->user->setFlash('registration', "You already have your account. You can't create a new one. Please go to another page.");

			//$this->render('registration',array('model'=>$model));

			$this->redirect(Yii::app()->user->returnUrl);

		}

	}

    



Helpers (../components/Helpers.php)

I made this class extending Controller to use some own classes inside my code.

I really don’t know if it is the best place to put this but on my projet it works fine.




<?php

class Helpers extends Controller

{

	final function _encrypt($value)

	{

		$valueHash = md5(md5($value));

		return $valueHash;

	}

	//return valid days range

	public static function getDays()

	{

		for ($i=1;$i<=31;$i++)

		{

			$days[$i]=$i;

		}

		return $days;

	}

	//return month names

	public static function getMonths()

	{

		$monthNames = array('','January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');

		for ($i=1;$i<=12;$i++)

		{

			$months["{$i}"]=Yii::t('default',$monthNames[$i]);	

		}

		return $months;

	}

	//return valid years range

	public static function getYears() {

		return array_combine($i=range((date('Y')-16),(date('Y') - 90)),$i);

	}

	//return secret questions

	public function getSecretQuestions()

	{

		$question[] = Yii::t('default', 'What is your primary frequent flyer number?');

		$question[] = Yii::t('default', 'What is your library card number?');

		$question[] = Yii::t('default', 'What is your favorite car?');

		$question[] = Yii::t('default', 'What was your first phone number?');

		$question[] = Yii::t('default', 'What is your favorite place on world?');

		$question[] = Yii::t('default', "What was your first teacher's name?");

		$question[] = Yii::t('default', "What is your father's middle name?");

		$question[] = Yii::t('default', "What is your gradmother's middle name?");

		for ($i=0;$i<count($question);$i++)

		{

			$questions[$question[$i]]=$question[$i];

		}

		return $questions;

	}

}



You can set the fields that you don’t want not required on rules and use the function beforeSave() to create your internal values.

Thanks to all for the responses,

Tri, I was evaluating that solution about using scenarios, but I thiink is not possible to configure the rules for what I want. In the example I put with (user, password and salt), user and password are required for scenario ‘register’ and when I save it to the database (default scenario), but salt is only required when it’s stored to the database (default scenario). So if I put the rules() => array(‘user,password’, ‘required’, ‘on’=>‘register’), array(‘salt’, ‘required’)

Thiagovidal, thanks for your code, it has been very useful, but I continue with the doubt about how to check that salt is not null when is saved into the database with the rules mechanism. Anyway I put a calidation in the beforeSave method as you suggested.

Regards:

Kike

You should usually only define validation rules for fields that are filled by the user. So you probably shouldn’t have any rules defined for the salt field, as it is generated internally.

Thanks thiagovidal