You are on page 1of 8

http://bakery.cakephp.

org/articles/view/using-equalto-validation-to-compare-two-form-fields

CakePHP API Docs Bakery Live Forge Trac Login Register About CakePHP Donate

News Articles Code Articles

Using Custom Validation Rules To Compare Two Form Fields


By Aran Johnson (aranworld) TUTORIALS JAN 14, 2008

Want to make sure that two submitted form fields have the same value? Use the ability to write Details
your own validation rules, to add this validation to your model.
Version: 1.2 beta
It is very common when collecting a user's email address, or password, to force the user to type the Views: 14082
string twice in two separate form fields. Prior to saving the data, one can check that the string in both
Comments (14)
fields match.

While there are already a number of ways to do this using Javascript alone, or Ajax, I wanted to use Rating
Cakephp 1.2's validation system to handle this task.
4.00 by 1 User
There doesn't exist a built-in validation that works quite right, but luckily you can write your own
validation rules ( http://tempdocs.cakephp.org/#TOC132710 ) and they will process along with any
built-in rules you are using. Log in to add
rating
I will demonstrate this using a simple contact information form.

The Table Tags

Download code validation


CREATE TABLE `contacts` ( validate
`id` int(6) NOT NULL auto_increment, rule
`name` varchar(100) default NULL,
password
`email` varchar(200) default NULL,
identical
PRIMARY KEY (`id`)
); form
fields
email
Controller Class:
custom
Download code
<?php class ContactsController extends AppController { Log in to add tags

var $name='Contacts';
var $uses = array('Contact');
var $helpers = array('form');

function add(){

if( !empty( $this->data ) ){

if( $this->Contact->save( $this->data ) ){


$lastId = $this->Contact->getLastInsertId();
$this->flash
('Your new user has been created.','/c/contacts/view/'.$lastId );
}
}
}
}
?>
Just a very basic add method. Note, however, that nothing in this controller deals with comparing form fields.
View Template:
Download code
<h1>Enter Contact Information</h1>

<form method="post" action="<?php echo $html->url('/contacts/add')?>">

<?php echo $form->label('Contact.name', 'Full Name of Contact'); ?>


<?php echo $form->error('Contact.name'); ?>
<?php echo $form->text('Contact.name', array('size' => '80') ); ?>

<?php echo $form->label('Contact.email', "Contact's E-mail"); ?>


<?php echo $form->error('Contact.email'); ?>
<?php echo $form->text('Contact.email', array('size' => '80') ); ?>

<?php echo $form->label('Contact.confirm_email', 'Re-enter E-mail For Verification'); ?>


<?php echo $form->text('Contact.confirm_email', array('size' => '80') ); ?>

<?php echo $form->submit('Add Person to Directory'); ?>


</form>

If you are new to Cake 1.2, this might look a bit strange. The html helper is no longer used for forms, but instead the 'form' helper is
used.

Note that in the calls to the Form::error method, I do not include an error message. This message will be provided by the particular
rule in the Contact class.

Model Class:
Download code
<?php
class Contact extends AppModel
{
var $name = 'Contact';
var $validate = array(
'email' => array(
'identicalFieldValues' => array(
'rule' => array('identicalFieldValues', 'confirm_email' ),
'message' => 'Please re-enter your password twice so that the values match'
)
)
);

function identicalFieldValues( $field=array(), $compare_field=null )


{
foreach( $field as $key => $value ){
$v1 = $value;
$v2 = $this->data[$this->name][ $compare_field ];
if($v1 !== $v2) {
return FALSE;
} else {
continue;
}
}
return TRUE;
}

}
?>
This is where things get interesting. Here is more information about Cake 1.2's new validation configuration:
http://tempdocs.cakephp.org/#TOC127334.
The validate attribute contains an array. In the array, we declare that for the field email, we will use a rule called identicalFieldValues.

Download code
'rule' => array('identicalFieldValues', 'confirm_email' )

This line says that the rule will use the validation method identicalFieldValues, and when it calls this method it will provide as the second
argument the string 'confirm_email'.

The Home Brewed Validation Function


As the model code above illustrates, I added a method named identicalFieldValues into the Contact class.

The call to this method happens from within Model::invalidFields(). When it is called, the first parameter is passed as an array:
Download code
array('email' => 'webmaster@gmail.com')

The key is the string representing the field's name, and the value represents the value of that field. This is how all customized
validation functions are now called.

The second argument is the string provided in the array under 'rule' in the validate attribute. In this case it is the string
'confirm_email'. This string represents the name of the field I am comparing the first field to.

To get the first value I extract it from the passed array.

The second value I extract from the Model's data array by using the string passed as the second argument.

Once I have the two variables set, I can compare them however I want. I return a false if the values don't match, and a true if they
do.

Now, if a person submits the Contact form with mismatched values in the two email fields, the Contact::save method will fail and the
form will be re-displayed with an error message.

Using this for Passwords


The other obvious usage of this is when a new user registers and provides a password.

If you are using the AuthComponent, and the name of the password field you are checking is equal to the column name for the User's
password then this value will automatically be hashed prior to validation, but the 'confirm_password' value will NOT be hashed.

A fix to this, is to name the password fields in your Users/add form something like 'new_password' and 'confirm_password'. Before
calling the User::save() method, hash both of these values using the Auth->password() function.
Download code
//add this function to the users_controller.php
function convertPasswords()
{
if(!empty( $this->data['User']['new_passwd'] ) ){
$this->data['User']['new_passwd'] = $this->Auth->password($this->data['User']['new_passwd'] );
}
if(!empty( $this->data['User']['confirm_passwd'] ) ){
$this->data['User']['confirm_passwd'] = $this->Auth->password( $this->data['User']['confirm_passwd'] );
}
}
Then in a custom User::beforeSave() method, which is called after validation succeeds, pass the value of new_password to the data
field for the real password field (most likely something like 'passwrd').
Download code
//add this function to your user model and call it from within beforeSave()
function setNewPassword()
{
$this->data['User']['paswd'] = $this->data['User']['new_passwd'];
return TRUE;
}
function beforeSave(){
$this->setNewPassword();
return true;
}
Using these modifications, you can now use the identicalFieldValues() function in your User model to make sure that when the user
adds their requested password, that both fields match. In addition, don't forget that you can have multiple rules for each field
( http://tempdocs.cakephp.org/#TOC127334 ), so if you want to do any other checks on the password field you can do those as well.

Comments
CakePHP Team Comments Author Comments

1 Feedback Comment

@Aran: thanks for sharing! Can you please update your article as Changeset 6187 has implemented the equalTo validation? Thanks!

Posted Dec 26, 2007 by Mariano Iglesias

2 Now updated to reflect more recent changes... Comment

@Aran: thanks for sharing! Can you please update your article as Changeset 6187 has implemented the equalTo validation?
Thanks!

Thanks for the heads up. I have modified the article now to reflect the way the equalTo method has been implemented. I think the
article is possibly more useful now, since the implemented equalTo() method doesn't seem to function in the way I write about in this
article, so overwriting the core function makes more sense.

Posted Dec 31, 2007 by Aran Johnson

3 Modified in response to 1.2 beta Comment

@Aran: thanks for sharing! Can you please update your article as Changeset 6187 has implemented the equalTo
validation? Thanks!

Thanks for the heads up. I have modified the article now to reflect the way the equalTo method has been implemented. I think
the article is possibly more useful now, since the implemented equalTo() method doesn't seem to function in the way I write
about in this article, so overwriting the core function makes more sense.

I have just seen the nice documentation in tempdocs about creating custom validation rules. So, I have eliminated all references to
equalTo and instead focuesed this on being a tutorial about solving the compare fields problem by using custom validation rules.

Posted Jan 2, 2008 by Aran Johnson

4 new password and confirm password field populated with hash after validation fails Comment

I have one small problem using this with Auth (with your instructions above).

When the validation fails, $this->data['User']['new_passwd'] and $this->data['User']['confirm_passwd'] is populated with the HASH.

Any ideas?

Posted Jan 4, 2008 by Baz L

5 Nullifying password values Comment

I have one small problem using this with Auth (with your instructions above).
When the validation fails, $this->data['User']['new_passwd'] and $this->data['User']['confirm_passwd'] is populated with the
HASH.

Any ideas?

In your Users::add() create the following logic:


if ($this->User->save($this->data)) {
$this->flash('Your new user has been created.','/users/index' );
} else {
$this->data['User']['new_passwd'] = null;
$this->data['User']['confirm_passwd'] = null;
}

This will ensure that the hashed password is never re-displayed in the form.

Posted Jan 7, 2008 by Aran Johnson

6 convertPasswords hashes before valdiation. How do I prevent this Question

In your Users::add() create the following logic:


if ($this->User->save($this->data)) {
$this->flash('Your new user has been created.','/users/index' );
} else {
$this->data['User']['new_passwd'] = null;
$this->data['User']['confirm_passwd'] = null;
}

This will ensure that the hashed password is never re-displayed in the form.

I actually used unset() but I guess where you're going.


Here's another issue. When you call convertPasswords() it sends hashed values to the validation. This is fine for most people, but not
for me. I can't do any other validation on the new_password fields (not empty, length, etc).

To answer my question this is what I've had to do: change all my $this->User->save's to:

// needed for validation for some reason


$this->User->set($this->data);
if ($this->validates($this->data))
{
$this->convertPasswords();

// all we did was hash passwords, no need to revalidate.


if ($this->save($this->data, false))
{
// do stuff
} else
{
// invalid
}
}

Is there a more elegant way to do this that doesn't break MVC guidelines? Somehow calling the Auth comp. from the model's
beforeSave()?

Posted Jan 7, 2008 by Baz L

7 Calling AuthComponent in Model Comment


Somehow calling the Auth comp. from the model's beforeSave()?

You can call password() statically (AuthComponent::password()), but you have to ensure that such class wis declared previously.

By the way:
Method identicalFieldValues() can by shortened:

function identicalFieldValues( $field=array(), $compareWith=null ) {


return($this->data[$this->name][$compareWith] === array_shift($field));
}

Regards

Posted Jan 9, 2008 by Michał Bachowski

8 No Loop Comment

Isn't there supposed to be a loop with this array_shift thing?

I'm pretty sure calling statically isn't going to work, since it's not defined that way.

I'll just extract the contents of it. It's a one liner anyways:

return Security::hash(Configure::read('Security.salt') . $password);

Posted Jan 9, 2008 by Baz L

9 Loop is not necessary Comment

Isn't there supposed to be a loop with this array_shift thing?

I don`t use Cake 1.2 (still 1.1.x ;) ), but Aran wrote in the article:

The call to this method happens from within Model::invalidFields(). When it is called, the first parameter is passed as an array:

array('email' => 'webmaster@gmail.com);

So IMO there`ll be only one item in array - foreach() isn`t necessary.

I'm pretty sure calling statically isn't going to work, since it's not defined that way.

It will work both PHP 4 and 5 (but in PHP5 very slow).

I'll just extract the contents of it. It's a one liner anyways...

If you use it only once it`s ok, but what if you want to use it many times in many places?

Regards

Posted Jan 9, 2008 by Michał Bachowski

10 foreach not needed Comment

So IMO there`ll be only one item in array - foreach() isn`t necessary.


Regards

You are probably right about the foreach() not being needed.
I mainly used it to make it very explicit that passed argument was an array.

Posted Jan 11, 2008 by Aran Johnson


11 Modification to convertPasswords() Bug

I have modified the example convertPassword() function. In my original version, there was the possibility of a user creating an empty
password, because when an empty value is hashed, it creates a full length hash that would be entered into the database.

Posted Jan 11, 2008 by Aran Johnson

12 simplify model method Comment

you should simplify the method in your model... There's no need for it to be that complicated, something like:

function identicalFieldValues($field1, $field2) {


if (!strcmp($this->data['User'][key($field1)], $this->data['User'][$field2]))
return true;
return false;
}

should achieve the same result

Posted May 3, 2008 by nick milosevic

13 not so sure about strcmp Comment

strcmp is returning false positives for me. Michal's alternate code using the '===' is a better abbreviated version of this.

Posted Jun 10, 2008 by Aran Johnson

14 Great Post Comment

Aran, thanks for the great example using Cake's custom validation feature. I've added this to my application and did some cleanup of
my own to comply with the documentation's convention. Cheers!

Posted Aug 23, 2008 by Oliver John Tibi

15 Alternative to convertPasswords Comment

I'm new to cake, so I'd like to hear some thoughts on this. I'm using Auth, so my password is being hashed before I can validate it.
But instead of using a 'new_passwd' and convertPasswords, it occurred to me that my confirmpassword field is not being hashed.
So, I can do all of my complexity validation on the confirmpassword, then hash it for comparision to password.
I made a small change to identicalFieldValues:

function identicalFieldValues( $field=array(), $compare_field=null )


{
foreach( $field as $key => $value ){
$v1 = $value;
$v2 = $this->data[$this->name][ $compare_field ];
if ($key == 'password') $v2 = AuthComponent::password($v2);
if($v1 !== $v2) {
return FALSE;
} else {
continue;
}
}
return TRUE;
}
}

I've added the line to hash the compare field if I'm validating 'password'.
(This needs some work to compare against Auth->fields['password'] instead of assuming the field is named 'password', and to detect
if Auth is even being used, but this works for me.)

Here's my $validate:
var $validate = array(
'password' => array(
'rule' => array('identicalFieldValues', 'confirmpassword' ),
'message' => 'Passwords do not match.'
),

'confirmpassword' => array(


'complex' => array(
'rule' => array('custom', '/(?=^.{7,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/'),
'message' => 'Password should be at least 7 characters, must have at least 1 upper case, lower case and num
),
'notEmpty' => array(
'rule' => array('notEmpty'),
'allowEmpty' => false,
'message' => 'Please enter a password.'
),
),

So far, the only problem I've seen is that the error messages get swapped from what would be expected; The 'Passwords do not
match' message appears on the password field and the 'Please enter a password' appears on the confirmpassword field. So I just
arranged my form so the confirmpassword error appears above the field, between the two.

Posted Oct 17, 2008 by Chris Darling

Login To Submit A Comment

Latest Articles Latest Code Latest News Latest Comments

Mobilize your cake app in Validanguage Helper for introducing something new Problem
minutes customizable Javascript form to chew on Nothing wrong with
Wizard Component 1.2 validation CakeFest #2 includes...
Tutorial FlashHelper - a wrapper for Release: CakePHP RC3 - The admin routing
Migrating a real-world the SwfObject js class RC of Triumph!
Referrer is not always
application to CakePHP 1.2 Wizard Component 1.2 CakePHP Workshop, Sept reliable
Brita component with HTML FlashChartHelper - version 3 6&7, Raleigh NC USA JSON scaffolding
Purifier How to create an XML-RPC Release: Some RC2
Bake ROT13 Encoded server with CakePHP Sweetness
"mailto:" Links

© 2006-2008 Cake Software Foundation

You might also like