Validation with CakePHP 1.2

Published on and tagged with cakephp  model  validation

Up to now the validation support consisted of four constants (VALID_NOT_EMPTY, VALID_NUMBER, VALID_EMAIL, and VALID_YEAR), everything else you had to do yourself. The new validation class in CakePHP 1.2 is a bit more powerful ;-)

The aforementioned constants still work in 1.2, but they are deprecated, so for new code you shouldn’t use them. The usage of the new validation rules follows the following pattern (in your model):

var $validate = array('field' => array('rule' => array('validationmethod', 'param1', 'param2')));

Ok, let’s have a look at the validation methods.

alphaNumeric should be self-explanatory, it allows only letters and strings. Useful for e.g. user names:

var $validate = array('username' => array('rule' => array('alphaNumeric')));

between is a bit misleading, as I expected that it checks whether a number is in a certain range. But what it actually does is to check the string length. In the following example the length of the user name must be at least 3 characters, and maximal 10 characters:

var $validate = array('username' => array('rule' => array('between', 3, 10)));

blank validates that a field is blank or contains only whitespace characters like tabs or spaces. It doesn’t look very useful to me. Its usage is:

var $validate = array('x' => array('rule' => array('blank')));

cc doesn’t stand for “carbon copy”, it stands for “credit card”. It checks whether a credit card number is valid. If you want to check for Visa numbers, you would do the following:

var $validate = array('creditcard' => array('rule' => array('cc', array('visa'))));

comparison is used to compare numeric values. It supports “is greater”, “is less”, “greater or equal”, “less or equal”, “is less”, “equal to”, and “not equal”. To check whether the provided age was higher than 18 you would do:

var $validate = array('age' => array('rule' => array('comparison', '>=', 18)));
or
var $validate = array('age' => array('rule' => array('comparison', 'greater or equal', 18)));

custom is also a bit misleading imho. It allows you to define a regular expression.

var $validate = array('username' => array('rule' => array('custom', '/^[0-9a-z]+$/i')));

date checks for a valid date. It allows different formats, see the API for details. To check whether the entered date is of the format yyyy-mm-dd resp. yy-mm-dd you would do:

var $validate = array('startdate' => array('rule' => array('date', 'ymd')));

decimal checks for a valid decimal number. If you want to have decimal numbers with two digits after the dot you have to use:

var $validate = array('price' => array('rule' => array('decimal', 2)));

It is obvious, what email does: it checks for a valid email address:

var $validate = array('email' => array('rule' => array('email')));

I am not sure whether money is fully implemented yet, at least I was not able to use this validation rule.

numeric is a simple wrapper for the PHP function is_numeric:

var $validate = array('age' => array('rule' => array('numeric')));

phone validates phone numbers. At the moment it supports only US phone numbers, if you want to validate other phone numbers you have to provide a regular expression.

var $validate = array('phone' => array('rule' => array('phone', null, 'us')));

postal is used to validate zip codes from the U.S., Canada, and the U.K. For other zip codes you have to provide a regular expression.

var $validate = array('zipcode' => array('rule' => array('postal', null, 'us')));

ssn validates social security numbers from the U.S., Denmark, and the Netherlands. For other social security numbers you have to provide a regular expression.

var $validate = array('ssn' => array('rule' => array('ssn', null, 'us')));

url is used to validate urls.

var $validate = array('website' => array('rule' => array('url')));

If you look at the validation class you will notice several other public methods. equalTo, file, ip, minLength, maxLength, multiple, and number are validation rules which are not implemented yet, so I don’t have described them. The userDefined method looks like it allows you to create custom validation methods, but up to now I couldn’t figure out how to use it…

Thanks to Nate for his initial explanations in the Google Groups.

Update 2007-01-06: minLength and maxLength have been implemented in changeset 4264. They can be used to validate the length of a string. If a string must consist of at least three characters you would do:

var $validate = array('username' => array('rule' => array('minLength', 3)));

And to validate that a string can consist of maximal 20 characters you do:

var $validate = array('username' => array('rule' => array('maxLength', 20)));

