Multiple rules of validation per field in CakePHP 1.2

By Mariano Iglesias aka "mariano"
There are many great improvements coming with CakePHP 1.2. On this article we'll take a look at multiple rules of validation per field, and how easy it is to use them on our 1.2 models.
On its 1.1 release, CakePHP only allowed us to define one rule of validation per field. If we needed to specify more than one rule, we either had to use some handy extensions to CakePHP that are available, or achieve multiple validation by using the callback method beforeValidate() in our models. CakePHP 1.2 brings us an improved technique to specify multiple rules per field, and best of all, it's backwards compatible, so our existing rules will continue to work.

What better way to start that with an example, specifically the typical Article CRUD example. We start by creating a simple controller that provide us with an add form:

Controller Class:

Download code <?php 
class ArticlesController extends AppController {
    var 
$name 'Articles';
    var 
$helpers = array('Form');
    
    function 
add() {
        if (!empty(
$this->data)) {
            
// We don't do any real saving, we just validate the model
            
            
if ($this->Article->create($this->data) && $this->Article->validates()) {
                
$this->set('valid'true);
            }
        }
    }
}
?>


and its matching articles/add.ctp view:

View Template:

Download code
<?php echo $form->create('Article'); ?>
    <?php echo $form->input('title'); ?>
    <?php echo $form->input('body'); ?>
<?php 
echo $form->end('Add Article'); ?>


Let's take the classic Article model, and add only one validation to some fields:

Model Class:

Download code <?php 
class Article extends AppModel {
    var 
$name 'Article';
    var 
$validate = array(
        
'title' => VALID_NOT_EMPTY,
        
'body' => VALID_NOT_EMPTY
    
);
}
?>


Nothing new here, except that as you can see we're not defining the error messages. We are going to do so in the view. So we go back and change the add.ctp view so it now looks like this:

View Template:

Download code
<?php echo $form->create('Article'); ?>
    <?php echo $form->input('title', array('error' => 'Please specify a valid title')); ?>
    <?php echo $form->input('body', array('error' => 'Please specify a valid body')); ?>
<?php 
echo $form->end('Add Article'); ?>


If we try the add action and submit the form with both fields empty, we'll get our newly defined error messages. What if we also wanted to add another rule of validation for the title field, where we should check that the title is at the most 100 characters long? Let's review the model, and now change it to look like this:

Model Class:

Download code <?php 
class Article extends AppModel {
    var 
$name 'Article';
    var 
$validate = array(
        
'title' => array(
            
VALID_NOT_EMPTY,
            array(
                
'rule' => array('maxLength'100)
            )
        ),
        
'body' => VALID_NOT_EMPTY
    
);
}
?>


We have just converted the 'title' field to consist of an array of rules. The first rule is a CakePHP provided regular expression that checks for a specified value, and the second rule is a method existing in CakePHP's built in Validation class called 'maxLength'. Since this method takes an extra parameter (the number of maximum characters the field can contain), we add it on an array.

So we have now two conditions that can generate an error for the field title: an empty value, or a value with more than 100 characters. How do we differentiate the error messages on the view? Let's change the view so it now looks like:

View Template:

Download code
<?php echo $form->create('Article'); ?>
    <?php echo $form->input('title', array('error' => array(
        
=> 'Please specify a valid title',
        
=> 'The title must have no more than 100 characters'
    
))); ?>
    <?php echo $form->input('body', array('error' => 'Please specify a valid body')); ?>
<?php 
echo $form->end('Add Article'); ?>


As you can see we're setting an error message per rule. 0 corresponds to the first rule, 1 to the second, and so on. If we wanted more flexibility (such as having the option to change the order of the rules and still have the same error message assignment) and needed more readability, we can then use the string index approach. Change the model so it now looks like:

Model Class:

Download code <?php 
class Article extends AppModel {
    var 
$name 'Article';
    var 
$validate = array(
        
'title' => array(
            
'required' => VALID_NOT_EMPTY,
            
'length' => array( 'rule' => array('maxLength'100) )
        ),
        
'body' => VALID_NOT_EMPTY
    
);
}
?>


