Rails-like Data Validation

By Adeel Khan (chess64)
Now you can validate your data (almost) like in rails (see http://rails.rubyonrails.com/classes/ActiveRecord/Validations/ClassMethods.html)!

Note: this has not been fully tested yet, so please submit bugs and suggestions...

Example Usage



Functions and parameters are almost exactly the same as the rails functions and parameters. As of right now, there is no validates_agreement_of() or validates_confirmation_of().

In your users model, you might have:

Model Class:

Download code <?php 
class User extends AppModel {
    function 
validates() {
        
$this->setAction();
        
#always validate presence of username
        
$this->validates_presence_of('username');
        
#validate uniqueness of username when creating a new user
        
$this->validates_uniqueness_of('username',array('on'=>'create'));
        
#validate length of username (minimum)
        
$this->validates_length_of('username',array('min'=>3));
        
#validate length of username (maximum)
        
$this->validates_length_of('username',array('max'=>50));
        
#validate presence of password
        
$this->validates_presence_of('password');
        
#validate presence of email
        
$this->validates_presence_of('email');
        
#validate uniqueness of email when creating a new user
        
$this->validates_uniqueness_of('email',array('on'=>'create'));
        
#validate format of email
        
$this->validates_format_of('email',VALID_EMAIL);

        
#if there were errors, return false
        
$errors $this->invalidFields();
        return (
count($errors) == 0);
    }
}
?>


The Code



Place the following code in app/app_model.php:

Model Class:

Download code <?php 
class AppModel extends Model {
        var 
$action;
        function 
setAction() {
                if (empty(
$this->id)) {
                        
$this->action 'create';
                } else {
                        
$this->action 'update';
                }
                return 
true;
        }
        function 
invalidFields($data = array()) {
                if ( empty(
$data) ) {
                        
$data $this->data;
                }
                if ( !
$this->beforeValidate() ) {
                        return 
$this->validationErrors;
                }
                return 
$this->validationErrors;
        }

        function 
validates_presence_of($fieldName,$options=array()) {
                if ( !isset(
$options['message']) ) {
                        
$options['message'] = Inflector::humanize($fieldName) . ' is required.';
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if ( empty(
$this->data[$this->name][$fieldName]) ) {
                                
$this->validationErrors[$fieldName] = $options['message'];
                        }
                }
        }
        function 
validates_exclusion_of($fieldName,$options=array()) {
                
$fieldValue $this->data[$this->name][$fieldName];
                if ( @
$options['allow_null'] && ($fieldValue == null) ) {
                        return 
true;
                }
                if ( !isset(
$options['in']) ) {
                        
$options['in'] = array();
                }
                if ( !isset(
$options['message']) ) {
                        
$options['message'] = Inflector::humanize($fieldName) . ' should be one of the following: ' join(',',$options['in']) . '.';
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if ( 
in_array($fieldValue,$options['in']) ) {
                                
$this->validationErrors[$fieldName] = $options['message'];
                        }
                }
        }
        function 
validates_format_of($fieldName,$options=array()) {
                
$fieldValue $this->data[$this->name][$fieldName]; 
                if ( @
$options['allow_null'] && ($fieldValue == null) ) {
                        return 
true;
                }
                if ( !isset(
$options['message']) ) {
                        
$options['message'] = Inflector::humanize($fieldName) . ' has an invalid format.';
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( !isset(
$options['with']) ) {
                        
$options['with'] = '//';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if ( !
preg_match($options['with'],$fieldValue) ) {
                                
$this->validationErrors[$fieldName] = $options['message'];
                        }
                }
        }
        function 
validates_inclusion_of($fieldName,$options=array()) {
                
$fieldValue $this->data[$this->name][$fieldName];
                if ( @
$options['allow_null'] && ($fieldValue == null) ) {
                        return 
true;
                }
                if ( !isset(
$options['in']) ) {
                        
$options['in'] = array();
                }
                if ( !isset(
$options['message']) ) {
                        
$options['message'] = Inflector::humanize($fieldName) . ' should be one of the following: ' join(',',$options['in']) . '.';
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if ( !
in_array($fieldValue,$options['in']) ) {
                                
$this->validationErrors[$fieldName] = $options['message'];
                        }
                }
        }
        function 
validates_length_of($fieldName,$options=array()) {
                
$fieldValue $this->data[$this->name][$fieldName];
                if ( @
$options['allow_null'] && ($fieldValue == null) ) {
                        return 
true;
                }
                if ( !isset(
$options['message']) ) {
                        
$options['message'] = Inflector::humanize($fieldName) . ' has the wrong length.';
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if ( isset(
$options['max']) ) {
                                if ( 
strlen($fieldValue) > $options['max'] ) {
                                        
$this->validationErrors[$fieldName] = $options['message'];
                                }
                        } elseif ( isset(
$options['min']) ) {
                                if ( 
strlen($fieldValue) < $options['min'] ) {
                                        
$this->validationErrors[$fieldName] = $options['message'];
                                }
                        } elseif ( isset(
$options['in']) ) {
                                if ( !
in_array($fieldValue,$options['in']) ) {
                                        
$this->validationErrors[$fieldName] = $options['message'];
                                }
                        } elseif ( isset(
$options['is']) ) {
                                if ( 
$fieldValue != $options['is'] ) {
                                        
$this->validationErrors[$fieldName] = $options['message'];
                                }
                        }
                }
        }
        function 
validates_numericality_of($fieldName,$options=array()) {
                
$fieldValue $this->data[$this->name][$fieldName];
                if ( @
$options['allow_null'] && ($fieldValue == null) ) {
                        return 
true;
                }
                if ( !isset(
$options['only_integer']) ) {
                        
$options['only_integer'] = false;
                }
                if ( !isset(
$options['message']) ) {
                        if ( 
$options['only_integer'] ) {
                                
$options['message'] = Inflector::humanize($fieldName) . ' should be an integer.';
                        } else {
                                
$options['message'] = Inflector::humanize($fieldName) . ' should be a number.';
                        }
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if (
                                !
is_numeric($fieldValue)
                                || ( 
$options['only_integer'] && !is_int($fieldValue) )
                        ) {
                                
$this->validationErrors[$fieldName] = $options['message'];
                        }
                }
        }
        function 
validates_uniqueness_of($fieldName,$options=array()) {
                
$fieldValue $this->data[$this->name][$fieldName];
                if ( @
$options['allow_null'] && ($fieldValue == null) ) {
                        return 
true;
                }
                if ( !isset(
$options['message']) ) {
                        
$options['message'] = Inflector::humanize($fieldName) . ' is already taken.';
                }
                if ( !isset(
$options['on']) ) {
                        
$options['on'] = 'save';
                }
                if ( ((
$options['on'] == 'save') || ($options['on'] == $this->action)) ) {
                        if ( 
$this->hasAny(array("{$this->name}.{$fieldName}" => $fieldValue)) ) {
                                
$this->validationErrors[$fieldName] = $options['message'];
                        }
                }
        }
}
?>


Place the following code in app/views/helpers/error.php:

Helper Class:

Download code <?php 
class ErrorHelper extends Helper {
    function 
forField($field) {
        list(
$model,$fieldName) = explode('/',$field);
        if ( isset(
$this->validationErrors($model,$fieldName) ) {
            return 
'<div class="error">' $this->validationErrors[$model][$fieldName] . '</div>';
        } else {
            return 
'';
        }
    }
}
?>


In your controller, put

Controller Class:

Download code <?php 
class BananasController extends AppController {
    
/* ... */
    
var $helpers = array('Html','Error');
    
/* ... */
}
?>


In your views, put

View Template:

Download code
<label for="quantity">Quantity:
    <input type="text" name="data[Banana][quantity]" id="quantity" />
</label><?php print $error->forField('Banana/quantity'); ?>


In your model, put:

Model Class:

Download code <?php 
class Banana extends AppModel {
    function 
validates() {
        
#make sure quantity is an integer
        
$this->validates_numericality_of('quantity',array('only_integer'=>true));

        
#if there were errors, return false
        
$errors $this->invalidFields();
        return (
count($errors) == 0);
    }
}
?>


Please comment!

 

Comments 360

CakePHP Team Comments Author Comments
 

Bug

1 Great feature

Thanx chess64 for your Rails-like Data Validation
I had to correct the Error Helper, after a copy/paste from screen...
Maybe that was because i used 1.1.15.XXX version.

<?php 
class ErrorHelper extends Helper {
    function 
forField($field) {
        list(
$model,$fieldName) = explode('/',$field);
        if ( isset(
$this->validationErrors[$model][$fieldName]) ) ) {
            return 
'<div class="error">' $this->validationErrors[$model][$fieldName] . '</div>';
        } else {
            return 
'';
        }
    }
}
?> 
Posted Jun 5, 2007 by Lionel
 

Question

2 please ignore

my error, sorry, nevermind.

This works great !

PS: It would be good if one could delete one's comments, at least if they are the last reply.
Posted Jun 18, 2007 by Jens
 

Comment

3 Good Job

I like it. Clean and simple to use. Nice job!
Posted Jul 11, 2007 by Steve Oliveira
 

Question

4 how does this work

how can i execute the validates() function in my model? it seems that cake isn't executing that function.
Posted Jul 19, 2007 by john diegor
 

Comment

5 im using cakephp version

btw, im using cakephp version 1.1.16
Posted Jul 19, 2007 by john diegor
 

Comment

6 please help

Im trying to use this rails-like-validation but i have problem with the helper.

Helper Class:

<?php 
class ErrorHelper extends Helper {
    function 
forField($field) {
        list(
$model,$fieldName) = explode('/',$field);
        if ( isset(
$this->validationErrors[$model][$fieldName]) ) ) {
            return 
'<div class="error">' $this->validationErrors[$model][$fieldName] . '</div>';
        } else {
            return 
'';
        }
    }
}
?>


the statement $this->validationErrors[$model][$fieldName] does not returning any result. That's why the error message is not displaying.

can anyone help me? i switch back to version 1.1.15.XXXX. would that be an issue?
Posted Jul 22, 2007 by john diegor
 

Comment

7 One little mistake

in the first paragraph when you check e-mail
$this->validates_format_of('email',array('with'=>VALID_EMAIL));
instead
$this->validates_format_of('email',array(VALID_EMAIL);
this is a small error.
Thank you for write this validation, it's very useful

Posted Jul 23, 2007 by Milos
 

Comment

8 validates confirmation of function


Hi Chess64,

first of all, I'd like to say thanks for submitting this article. After coding in Rails for quite sometime, I got used to certain things there and coding in other ways seems a bit difficult. So again, thanks.

I needed a function that worked like validates_confirmation_of. I made one based on the templates of the functions in your code. The function looks as follows:



validates_confirmation_of function



    /**
     * function to run a validates_confirmation_of on CakePHP model. This is an addition to
     * rails-like-validations for CakePHP. Originally inpired by Adeel Khan. More details
     * about setting it up here: http://bakery.cakephp.org/articles/view/rails-like-data-validation
     *
     * @author Jean-Paul Hounkanrin
     * @version 21-October-2007, 8:55 Pm.
     */
    public function validates_confirmation_of($fieldName, $options=array()) {
        $varToConfirm = $fieldName . "_confirmation";
        $varToConfirmValue = null;
        $fieldValue = $this->data[$this->name][$fieldName];
        
        if(!isset($options['on'])) {
            $options['on'] = 'save';
        }
            
        if(isset($this->data[$this->name][$varToConfirm])) {
            $varToConfirmValue = $this->data[$this->name][$varToConfirm];        
        }
        
        if(!isset($options['message'])) {
            $options['message'] = Inflector::humanize($varToConfirm) . ' and ' . Inflector::humanize($fieldName) . ' are not equivalent.';
        }        

        if ((($options['on'] == 'save') || ($options['on'] == $this->action))) {
            if ($fieldValue !== $varToConfirmValue) {
                $this->validationErrors[$fieldName] = $options['message'];
            }
        }
    }



You might want to test the code above. I've written a small test class for it. The code is as follows:



Test validates_confirmation_of function



<?php
require_once('inflector.php');

$test = new TestConfirmValidation();
$test->testValidatesConfirmationOf();

class 
TestConfirmValidation {

    var 
$data null;
    var 
$validationErrors null;
    var 
$name null;

    
/** test class constructor */
    
public function __construct() {
        
$this->name 'TestConfirmValidation';
        
        
$this->validationErrors = array(
            
'password' => 'valid',
            
'other_var' => 'valid' // this should contain error msg after validation call
        
);
        
        
$this->data = array(
            
'TestConfirmValidation' => array(
                
// supposed to pass test
                
'password' => 'c0nfirm_pa55w0rd',
                
'password_confirmation' => 'c0nfirm_pa55w0rd',
            
                
// supposed to fail test
                
'other_var' => 'hell0',
                
'other_var_confirmation' => 'hello'
            
)
        );
    }

    
/** validates the validates_confirmation_of(...) function */
    
public function testValidatesConfirmationOf() {
        
$this->validates_confirmation_of('password');
        
$this->validates_confirmation_of('other_var');
        
        
// This should show that password passed the validation while other_var failed it.
        // As follows: Array ( [password] => valid [other_var] => Other Var Confirmation and Other Var are not equivalent. )
        
print_r($this->validationErrors);
    }

    
/**
     * function to run a validates_confirmation_of on CakePHP model. This is an addition to
     * rails-like-validations for CakePHP. Originally inpired by Adeel Khan. More details
     * about setting it up here: http://bakery.cakephp.org/articles/view/rails-like-data-validation
     *
     * @author Jean-Paul Hounkanrin
     * @version 21-October-2007, 8:55 Pm.
     */
    
public function validates_confirmation_of($fieldName$options=array()) {
        
$varToConfirm $fieldName "_confirmation";
        
$varToConfirmValue null;
        
$fieldValue $this->data[$this->name][$fieldName];
        
        if(!isset(
$options['on'])) {
            
$options['on'] = 'save';
        }
            
        if(isset(
$this->data[$this->name][$varToConfirm])) {
            
$varToConfirmValue $this->data[$this->name][$varToConfirm];        
        }
        
        if(!isset(
$options['message'])) {
            
$options['message'] = Inflector::humanize($varToConfirm) . ' and ' Inflector::humanize($fieldName) . ' are not equivalent.';
        }        

        if (((
$options['on'] == 'save') || ($options['on'] == $this->action))) {
            if (
$fieldValue !== $varToConfirmValue) {
                
$this->validationErrors[$fieldName] = $options['message'];
            }
        }
    }
}
?>


I hope this helps someone out there.



Cheers,

Jean-Paul H.
Posted Oct 21, 2007 by Jean Paul Hounkanrin
 

Question

9 Validates method

I was wondering if anyone has gotten this to worked with $this->Model->validates($data);

It works well with $this->Model->save($data) but for some reason it doesn't work with $this->Model->validates($data)
Posted Oct 23, 2007 by Steve Oliveira