[Edit] fixed up and all processing well . In a span of 5 to 10 minutes you can now create and customize a registration setup for your site to your likings .
Just figured I’d post a couple tips to help people out because in the process of establishing my own registration form which works nicely now, I had to read through a variety of other threads to come to conclusions.
Ok to start, my registration form acts as an AR class model. Now to be honest, I don’t know why anyone would want to make a registration class extending CFormModel unless you’re not using a database.
You just don’t get the same options not to mention the easier efficiency in coding.
So to lay out what I was doing:
I was getting users to upon registering provide…
A username, that must be unique, between x length, and converted to lowercase.
A password, that must be converted to md5 hashing and then compared to a repeated password value also converted to md5 (however this 2nd password is not located in the database)
An email that must be unique and a valid email
A secret question for forgotten password usage (later) that is converted to lowercase and checks if a "?" is at the end, if false append a "?" at the end
A secret answer for forgotten password usage that is converted to lowercase.
Ok so it’s simple enough but here are some things to understand.
The reason why I would suggest using AR as opposed to CFormModel is because AR allows you to use more rule sets and as such, validation is done automatically, you don’t have to query the db, nor do you have to catch the exceptions and all that heavy work.
Also, in my case having to change all these inputs to valid inputs before saving, well by using the AR model and using the rules I do, it auto validates those values for me and then if that passes true, it will save all the values to the database. Simple - pretty much 3 or 4 lines of code.
Another thing to note, before figuring out filter rules I was trying to use $model->username=strtolower($model->username); to edit my values along with using md5 on password and strtolower for the other values. While this does work, you cannot and I will repeat this, you <strong class=‘bbc’>cannot</strong> modify a value after validation has been processed. So you’d have to punch in all that tedious coding for every input field (including the 2nd password into a hash) just to make sure the validation passes and then saves. Unfortunately that means you couldn’t just validate user input and then modify the needed values as you see fit before saving. (ie: comparing the two passwords the user submits before converting only the one to md5, and then trying to convert the password to md5 after they validate - this will deny the save process to the db, even though it will look like it went through). So the solution? Just use AR Rules
Why not just use the rules defined and then have validation do all the work for you? Here’s a look:
—> Please note that within your model class, any values that you define for registration that are not located in the database table you must define as variables within the class <— See the top of the user model rules code
My User model rules:
<?php
/*
* define repeat password variable & verify code variable
*/
public $password2;
public $verifyCode;
...
return array(
/* Due to a fellow user's observation this note is being
* posted for users. Using filters, you can combine certain
* variables together to simplify the processing. Where this
* was pointed out was for the question and answer filtering
* to lowercase. In fact the username as well. Technically
* the more efficient way is to process them as followed:
* array('username,question,answer', 'filter', 'filter'=>'strtolower'),
*/
array('username','length','max'=>32),
// convert username to lower case
array('username', 'filter', 'filter'=>'strtolower'),
array('password','length','max'=>64, 'min'=>6),
array('password2','length','max'=>64, 'min'=>6),
// compare password to repeated password
array('password', 'compare', 'compareAttribute'=>'password2'),
array('email','length','max'=>256),
// make sure email is a valid email
array('email','email'),
// make sure username and email are unique
array('username, email', 'unique'),
array('question','length','max'=>256),
// convert question to lower case
array('question', 'filter', 'filter'=>'strtolower'),
array('answer','length','max'=>128),
// convert answer to lower case
array('answer', 'filter', 'filter'=>'strtolower'),
array('username, password, password2, email, question, answer, verifyCode', 'required'),
// verifyCode needs to be entered correctly
array('verifyCode', 'captcha', 'allowEmpty'=>!extension_loaded('gd')),
);
?>
Now in discovering a flawed issue with using the filters to hash a password, I determined that the filter method of hashing does not hash properly – either it adds a salt or trims the string or something. At any rate during discussion with other wise members I was informed that it’s probably not a good idea to do this anyway and rightly so. Let’s assume that you did hash a password before a validation occurred – and then by odd chance something happens and the validation is returned to the user because it didn’t go through. That hashed password is not information you want your users to have access to. Since this is an undesired behaviour – it’s wiser to use the beforeSave() setup overriding the default function. Which is what I did.
<?php
/**
* @return actions to perform before saving ie: hash password
*/
public function beforeSave()
{
$pass = md5($this->password);
$this->password = $pass;
return true;
}
Now some people believe that a simple md5 hash is not enough and that’s fair, considering you are in control of how secure your site is, so why not make it more secure. I found an excellent example of a simple salt addition to a hashed password and that’s what I used instead. You’re welcome to use it – it just means you have to remember to use it everything you access the password information.
Just replace the appropriate code in beforeSave() with this:
<?php
$pass = md5(md5($this->password).Yii::app()->params["salt"]);
$this->password = $pass;
Now one thing important to note, if you do chose to do this, you need to go to your UserIdentify Component and modify the line there as well that authorizes the user login access. Since you’re now using a different hashing method you have to make sure you’re whole site is compliant with this or else you’ll have dysfunction throughout your site, and you wouldn’t want that
UserIdentity Componet » /protected/components/UserIdentity.php
<?php
// change this....
else if(md5($this->password)!==$user->password)
// to this...
else if(md5(md5($this->password).Yii::app()->params["salt"])!==$user->password)
And that’s that
Now to continue…
My SiteController actionRegister() method
<?php
public function actionRegister()
{
$form=new User;
// collect user input data
if(isset($_POST['User']))
{
$form->attributes=$_POST['User']; // set all attributes with post values
// NOTE Changes to any $form->value have to be performed BEFORE $form-validate()
// or else it won't save to the database.
// Check if question has a ? at the end
$last = $form->question[strlen($form->question)-1];
if($last !== "?")
{
$form->question .= '?';
}
// validate user input and redirect to previous page if valid
if($form->validate())
{
// save user registration
$form->save();
$this->redirect($this->render('finished',array('form'=>$form))); // Yii::app()->user->returnUrl
}
}
// display the registration form
$this->render('register',array('form'=>$form));
}
?>
As you can see I used a finished page render just to simulate the registration process working - however later I’ll be modifying that to make it like an email activation link.
I also commented about changing values BEFORE validating them and there you see the example I use to append the ? to the question value. You don’t see the beforeSave() function being called because Yii automatically calls that function, part of the basic model script. So stuff like beforeSave, afterSave, beforeValidation and default functions like that will run before those given actions – you don’t need to call them twice, or you’ll likely screw up your coding somewhere .
Basically the code works like this:
$variable = define new model instance
if condition -> check if form values ($_POST) are set
true -> proceed to set all $variable->attributes to the values set in $_POST
----->make any changes you need to any attribute before validating
----->call the $variable->validate() function in an if condition
---------->If Validation passes then call $variable->save() and process the url redirection or new page render.
---------->If Validation fails, it returns to the registration page render with the errors.
if the post values condition is false (they aren’t set) just render the registration page as normal.
And Voila, very simple.
So with that, aside from just customizing how the register form looks which I’ll post for people as well anyway, that was basically all I had to do to set up a user registration. Has to be the easiest user registration app I’ve ever done, for all the complexity it includes.
Here’s the rendered view:
<?php $this->pageTitle=Yii::app()->name . ' - Register'; ?>
<h1>Register</h1>
<div class="yiiForm">
<?php echo CHtml::beginForm(); ?>
<?php echo CHtml::errorSummary($form); ?>
<div class="simple">
<p class="hint" style="margin-left:70px;">
Please Note:
<br/>
Your login name, Secret Question and Answer will all be converted to lower case characters.
<br/>
This is to insure later use of these are not case-sentively forgotten.
</p>
<br/>
<p class="hint" style="margin-left:70px;">
Note: Your login name must be unique and a max of 32 characters.
</p>
<?php echo CHtml::activeLabel($form,'username', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'username') ?>
</div>
<div class="simple">
<?php echo CHtml::activeLabel($form,'password', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activePasswordField($form,'password') ?>
</div>
<div class="simple">
<?php echo CHtml::activeLabel($form,'password2', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activePasswordField($form,'password2') ?>
</div>
<div class="simple">
<?php echo CHtml::activeLabel($form,'email', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'email') ?>
</div>
<br/>
<div class="simple">
<p class="hint" style="margin-left:70px;">
Note: Your secret question that will pop up for you to reset your password (if needed).
</p>
<?php echo CHtml::activeLabel($form,'question', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'question') ?>
</div>
<div class="simple">
<p class="hint" style="margin-left:70px;">
Note: Your secret answer you'll need to type in when asked your secret question.
</p>
<?php echo CHtml::activeLabel($form,'answer', array('style'=>'width:150px;')); ?>
<?php echo CHtml::activeTextField($form,'answer') ?>
</div>
<br/>
<?php if(extension_loaded('gd')): ?>
<div class="simple">
<?php echo CHtml::activeLabel($form,'verifyCode', array('style'=>'width:150px;')); ?>
<div>
<?php $this->widget('CCaptcha'); ?>
<?php echo CHtml::activeTextField($form,'verifyCode'); ?>
</div>
<p class="hint">Please enter the letters as they are shown in the image above.
<br/>Letters are not case-sensitive.</p>
</div>
<?php endif; ?>
<div class="action">
<?php echo CHtml::submitButton('Register'); ?>
</div>
<?php echo CHtml::endForm(); ?>
</div><!-- yiiForm -->
Any questions feel free to post otherwise hope this helps people