and change the view so it now looks like:

View Template:

Download code
<?php echo $form->create('Article'); ?>
    <?php echo $form->input('title', array('error' => array(
        
'required' => 'Please specify a valid title',
        
'length' => 'The title must have no more than 100 characters'
    
))); ?>
    <?php echo $form->input('body', array('error' => 'Please specify a valid body')); ?>
<?php 
echo $form->end('Add Article'); ?>


Custom Validation



What about custom validation? What if we needed more rules than those provided by CakePHP's Validation class? Don't sweat, it comes very easy! All you need to do is set up your own validation functions on either your model or your AppModel class (if you wish to share them across your models.) For example, we're going to add a new validation rule to allow us to specify a minimum and a maximum length for our title. I know, what's the point when we have both minLength and maxLength in CakePHP's Validation class? Well, to show how it can be done :)

Edit the model and change it so it now looks like this:

Model Class:

Download code <?php 
class Article extends AppModel {
    var 
$name 'Article';
    var 
$validate = array(
        
'title' => array(
            
'required' => VALID_NOT_EMPTY,
            
'length' => array( 'rule' => 'validateLength''min' => 5'max' => 100 )
        ),
        
'body' => VALID_NOT_EMPTY
    
);
    
    function 
validateLength($value$params = array()) {
        
$valid false;
        
        
$params am(array(
            
'min' => null,
            
'max' => null,
        ), 
$params);
        
        if (empty(
$params['min']) || empty($params['max'])) {
            
$valid false;
        } else if (
strlen($value) >= $params['min'] && strlen($value) <= $params['max']) {
            
$valid true;
        }
        
        return 
$valid;
    }
}
?>


A custom validation function takes one mandatory first parameter: the value to validate, and must return a boolean value of true when the value validates, or false when it doesn't. Extra parameters will be sent to the validation function as an array through its second parameter, and the values in the array are those values specified in the validation rule that do not correspond to CakePHP's internal values (such as rule or allowEmpty.)

Comments 369

CakePHP team comments Author comments

Question

1 Problem with multiple validation criteria

Hello, I'm new to Cake 1.2.x and I've tried the above tutorial on Users table model:
username, password, email, first_name, last_name.

I've tried the example and $form->input('username', 'error' => 'error msg') works if I put only one validation rule.

But as I've tried:

var $validate = array(
'username' => array(
VALID_NOT_EMPTY,
array(
'rule' => array('maxLength', 40)
)
),
'password' => VALID_NOT_EMPTY,
'email' => VALID_EMAIL
);

Controler code:

function register()
{
if (!empty($this->data))
{
if ($this->User->create($this->data) && $this->User->validates()) {
$this->set('valid', true);
}
}
}

And view:

<?php echo $form->input('username',
array( 'error' => array(
0 => 'Username field must be filled',
1 => 'The title must have no more than 40 characters',
))); ?>

If I submit a form with no username value there is no error msg. If I input any value into it I get 'Array'.
Could anybody tell me what am I doing wrong in this one?
posted Thu, May 17th 2007, 16:40 by Michal

Comment

2 Problem with multiple validation criteria

@Michal: as I asked you on the google group confirm that you have CakePHP 1.2 latest SVN version.
posted Thu, May 17th 2007, 18:10 by Mariano Iglesias

Comment

3 Thank You

This article was very well done and informative. Thanks!
posted Fri, May 18th 2007, 12:16 by John Hanauer

Comment

4 Order Matters

When writing validation rules, the order they are written matters:

var $validate = array (
'email' => array (
'valid' => array(
'rule' => array('email')
),
'required' => array (
'rule' => array("minLength", 1)
)
)
);
works ok, but

var $validate = array (
'email' => array (
'required' => array (
'rule' => array("minLength", 1)
),
'valid' => array(
'rule' => array('email')
)
)
);

Generates 'valid' when left blank and when something different from an email. I'm not sure if this is the same behavior on cake 1.1
posted Sat, May 19th 2007, 21:48 by Joaquin Windmuller

Comment

5 Error string in the validation rule

I found that instead of declaring the error strings in the forms all the time (as you may need to type in the same thing over and over again if you validate the Model in multiple forms), you can easily do it in the validation array in the model.