Update 2007-01-07: Also ip has been implemented, which is used to validate IP addresses.

var $validate = array('ip' => array('rule' => 'ip'));

141 comments baked

  • cakebaker

    @Jaydeep: Yes, this validation rule accepts “urls” like “test.com” which is, as you say, not correct according to RFC 1738. So I reopened a ticket for this issue which was closed a while ago as “invalid”. Maybe it will get fixed…

  • rafaelbandeira3

    the idea of having $validate set on __construct is certainly a bad and by any means a good design. Validation rules are part of the signature, specification and concept of the Model, and as such should be signed on it’s declaration.
    There are so many other means to achieve L10n on error messages, and probably the best is direct in the view layer:
    1 – you’ll won’t suffer the overhead of having localised messages you won’t use;
    2 – there are absolute no usage for translated messages being set on the “runtime environment” (if you test errors by it’s message, know that it’s totally bad design: errors should be tested by code) so they should only be translated where they will really be used.

  • cakebaker

    @Rafael: Setting $validate in the constructor is not worse than setting $validate in the traditional way. It is just a different way to accomplish the same.

    And regarding L10n of error messages, I think it depends on your point of view whether you localize them in the model or in the view. Doing it in the model has the advantage that it is DRY (don’t repeat yourself).

  • Matthew

    I think there is a bug in maxLength validation. (??)

    I must set maxLength to 6
    in order to effectively limit to 4 characters. There’s an extra 2 unaccounted for characters.

    For example try this validation code on field “Name:”

    var $validate = array(
    ‘name’ => array(
    ‘alphaNumeric’ => array(
    ‘rule’ => ‘alphaNumeric’,
    ‘allowEmpty’ => false,
    ‘message’ => ‘no crazy characters allowed’
    ),
    ‘maxLength’ => array(
    ‘rule’=>array(‘maxLength’,6),
    ‘allowEmpty’ => false,
    ‘message’ => ‘no more than 4 characters allowed’
    )
    )
    );

    Starting with a submission of 123456™, you get the error “no more than 4 characters allowed” and don’t get the error “no crazy characters allowed” until you cut the field submission down to 123™.

    Any thoughts on why this is?

  • Matthew

    more about the above
    I tested “maxLengh” alone, with no other conditions, and it works fine.
    But when coupled with a second condition (alphaNumeric) as described above, it is again off by two characters.

    My only theory is that in the encoding and form submission process, non-alphanumeric characters (like ™ and ∞ and ¶) get converted to multiple characters.

    Reversing the order of the conditions, so that it checks for alphaNumeric first makes this a non-issue. Hope this is helpful….

  • cakebaker

    @Matthew: Yes, it is because those characters are multibyte characters and Cake uses the strlen() function in the Validation::maxLength() method. And strlen() doesn’t work correctly with multibyte characters (you would have to use mb_strlen()). For example, if you use strlen(‘™’) it returns 3…

  • nowasabi

    What about conditional validation?

    assume $this->MyModel[‘flag’] and $this->MyModel[‘field’]
    I want to validate ‘field’ ONLY if ‘flag# is set ?

    any idea?

  • cakebaker

    @nowasabi: You have to write a custom validation method. Something like

    // in your model
    public validate = array('field' => array('myValidationMethod'));
    
    public function myValidationMethod() {
        // here you can access $this->data['YourModel']['field'] and perform your validation
    }

    Hope that helps!

  • nowasabi

    Thanks that helped a lot.

    Please let me ask another question:

    I stumpled about a “simple” task :

    echo $form->input(‘appointment’,array( ‘dateFormat’ => ‘DMY’,
    ‘separator’=>”, ‘showEmpty’=> true);

    This does not work. How do I get an empty date fieldset?

  • cakebaker

    @nowasabi: Well, you are using a non-existent option, replace “showEmpty” with “empty”, and it should work :)

  • yoxs

    Hi, I’m just a beginner like me to know how to apply the label appearance, ie the appearance of the error message:

    echo $ form-> error ( ‘User.state’, array (‘?’));

    Thank you very much

  • cakebaker

    @yoxs: Hm, I don’t understand what you try to accomplish :| Can you elaborate a bit more on what you try to do?

  • m

    about ‘blank’ rule:
    problem: i want validate field ‘id’ .
    use case
    1. adding rows to table (then ‘id’ is left blank);
    2. modifing rows in table (then ‘id’ should be correct one)

    my rule:

    var $validate = array('id' => 
                            array('nullness'=>array('rule'=>'blank'), //should be null at all
                                'format'=>array('rule'=>array('custom', //correct id shouldn't be null
                                                             '/[1-9]([0-9])*/')),
                                'length'=>array('length'=>array('rule'=>array('maxLength',11)))
                                               //database allows only for primary keys 11 sings long (my design)
                                ),
                       // .... other stuff here

    i wonder if it is right build of the rule (order of the rules and how cake behave analizing them), but
    how to validate ‘id’ field without ‘blank’ rule?

    regards,
    m

  • cakebaker

    @m: Hm, I’m not sure I understand what you try to accomplish, but maybe the “on” option is what you are looking for. You can set it to either “create” or “update”, which then determines when the respective rule is applied. See also http://book.cakephp.org/view/127/One-Rule-Per-Field.

    Hope that helps!

  • m

    @cakebaker: it seems to me, that i’ve missed sth. i didn’t know about ‘on’. Now my rules (not only checking ‘id’ but also ‘username’, ‘pass’ …) will be much clearer.

    But now i wonder if i realy need to validate field ‘id’, or it’s just my paranoic atitude.

    Thanks a lot,
    m

  • cakebaker

    @m: Personally, I don’t validate the “id” field because I usually use the auto-increment feature of MySQL. However, this means I have to ensure that an “id” provided by the user is ignored when creating a new record. For this purpose the $fieldList param of Model::save() can be used.

    Hope that helps!

  • Atea

    Usefull, thank you!

  • cakebaker

    @Atea: You are welcome!

  • von

    I have the problem with my validation, my form will never been validate.
    I am a beginner, can u help u to solve my problem?

    This is my View:

    <?php
    echo $form->input('first_name',array('error'=>array('required' => 'First name is require', 'length' => 'Must be more than 2 characters'), 'size' => 30, 'class' => 'field'));
    ?>
    
    this is my controller:
    function register(){
    if ($this->Member->create($this->data) && $this->Member->validates()) {
    $this->Session->setFlash('Your registration information was accepted.');
    }}
    
    and this is my model:
    var $validate = array(
        'first_name' => array(
            'required' => VALID_NOT_EMPTY,
    		'length' => array(
    		'rule' => array(’between’,3,30))));

    It is really urgent! Hope u can reply ASAP!
    thanks for helping!

  • cakebaker

    @von: Hm, one possible issue could be the “required” option, because it should be a boolean value (see http://book.cakephp.org/view/127/One-Rule-Per-Field#required-129), though I’m not sure whether this is the reason it doesn’t work.

    Did you have a look at what $this->data contains?

    Hope this helps!

  • Angeline

    I have the same problem as senseBOP and tiago. I am having a similar view page and a controller as mentioned by you. Also I tried including the $form->error message in the view as said by tiago,Yet I do not get error msgs when I give invalid inputs. It just goes to the next page,but the entry does not get saved in the database.

  • cakebaker

    @Angeline: Hm, do you do a redirect when the validation fails? If you do, then remove the redirect, and try it again.

    Hope that helps!

  • Angeline

    Earlier I had a redirect when the validation failed. But now I have changed the code as given below. Yet i dont get the error messages.
    Only when the login is successful,I do a redirect to the homepage. Is there any method to render the next page without using a redirect. I also used $this->render(),but it doesn’t work. Please help me.

    function register()
    	{
    		$userId=$this->User->registerUser($this->data);
    		$this->User->data=$this->data;
        		if (!$this->User->validates())
        		{
          			$this->Flash('Please enter valid inputs','/forms' );
        			return;	
        		}
        		$this->Flash('User account created','/forms/homepage/'.$userId);           	
    	}   
    function login()
           {
    		
    		$userId=$this->User->loginUser($this->data);
    		$this->User->data=$this->data;
    		
    		if (!$this->User->validates())
        		{
          			$this->Flash('Please enter valid inputs','/forms' );
        			return;	
        		}
    		if($userId>0){
    			$this->Flash('Login Successful');
    			$this->redirect('/forms/homepage/'.$userId);
    			break;
    			
    		}
    		else{
    			$this->flash('Username and password do not match.','/forms');
    		}		
    	}
  • cakebaker

    @Angeline: Hm, somehow I don’t understand what the problem is. If you post data to the register() action, what happens then? Do you get any errors? And what is displayed?

    And instead of using the flash() method (because it makes a redirect) I would use $this->Session->setFlash(‘your message’) in your controller, and echo $session->flash() in your view to show the message.

    Hope that helps!

  • Angeline

    If I am to register myself to the application, and if I leave the Name field empty, the custom message ‘This field should not be empty’ should appear near the name text box right?
    I don’t get that.

    I only get the flash message, ‘Enter valid inputs’.

    I need the error message to appear near the text box, if it is submitted empty.

  • Angeline

    I do not have a separate view file for register. I have my registration and login code of the application in a main controller and the views in a single index.ctp file. If the registration or login is valid, the page is redirected to the home page of the main controller, else the control returns to the same main function.

    Is that why, the custom error messages are not displayed. Because, if I have a separate view file for register module, then I get the custom messages.

    But I do not want a separate register view file and a separate login view file. I want to have both the functions in index file of the main controller. Could you help me?

  • cakebaker

    @Angeline: Hm, can you provide some code?

  • Angeline

    This is my model:

    class User extends AppModel {
        var $name = 'User';
        var $components=array('Auth');
        var $validate = array(
                              'name' => array(
                                             'rule'    => VALID_NOT_EMPTY,
                                             'message'  => 'Name cannot be null.'
                                             ),
                              'password' => array(
                                                  'rule' => array('minLength', '6'),
                                                  'message' => 'Minimum 6 characters long.'
                                                  ),
                              'email_id' => array(
                                                'rule'=> array('email'),
                                                'message'=> 'Invalid email.'
                                                )
                              );
    
        function registerUser($data)
        {
            if (!empty($data)) 
            {
                $this->data['User']['name']=$data['User']['name'];
                $this->data['User']['email_id']=$data['User']['email_id'];	
                $this->data['User']['password']=$data['User']['password'];
                if($this->save($this->data))
                {
                    $this->data['User']['id']= $this->find('all',array('fields' => array('User.id'),
    									'order' => 'User.id DESC' 		
    									));
                    $userId=$this->data['User']['id'][0]['User']['id'];
                    return $userId;
                }
            }
        }
    
        function loginUser($data)
        {
            $this->data['User']['email_id']=$data['User']['email_id'];	
            $this->data['User']['password']=$data['User']['password'];			
    		
            $login=$this->find('all');
            foreach($login as $form):
                if($this->data['User']['email_id']==$form['User']['email_id'] && $this->data['User']['password']==$form['User']['password'])
                {
                    $this->data['User']['id']= $this->find('all',array('fields' => array('User.id'),
    									'conditions'=>array('User.email_id'=> $this->data['User']['email_id'],'User.password'=>$this->data['User']['password'])		
    									));
                    $userId=$this->data['User']['id'][0]['User']['id'];
    				
                    return $userId;
    				
                }
            endforeach;
        }	
    }

    And my controller file is as follows:
    class UsersController extends AppController 
    {
        var $name = 'Users';
        var $uses=array('Form','User','Attribute','Result');
        var $helpers=array('Html','Ajax','Javascript','Form');
    	
        function register()
        {
            $this->Session->write('userId',$this->User->registerUser($this->data));
            $this->User->data=$this->data;
            if (!$this->User->validates())
            {
                $this->Flash('Please enter valid inputs','/main' );
          			
                return;	
            }
            $this->Flash('User account created','/main/home');     		
        }  
    
        function login()
        {
            $this->Session->write('userId',$this->User->loginUser($this->data));
            $this->User->data=$this->data;
    		
            if (!$this->User->validates())
            {
                $this->Flash('Please enter valid inputs','/main' );
                return;	
            }
            if($this->Session->read('userId') > 0){
                $this->Session->setFlash('Login Successful');
                $this->redirect('/main/home');
                break;			
            }
            else{
                $this->flash('Username and password do not match.','/main');
            }		
        } 
    }

    <!-- File: /app/views/main/index.ctp -->
    Register
    <?php
    create('User',array('action'=>'register'));
    echo $form->input('name');
    echo $form->input('email_id');
    echo $form->input('password');
    echo $form->end('Register');
    ?>
    
    Login
    <?php
    create('User',array('action'=>'login'));
    echo $form->input('email_id');
    echo $form->input('password');
    echo $form->end('Login');
    ?>
  • cakebaker

    @Angeline: Well, flash() performs a redirect and so you don’t see the other validation errors. They are only shown if you stay on the same page.

    To avoid this problem I would use in the controller $this->Session->setFlash(‘Some error’); instead of flash(), and in the view you can then output this message with echo $session->flash().

    Hope that helps!

  • Angeline

    Well, If I use setFlash, I get this error:

    Please enter valid inputs
    Not Found

    Error: The requested address ‘/users/register’ was not found on this server.

    Because I do not have a separate ctp file for register or login. If the login or register functions fail, I remain in the same page, that is /main/index or I move to /main/home.

    So is there a way to bring those error messages in such conditions?

  • cakebaker

    @Angeline: Hm, isn’t it a “missing view” error? In that case use the render() method to display your view.

    Hope that helps!

  • Celso

    How validate with dd/mm/yyyy ???

  • cakebaker

    @Celso: Well, if it is not required that the input strictly follows the format “dd/mm/yyyy” then you can use the built-in “date” validation rule:

    var $validate = array('thefield' => array('rule' => array('date', 'dmy')));
    

    However, if the input must exactly follow your format, then you either have to use the “custom” validation rule with a regex or you have to write your own validation method.

    Hope that helps!

  • asmad

    please give coding validate category_id if category_id NULL or equal to zero ! where category_id is foreign key

  • cakebaker

    @asmad: I am not sure I understand you correctly, but if you want to validate whether the category_id is not null and greater than zero, than you could use a custom validation rule like:

    public function isint($value, $params = array()) {
        return ( preg_match( '/^\d+$/'  , $value['category_id']) == 1 && $value['category_id'] > 0);
    }

    Hope that helps!

  • pablo005

    Hi,

    I need to translate the error messages. I have this in my model:

    var $validate = array(
           'username' => array(
               'between' => array(
                  'rule' => array('between', 4, 10),
                  'last' => true
               ),
               'unique' => array(
                  'rule' => 'isUnique'
               )
          )
    );

    And this in my view:

    echo $form->input('username', array('error' => array('between' => _('Between 4 and 10.', true), 'unique' => _('Existing User', true)));
    

    But do not show these error messages. Shown are the error messages by default. But if I put only an error message then it is displayed.

    echo $form->input('username', array('error' => _('Between 4 and 10.', true)));
    

    What may be happening. Is it a bug?. Does anyone have the same
    problem?.

    Thanks

  • cakebaker

    @pablo: Your code looks good. What CakePHP version do you use?

  • pablo005

    Sorry, already found the problem.

    The problem is that within the rule ‘between’ was this:

    'between' => array (
        'rule' => array ( 'between', 4, 10),
        'allowEmpty' => false,
        'required' => false,
        'last' => true
    )

    In theory when the field is left empty should display the error message defined for ‘between’. But no, it shows the default error message. But if completed less than 4 characters then it displays the correct error message. The solution is to create another rule only for ‘allowEmpty’ with an error message.

    Thank you very much for your help.

  • cakebaker

    @pablo: Good to hear you could resolve your issue :)

  • Anonymous

    Nice post and also helpful for new learner.

    Thanks.

  • PHP OOPS Blog » 10 CakePHP Tutorials You Should Read and Become and expert in CakePHP Programming

    […] Validation with CakePHP […]

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License