Complex Validation in CakePHP 1.2
With version 1.2 we got a much more powerful validation feature. I think that many people migrating from version 1.1 may not realize how much it offers.
I offer a simple Users model. It has fields like username, password and email that require special and perhaps multiple validation rules. Here’s how we’ll get tricky;
- Username must be atleast X characters without spaces,
- Username must of course, Be unique!
- Email must be valid email and Unique
- Password must be confirmed during registration or renewal process
That is to say compared.
We can also set a unique error message to each rule, and it will apply to both our add and edit forms.
Controller
No special work is done in the controller, so you can just use your basic scaffolded controller action and $this->Model->save() method. All the trickery can be handled within the model.
Model
Here’s where the magic happens. We can use multiple rules per field. This included minimum / maximum length requirements; and for username, a special RegEX. The password field also has a basic RegEx check before calling in a method to compare the two password fields before signing off and hashing the field. Each individual rule is assigned its own error message.
1.2 Users – In app/models/user.php
[sourcecode language="php"]
class User extends AppModel{
var $name = 'User';
var $actsAs = array ('Userban'=>array());
var $displayField = ‘first_name’;
var $recursive = 0;
var $validate = array(
‘username’ => array(
‘required’ => array(‘rule’=>VALID_NOT_EMPTY,’message’=>’Please enter your login name’),
‘pattern’ => array(‘rule’ => array(‘custom’,'/[a-zA-Z0-9\_\-]{6,30}$/i’),’message’=>’Must be 4 characters or longer with no spaces.’),
‘unique’ => array(‘rule’ => array(‘validateUniqueUsername’),’message’=>’This username is already in use, please try another.’),
),
‘first_name’ => array(
‘required’ => array(‘rule’=>VALID_NOT_EMPTY,’message’=>’You\’ll need a name friends and family will recognize!’),
‘length’ => array( ‘rule’ => array(‘maxLength’, 60),’message’=>’That names a bit too long, keep it under 60 characters’ )
),
‘last_name’ => array(
‘required’ => array(‘rule’=>VALID_NOT_EMPTY,’message’=>’You\’ll need a name friends and family will recognize!’),
‘length’ => array( ‘rule’ => array(‘maxLength’, 60),’message’=>’That names a bit too long, keep it under 60 characters’ )
),
‘password’ => array(
‘required’ => array(‘rule’ => array(‘custom’,'/[a-zA-Z0-9\_\-]{6,}$/i’),’message’=>’Must be 6 characters or longer’),
‘length’ => array( ‘rule’ => ‘validatePassword’,'message’=>’Your passwords dont match!’ )
),
‘email’ => array(‘rule’=>’email’,'message’=>’Please enter your email address’)
);
/**
* validation functions
*/
/**
* Check for existing user
*/
function validateUniqueUsername(){
$error=0;
//Attempt to load based on data in the field
$someone = $this->findByUsername($this->data['User']['username']);
// if we get a result, this user name is in use, try again!
if (isset($someone['User']))
{
$error++;
//debug($someone);
//exit;
}
return $error==0;
}
function validatePassword(){
$passed=true;
//only run if there are two password feield (like NOT on the contact or signin pages..)
if(isset($this->data['User']['confirmpassword'])){
if($this->data['User']['password'] != $this->data['User']['confirmpassword']){
//die(‘you fail’);
$this->invalidate(‘checkpassword’);
//they didnt condifrm password
$passed=false;
}else{
//hash passwordbefore saving
$this->data['User']['password']=md5($this->data['User']['password']);
}
}
return $passed;
}
}?>
[/sourcecode]
View
in app/views/users/register.ctp,
[sourcecode language="php"]
register
input(‘User.first_name’, array(‘size’ => ’30′));?>
input(‘User.last_name’, array(‘size’ => ’20′));?>
error(‘User.duplicateemail’,'Sorry, That email is already is use
‘.$html->link(‘Is it yours?’,'/users/resetpassword/’.$this->data['User']['email']).’ ‘,array(‘escape’=>false))?>
error(‘User.’, ‘Are you human!’);?>
data);?>
[/sourcecode]
As you can see, the limits to validation are endless because worse scenario you write your own method unique to the situation.
Post Script:
Any feedback on the new syntax highlighting would be appreciated. This one allows direct copy to your clipboard, and plain text viewing but trashes the formatting. Maybe someone knows of another plugin for wordpress to do the same. If not I think I’ll stick with GeSHi
AFAIK, you don’t need to write your own custom ValidateUniqueUsername function. You can just specify the rule:
array('rule' => 'isUnique', 'field' => 'username', 'message' => 'This username is already in use, please try another')Hi, thanks very much for this post. Saved me time.
Little recommendation, though. In the model, instead of manually hash the password, try this better:
Security::hash($this->data['User']['password'],null,true);
that will use default hashing algorithm and salt.
@Javier
Excellent correction, I completely looked over that method myself.
Anyone else may find more info on the isUnique method,
http://api.cakephp.org/class_model.html#e631f2cba7ec7fff079d3a7b62c46f0e
@Zarate, glad it helped you, and thanks for the tip!
Anyone who wants to know more about the Hash function should visit the API,
http://api.cakephp.org/security_8php-source.html#l00119.
For a bit more info on SHA1 and MD5 encryption this thread may be interesting,
http://www.sandelman.ottawa.on.ca/ipsec/1996/05/msg00116.html.
Just a quick note…
VALID_NOT_EMPTY has deprecated and is now replaced by the ‘notEmpty’ rule…
so ‘rule’=>’notEmpty’ is the “new” way of handling that validation.
Pretty interesting, I’ll use it as starting point for my user model. Thanks^^
@Mirthis
Glad you found it useful. You may be interested to check out http://edwardawebb.com/programming/php-programming/cakephp/reset-lost-passwords-cakephp for some additional requirements in a User model.
Nice tutorial but I’m missing one thing that was explained in the previous tutorial concerning passwords. The password length is no problem, those rules are all pretty easy to handle. But I want my passwords to have at least 1 upper case and 1 special character and I just can’t figure out a proper rule for that. How would I do that? Thanks in advance.
@sooid Thanks.
I think if you have a look at my article on validating passwords you’ll find what you need.
This password must be 8 characters
Contain a Number
Contain a special character
Contain a Capital Letter
Contain a Lowercase Letter
Basically you just remove the original parentheses grouping digits and non-words in the original regex I think you refer to.
I added a Ludicrously Complex regex on that page.
Just a quick question… Isn’t the password hashed before the validation takes place? Wouldn’t this make checking password complexity inaccurate because you are checking the length of the hash opposed to the actual password the user has entered?
@Lane
Good Question
I don’t use the Auth component and hash the password myself after I do validation. You’ll notice these lines in the code above in the model’s validation