So, the validation array simply becomes something like

var $validate = array( 'column_name' =>
 array('ruleIndexName' => 'numeric', 
'message' =>'*Valid characters: numbers only please')); 


That way, in all your forms, you do not need to specify the error message, as the message in the Model's validation array is passed to the form and displayed.
posted Wed, May 23rd 2007, 15:33 by Ian Farr

Comment

6 Error string in the validation rule

Hello Ian,

The possibility of declaring the error string in the model itself instead of repeating it on the views is really attractive, could you please elaborate on how to use this?
posted Thu, Jun 28th 2007, 12:08 by Gorka Lopez de Torre

Comment

7 Custom Validation Function

Hi,

Is it possible to get the field-name in a custom vaildation function without giving it as a param in the 'rule'-array?

Bye,
Alex
posted Fri, Jun 29th 2007, 11:22 by Alexander Wegener

Comment

8 In depth look at defining the error string in the validation array

Hello Gorka,

Sure, I will describe how I am using it. (I will copy/paste my actual code - so this is working code - for me at least).

I have a 'User' model which queries my 'users' mysql table. I use the method described above for a couple of things when interacting with the 'users' table.

validating data for:
  1. adding new users through a form in an admin section
  2. logons through a public logon form/page
  3. new user signup through a public sing-up page


I started looking around for an easier way so that I wouldn't have to copy and paste error strings all over the place in my views. It became a hassle when I wanted to change the wording in the "error message", I had to remember which views where were responsible for handing out this message, compound that with all the validations in all the other sections of my web apps, and it quickly became a "finding a needle in a haystack" situation - i thought, there must be a better way. And there was/is :)

I found this out by reviewing/studying the model/controller/validation/form sections of the 1.2 API - http://api.cakephp.org/ is my best friend these days :)

First, as described above, add the error string value to the 'message' attribute when defining the validation array. An example of my validation array that I use for the 'User' model is:


<?php
var $validate = array(
                            
'username' => array( 'rule' => 'alphaNumeric''message' =>'Valid characters: letters and numbers only'),
                            
'firstname' => array( 'rule' => 'alphaNumeric''message' =>'Valid characters: letters and numbers only'),
                            
'lastname' => array( 'rule' => 'alphaNumeric''message' =>'Valid characters: letters and numbers only'),
                            
'displayname' => array( 'rule' => array('custom''/^[\da-zA-Z _]+$/'), 'message' =>'Valid characters: letters,numbers,spaces,underscores only'),
                            
'password' => array( 'rule' => array('custom''/^.{6,}$/'), 'message' =>'Passwords must be at least 6 characters in length'),
                            
'email' => array( 'rule' => 'email''message' =>'E-mail must be a valid e-mail address')
                        );
?>


This centralizes the error strings in one place, I can change them to whatever I want until my heart is content, and they automatically update in all my views.

The controller code is very basic, and if I take my logon page as an example, all it does is verify that the user is not already logged in, manually validate the data to see if the entered data is OK (validates using the validation array in the model), If the data entered validates, see if the username/password entered are good, and do the appropriate action. (Note, I use a custom component that I called SiteAuth, which actually handles the authentication check, among other things, like figure out the users history on my site, and redirect the user to the last page that he was visiting (the $this->SiteAuth->getRegexURL('/users/login') line). Also ignore the $this->menu stuff, again, a custom component. It's just to set my menu entries for the site):


<?php
function login() {
        
//only allow access to the page if user has not logged in before
        
if ( $this->Session->read('userid') != ) {
            
$this->Session->setFlash('You are already logged in. <br />You do not need to access the logon page again.');
            
$this->redirect('/');
            exit;
        }

        
//set the view error var to false. Used to display bad user/pass error message
        
$this->set('error'false);

        
//if we have data
        
if( ! empty($this->data) ) {
            
//pass the data to the model so we can manually validate things
            
$this->User->data $this->data;

            
//validate the data, and try to authenticate the user
            
if ( $this->User->validates() ) {
                if ( 
$this->SiteAuth->login($this->data['User']['username'], $this->data['User']['password']) ) {
                    
$this->Session->setFlash('You have been logged in successfully.');
                    
$this->redirect($this->SiteAuth->getRegexURL('/users/login'));
                    exit;
                } else {
                    
$this->set('error'true);
                }
            }
        }
        
//set the page menu options
        
$this->Menu->setMenu('botMenu');
    }
?>


The main point here is the manual validation method. We first pass the form data the user entered to the Model:


$this->User->data = $this->data;


Next, we manually see if the data validates:

if ( $this->User->validates() ) {}


Once we do this, if the data entered does not validate, the error messages in the model's validation array are populated.

As far as the view is concerned, again, it is very basic. Make sure you use the form helper in your controller:


var $helpers = array('Html', 'Form');


Then in your view, just use the form helper for displaying your form fields:


<div id="loginbox">
    <div id="loginHeader">
        <span>Login</span>
    </div>
    <div id="loginBody">
    <?php if ($error){?>
        <p class="error">The login credentials you supplied could not be recognized. Please try again.</p>
    <?php ?>
        <?php echo $form->create('User', array('action' => 'login'));?>
        <table border="0">
        <tr>
            <td class="right">
                <?php echo $form->label('username''Username: ');?>
            </td>
            <td>
                <?php echo $form->input('User.username', array('type'=>'text''label'=>false'div'=>false));?>
            </td>
        
        </tr>
        <tr>
            <td class="right">
                <?php echo $form->label('password');?>
            </td>
            <td>
                <?php echo $form->input('User.password', array('type'=>'password''label'=>false'div'=>false));?>
            </td>
        </tr>
        </table>
        <?php echo $form->submit('Login', array('class'=>'submit'));?>
        <?php echo $form->end(); ?>
    </div>
</div>


The form helper will automatically look for and display the error string in the 'message' attribute of the Model's validation array for any form input/checkbox/select... item that has an entry in it - that's it.

I use this all over the place - for everything basically. It also works if you don't manually validate things (like during a save operation, when the model automatically validates things before the save), so again, no magic in the view has to be done. The main thing is to make sure that the validation is done in the controller, so that those 'message' attributes will be populated so that the form helper can see them in the views, and display them for you.

I hope this makes more sense/helps some - let me know if I need to describe anything further.
posted Mon, Jul 2nd 2007, 09:57 by Ian Farr

Comment

9 Great explanation

That was a great explanation, Ian, thank you very much :)

The is some invaluable info, centralizing all validation error messages on the model can save hours of tedious work and hundreds of typos. In combination with i18n and l10n functionality this definitely rocks!
posted Thu, Jul 5th 2007, 09:45 by Gorka Lopez de Torre

Question

10 Validating datetime

Very useful explanation! Been wondering how to do that for a long time now. Thank you! =)

Should validations that check if a record exists on the database be on the model also? Like on sign up, the username is checked if it's unique. If so, how can I do it on the model?

Also, how can I validate data from $form->datetime fields? I can't seem to make the 'date' rule work. Should I use cleanUpFields() before validation or is it totally unrelated? =)
posted Thu, Jul 19th 2007, 06:12 by jahypee

Comment

11 Another little trick.

I discovered a way to output different messages without the need to know their names in the view.
My code gets a bit flattened and unreadable here.

public $validate = array(
'username' => array(
'short' => array(
'rule' => array('minLength',3),
'message' => 'At least 3 chars'),
'long' => array(
'rule' => array('maxLength',30),
'message' => 'Max 30 chars')
));

A validation rule like that will output one of the two messages even if you haven't specified their names in the view. Their names is irrelevant here. Plz. name them banana and Tarzan if you like.
posted Wed, Aug 8th 2007, 03:34 by Kristoffer Darj

Comment

12 Create function

I discovered a way to output different messages without the need to know their names in the view.
My code gets a bit flattened and unreadable here.

public $validate = array(
'username' => array(
'short' => array(
'rule' => array('minLength',3),
'message' => 'At least 3 chars'),
'long' => array(
'rule' => array('maxLength',30),
'message' => 'Max 30 chars')
));

A validation rule like that will output one of the two messages even if you haven't specified their names in the view. Their names is irrelevant here. Plz. name them banana and Tarzan if you like.
Nice Artical But may you define the purose of create fuction
posted Thu, Aug 9th 2007, 11:08 by Nabi Bux

Comment

13 About defining error messages in the model

Error messages - explaining the error to the front user - are really presentational, and have little place in the model (or controller) in proper MVC.

The error itself is semantic, and so a well chosen string index is definitely advisable - but if one were to define all their messages in the model - they would lose flexibility in the views because a certain amount of presentation is dictated by the model directly.

It sounds like I'm splitting hairs - but Imagine that your site becomes suddenly successful - and you need to have pages in multiple languages - Then its easy to use routing and multiple views to add flexibility to your site -- so long as everything was defined in the views - otherwise your going to have to start re-writing models and/or controllers.

And if that is too much work - or results in to much hunting around - you can always use layouts, constants, resource files - etc to consolidate your error messages somewhere in the proper domain.

Proper MVC shouldn't only make _development_ fast and straight forward, but _maintenance_ as well.

BUT... defining those messages in the models certainly makes things easier on a lot of small to medium sites - and nothing (including MVC) is a golden hammer - so use your judgment.

posted Tue, Sep 18th 2007, 11:43 by Adam

Comment

14 Very good

thank you very much
posted Tue, Nov 6th 2007, 01:14 by xmihu

Comment

15 validateUnique method

Good article, thanks very much! Helped me develop this very useful function to ensure you don't have duplicates in your database. Just pop it into your AppModel class like below.

Model Class:

<?php 

class AppModel extends Model {

    function 
validateUnique($value$params = array()) {
        if (!empty(
$this->id)) {
            
$conditions = array($this->primaryKey => '!= '.$this->id$params['field'] => $value);
        } else {
            
$conditions = array($params['field'] => $value);
        }
        return !
$this->field($this->primaryKey$conditions);
    }
}

?>



And then use it like so:

Model Class:

<?php 

class User extends AppModel() {

    var 
$name 'User';
    var 
$validate = array(
        
'email' => array(
            array(
                
'rule' => 'validateUnique',
                
'field' => 'email',
                
'message' => 'This email address is already in use'
            
)
        )
    );

}

?>


Please note you have to specify the field name again in the parameters, or until the following enhancement ticket is implemented.

https://trac.cakephp.org/ticket/2766


posted Wed, Nov 7th 2007, 04:31 by Adam

Question

16 Variables in error messages

Does anyone have any elegant ideas for using variables in error messages.

I'm building an app where one field must be in the form validTableName.validFieldName.

I using a custom validation method that in turn uses 3 validation rules, and I want a specific error message for any rule that fails...

In the MyModel::$validate looks like this...

    var $validate = array(
        "table_field"    => array(
            "rule"        => "validateTableField",
            "message"    => "This is not what I want"
        )

    );


And here is the custom validation function, MyModel::validateTableField()....


    function validateTableField(){
        
        $table_field = $this->data["OptionField"]["table_field"];
        
        if (!Validation::custom($table_field, VALID_SQL_TABLE_FIELD)){
            
            // Setting the message here doesn't work. 'This is not what I want' is displayed instead.
            $this->validate["table_field"]["message"] = "This field must be of the form table.field";
            
            return false;
        }
        else {
            list($table, $field) = explode(".", $table_field);
            
            if (!$this->tableExists($table)){
                
                // Setting the message here doesn't work. 'This is not what I want' is displayed instead.
                $this->validate["table_field"]["message"] = "This table '".$table."' does not exist.";
                return false;
            }
            else if (!$this->fieldExists($table, $field){
                
                // Setting the message here doesn't work. 'This is not what I want' is displayed instead.
                $this->validate["table_field"]["message"] = "The field '".$field."' is not a part of the '".$table."' table";
                return false;
            }
        }
        
        return true;
    }


... and note that:
1) AppModel::tableExists($table) returns true only if $table is a valid table in the database

2) AppModel::fieldExists($table, $field) returns true only if $field is a valid field in $table

3) VALID_SQL_TABLE_FIELD is a constant regular expression that validates SQL table.field syntax.

thanks

-a-
posted Mon, Feb 11th 2008, 12:24 by afmyers

Comment

17 Re Variables in error messages


Does anyone have any elegant ideas for using variables in error messages.

I'm building an app where one field must be in the form validTableName.validFieldName.

I using a custom validation method that in turn uses 3 validation rules, and I want a specific error message for any rule that fails...

Try this:

First, extend your AppModel like so:

Model Class:

<?php 
// Setup a unique constant to ignore an error message
if (!defined('SILENT_ERROR_MESSAGE')) {
  
define('SILENT_ERROR_MESSAGE''{D117A338-3DBB-4faa-8FEA-6024F2B5F41C}');
}

class 
AppModel extends Model {
  function 
invalidate($field$data null) {
    if (
$data != SILENT_ERROR_MESSAGE) {
      if (
is_string($data) && strpos($data'{{') !== false) {
        
$this->_currentInvalidFieldName $field;
        
$data preg_replace_callback(
          
'/\{\{(data|field)(?:\[(?:([-a-z0-9_]+)\.)?([-a-z0-9_]+)\])?\}\}/i',
          array(
$this'_formatErrorStringCallback'),
          
$data
        
);
        
$this->_currentInvalidFieldName null;
      }
      return 
parent::invalidate($field$data);
    }
  }
  
  function 
_formatErrorStringCallback($bits) {
    if (
$bits[1] == 'field') {
      if (empty(
$bits[2]) && empty($bits[3])) {
        return 
Inflector::humanize($this->_currentInvalidFieldName);
      }
    }
    else {
      if (empty(
$bits[3])) {
        return 
$this->data[$this->alias][$this->_currentInvalidFieldName];
      }
      else {
        if (empty(
$bits[2])) {
          return 
$this->data[$this->alias][$bits[3]];
        }
        else {
          return 
$this->data[$bits[2]][$bits[3]];
        }
      }
    }
    
// In case of invalid syntax, return the whole thing so
    // the user sees the mistake he's made
    
return $bits[0];
  }
}
?>


Now define your validation so that the 'message' is set to the constant, and the validation function invalidates the field manually:

Model Class:

<?php 
class Product extends AppModel {
  var 
$name "Product";

  var 
$belongsTo = array("Category");

  var 
$validate = array(
    
'code' => array(
      array(
       
'rule' => 'alphaNumeric',
       
'message' => '"{{data}}" is not a valid {{field}}',
       
'last' => true
      
),
      array(
        
'rule' => 'uniqueCodeForCategory',
        
'message' => SILENT_ERROR_MESSAGE
      
)
    )
  );

  function 
uniqueCodeForCategory($data) {
    if (!
$this->isUnique(array('code''category_id'), false)) {
      
$msg 'The Product Code "{{data}}" is already allocated for ';
      if (
$catTitle $this->Category->field($this->Category->displayField, array('Category.id' => $this->data['Product']['category_id']))) {
        
$msg .= 'the "'.$catTitle .'" category';
      }
      else {
        
$msg .= 'this category';
      }
      
$this->invalidate('code'$msg);
      
      return 
false;
    }
    return 
true;
  }
}
?>


Works for me!
posted Tue, Feb 12th 2008, 05:10 by JP Mortiboys

Question

18 multiple error messages

Thanks for this great guide.

I have one further question:
How can I display multiple error-messages in my views. For example a "username" is only valid if it contains only [a-z0-9_-] and if the length is between 3 and 10 letters..., so i would use something like this:

Model Class:

<?php     var $validate = array(
        
'name' => array(
            
'length' => array('rule'=> array('between'510), 'message'=>'Too short or too long'),
            
'valid' => array('rule'=>'#^[a-z0-9_-]*$#i''message'=>'Not valid...'),
        ));
?>


Now it is possible, that validation fails for both rules: "$a" is too short and has a wrong sign. But with <?php echo $form->error('User.name');?> i only get one error-message.

Any hints?
posted Sat, Mar 22nd 2008, 15:48 by Andreas

Login to Submit a Comment