Data validation is an important part of any application, as it helps to make sure that the data in a Model conforms to the business rules of the application. For example, you might want to make sure that passwords are at least eight characters long, or ensure that usernames are unique. Defining validation rules makes form handling much, much easier.
There are many different aspects to the validation process. What we’ll cover in this section is the model side of things. Essentially: what happens when you call the save() method of your model. For more information about how to handle the displaying of validation errors, check out Form.
The first step to data validation is creating the validation rules in the Model. To do that, use the Model::validate array in the Model definition, for example:
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array();
}
?>
In the example above, the $validate array is added to the User Model, but the array contains no validation rules. Assuming that the users table has login, password, email and born fields, the example below shows some simple validation rules that apply to those fields:
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'login' => 'alphaNumeric',
'email' => 'email',
'born' => 'date'
);
}
?>
This last example shows how validation rules can be added to model fields. For the login field, only letters and numbers will be accepted, the email should be valid, and born should be a valid date. Defining validation rules enables CakePHP’s automagic showing of error messages in forms if the data submitted does not follow the defined rules.
CakePHP has many validation rules and using them can be quite easy. Some of the built-in rules allow you to verify the formatting of emails, URLs, and credit card numbers – but we’ll cover these in detail later on.
Here is a more complex validation example that takes advantage of some of these built-in validation rules:
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'login' => array(
'alphaNumeric' => array(
'rule' => 'alphaNumeric',
'required' => true,
'message' => 'Alphabets and numbers only'
),
'between' => array(
'rule' => array('between', 5, 15),
'message' => 'Between 5 to 15 characters'
)
),
'password' => array(
'rule' => array('minLength', '8'),
'message' => 'Mimimum 8 characters long'
),
'email' => 'email',
'born' => array(
'rule' => 'date',
'message' => 'Enter a valid date',
'allowEmpty' => true
)
);
}
?>
Two validation rules are defined for login: it should contain letters and numbers only, and its length should be between 5 and 15. The password field should be a minimum of 8 characters long. The email should be a valid email address, and born should be a valid date. Also, notice how you can define specific error messages that CakePHP will use when these validation rules fail.
As the example above shows, a single field can have multiple validation rules. And if the built-in rules do not match your criteria, you can always add your own validation rules as required.
Now that you’ve seen the big picture on how validation works, let’s look at how these rules are defined in the model. There are three different ways that you can define validation rules: simple arrays, single rule per field, and multiple rules per field.
As the name suggests, this is the simplest way to define a validation rule. The general syntax for defining rules this way is:
var $validate = array('fieldName' => 'ruleName');
Where, ‘fieldName’ is the name of the field the rule is defined for, and ‘ruleName’ is a pre-defined rule name, such as ‘alphaNumeric’, ‘email’ or ‘isUnique’.
For example, to ensure that the user is giving a well formatted email address, you could use this rule:
var $validate = array('user_email' => 'email');
This definition technique allows for better control of how the validation rules work. But before we discuss that, let’s see the general usage pattern adding a rule for a single field:
var $validate = array(
'fieldName1' => array(
'rule' => 'ruleName', // or: array('ruleName', 'param1', 'param2' ...)
'required' => true,
'allowEmpty' => false,
'on' => 'create', // or: 'update'
'last' => false,
'message' => 'Your Error Message'
)
);
The ‘rule’ key is required. If you only set ‘required’ => true, the form validation will not function correctly. This is because ‘required’ is not actually a rule.
As you can see here, each field (only one field shown above) is associated with an array that contains six keys: ‘rule’, ‘required’, ‘allowEmpty’, ‘on’, ‘last’ and ‘message’. Let’s have a closer look at these keys.
The ‘rule’ key defines the validation method and takes either a single value or an array. The specified ‘rule’ may be the name of a method in your model, a method of the core Validation class, or a regular expression. For more information on the rules available by default, see Data Validation.
If the rule does not require any parameters, ‘rule’ can be a single value e.g.:
var $validate = array(
'login' => array(
'rule' => 'alphaNumeric'
)
);
If the rule requires some parameters (like the max, min or range), ‘rule’ should be an array:
var $validate = array(
'password' => array(
'rule' => array('minLength', 8)
)
);
Remember, the ‘rule’ key is required for array-based rule definitions.
This key should be assigned to a boolean value. If ‘required’ is true, the field must be present in the data array. For example, if the validation rule has been defined as follows:
var $validate = array(
'login' => array(
'rule' => 'alphaNumeric',
'required' => true
)
);
The data sent to the model’s save() method must contain data for the login field. If it doesn’t, validation will fail. The default value for this key is boolean false.
required => true
does not mean the same as the validation rule
notEmpty()
. required => true
indicates that the array key must
be present - it does not mean it must have a value. Therefore validation
will fail if the field is not present in the dataset, but may (depending
on the rule) succeed if the value submitted is empty (‘’).
If set to false
, the field value must be nonempty, where
“nonempty” is defined as !empty($value) || is_numeric($value)
. The
numeric check is so that CakePHP does the right thing when $value
is
zero.
The difference between required
and allowEmpty
can be confusing.
'required' => true
means that you cannot save the model without the
key for this field being present in $this->data
(the check is
performed with isset
); whereas, 'allowEmpty' => false
makes sure
that the current field value is nonempty, as described above.
The ‘on’ key can be set to either one of the following values: ‘update’ or ‘create’. This provides a mechanism that allows a certain rule to be applied either during the creation of a new record, or during update of a record.
If a rule has defined ‘on’ => ‘create’, the rule will only be enforced during the creation of a new record. Likewise, if it is defined as ‘on’ => ‘update’, it will only be enforced during the updating of a record.
The default value for ‘on’ is null. When ‘on’ is null, the rule will be enforced during both creation and update.
The ‘last’ key can be set to a boolean value equivalent to either true or false.
If set to true and the current rule does not validate, the current validation error message is then returned and all following rules for the current field will not be checked.
Practically, one should always use ‘last’ => true here since there is only one rule to be validated. For more relevant usages, see multiple-rules-per-field
The default value for ‘last’ is false.
The ‘message’ key allows you to define a custom validation error message for the rule:
var $validate = array(
'password' => array(
'rule' => array('minLength', 8),
'message' => 'Password must be at least 8 characters long'
)
);
The technique outlined above gives us much more flexibility than simple rules assignment, but there’s an extra step we can take in order to gain more fine-grained control of data validation. The next technique we’ll outline allows us to assign multiple validation rules per model field.
If you would like to assign multiple validation rules to a single field, this is basically how it should look:
var $validate = array(
'fieldName' => array(
'ruleName' => array(
'rule' => 'ruleName',
// extra keys like on, required, etc. go here...
),
'ruleName2' => array(
'rule' => 'ruleName2',
// extra keys like on, required, etc. go here...
)
)
);
As you can see, this is quite similar to what we did in the previous section. There, for each field we had only one array of validation parameters. In this case, each ‘fieldName’ consists of an array of rule indices. Each ‘ruleName’ contains a separate array of validation parameters.
This is better explained with a practical example:
var $validate = array(
'login' => array(
'loginRule-1' => array(
'rule' => 'alphaNumeric',
'message' => 'Only alphabets and numbers allowed',
'last' => true
),
'loginRule-2' => array(
'rule' => array('minLength', 8),
'message' => 'Minimum length of 8 characters'
)
)
);
The above example defines two rules for the login field: loginRule-1 and loginRule-2. As you can see, each rule is identified with an arbitrary name.
By default CakePHP tries to validate a field using all the validation
rules declared for it and returns the error message for the last failing
rule. But if the key last
is set to true
for a rule and it
fails, then the error message for that rule is returned and further
rules are not validated. So if you prefer to show the error message for
the first failing rule then set 'last' => true
for each rule.
When using multiple rules per field the ‘required’ and ‘allowEmpty’ keys need to be used only once in the first rule.
If you plan on using internationalized error messages, you may want to specify error messages in your view instead:
echo $form->input('login', array(
'label' => __('Login', true),
'error' => array(
'loginRule-1' => __('Only alphabets and numbers allowed', true),
'loginRule-2' => __('Minimum length of 8 characters', true)
)
)
);
The field is now fully internationalized, and you are able to remove the messages from the model. For more information on the __() function, see “Localization & Internationalization”
The Validation class in CakePHP contains many validation rules that can make model data validation much easier. This class contains many oft-used validation techniques you won’t need to write on your own. Below, you’ll find a complete list of all the rules, along with usage examples.
The data for the field must only contain letters and numbers.
var $validate = array(
'login' => array(
'rule' => 'alphaNumeric',
'message' => 'Usernames must only contain letters and numbers.'
)
);
The length of the data for the field must fall within the specified numeric range. Both minimum and maximum values must be supplied. Uses <= not < .
var $validate = array(
'password' => array(
'rule' => array('between', 5, 15),
'message' => 'Passwords must be between 5 and 15 characters long.'
)
);
The length of data is “the number of bytes in the string representation of the data”. Be careful that it may be larger than the number of characters when handling non-ASCII characters.
This rule is used to make sure that the field is left blank or only white space characters are present in its value. White space characters include space, tab, carriage return, and newline.
var $validate = array(
'id' => array(
'rule' => 'blank',
'on' => 'create'
)
);
The data for the field must be a boolean value. Valid values are true or false, integers 0 or 1 or strings ‘0’ or ‘1’.
var $validate = array(
'myCheckbox' => array(
'rule' => array('boolean'),
'message' => 'Incorrect value for myCheckbox'
)
);
This rule is used to check whether the data is a valid credit card number. It takes three parameters: ‘type’, ‘deep’ and ‘regex’.
The ‘type’ key can be assigned to the values of ‘fast’, ‘all’ or any of the following:
If ‘type’ is set to ‘fast’, it validates the data against the major credit cards’ numbering formats. Setting ‘type’ to ‘all’ will check with all the credit card types. You can also set ‘type’ to an array of the types you wish to match.
The ‘deep’ key should be set to a boolean value. If it is set to true, the validation will check the Luhn algorithm of the credit card (https://en.wikipedia.org/wiki/Luhn_algorithm). It defaults to false.
The ‘regex’ key allows you to supply your own regular expression that will be used to validate the credit card number.
var $validate = array(
'ccnumber' => array(
'rule' => array('cc', array('visa', 'maestro'), false, null),
'message' => 'The credit card number you supplied was invalid.'
)
);
Comparison is used to compare numeric values. It supports “is greater”, “is less”, “greater or equal”, “less or equal”, “equal to”, and “not equal”. Some examples are shown below:
var $validate = array(
'age' => array(
'rule' => array('comparison', '>=', 18),
'message' => 'Must be at least 18 years old to qualify.'
)
);
var $validate = array(
'age' => array(
'rule' => array('comparison', 'greater or equal', 18),
'message' => 'Must be at least 18 years old to qualify.'
)
);
This rule ensures that data is submitted in valid date formats. A single parameter (which can be an array) can be passed that will be used to check the format of the supplied date. The value of the parameter can be one of the following:
If no keys are supplied, the default key that will be used is ‘ymd’.
var $validate = array(
'born' => array(
'rule' => array('date','ymd'),
'message' => 'Enter a valid date in YY-MM-DD format.',
'allowEmpty' => true
)
);
While many data stores require a certain date format, you might consider doing the heavy lifting by accepting a wide-array of date formats and trying to convert them, rather than forcing users to supply a given format. The more work you can do for your users, the better.
This rule ensures that the data is a valid decimal number. A parameter can be passed to specify the number of digits required after the decimal point. If no parameter is passed, the data will be validated as a scientific float, which will cause validation to fail if no digits are found after the decimal point.
var $validate = array(
'price' => array(
'rule' => array('decimal', 2)
)
);
This checks whether the data is a valid email address. Passing a boolean true as the second parameter for this rule will also attempt to verify that the host for the address is valid.
var $validate = array('email' => array('rule' => 'email'));
var $validate = array(
'email' => array(
'rule' => array('email', true),
'message' => 'Please supply a valid email address.'
)
);
This rule will ensure that the value is equal to, and of the same type as the given value.
var $validate = array(
'food' => array(
'rule' => array('equalTo', 'cake'),
'message' => 'This value must be the string cake'
)
);
This rule checks for valid file extensions like .jpg or .png. Allow multiple extensions by passing them in array form.
var $validate = array(
'image' => array(
'rule' => array('extension', array('gif', 'jpeg', 'png', 'jpg')),
'message' => 'Please supply a valid image.'
)
);
This rule ensures that the value is a valid file name. This validation rule is currently non-functional.
This rule will ensure that a valid IPv4 or IPv6 address has been submitted. Accepts as option ‘both’ (default), ‘IPv4’ or ‘IPv6’.
var $validate = array(
'clientip' => array(
'rule' => array('ip', 'IPv4'), // or 'IPv6' or 'both' (default)
'message' => 'Please supply a valid IP address.'
)
);
The data for the field must be unique, it cannot be used by any other rows.
var $validate = array(
'login' => array(
'rule' => 'isUnique',
'message' => 'This username has already been taken.'
)
);
This rule ensures that the data meets a minimum length requirement.
var $validate = array(
'login' => array(
'rule' => array('minLength', 8),
'message' => 'Usernames must be at least 8 characters long.'
)
);
The length here is “the number of bytes in the string representation of the data”. Be careful that it may be larger than the number of characters when handling non-ASCII characters.
This rule ensures that the data stays within a maximum length requirement.
var $validate = array(
'login' => array(
'rule' => array('maxLength', 15),
'message' => 'Usernames must be no larger than 15 characters long.'
)
);
The length here is “the number of bytes in the string representation of the data”. Be careful that it may be larger than the number of characters when handling non-ASCII characters.
This rule will ensure that the value is in a valid monetary amount.
Second parameter defines where symbol is located (left/right).
var $validate = array(
'salary' => array(
'rule' => array('money', 'left'),
'message' => 'Please supply a valid monetary amount.'
)
);
Use this for validating a multiple select input. It supports parameters “in”, “max” and “min”.
var $validate = array(
'multiple' => array(
'rule' => array('multiple', array('in' => array('do', 'ray', 'me', 'fa', 'so', 'la', 'ti'), 'min' => 1, 'max' => 3)),
'message' => 'Please select one, two or three options'
)
);
This rule will ensure that the value is in a given set. It needs an array of values. The field is valid if the field’s value matches one of the values in the given array.
Example:
var $validate = array(
'function' => array(
'allowedChoice' => array(
'rule' => array('inList', array('Foo', 'Bar')),
'message' => 'Enter either Foo or Bar.'
)
)
);
Checks if the data passed is a valid number.
var $validate = array(
'cars' => array(
'rule' => 'numeric',
'message' => 'Please supply the number of cars.'
)
);
The basic rule to ensure that a field is not empty.
var $validate = array(
'title' => array(
'rule' => 'notEmpty',
'message' => 'This field cannot be left blank'
)
);
Do not use this for a multiple select input as it will cause an error. Instead, use “multiple”.
Phone validates US phone numbers. If you want to validate non-US phone numbers, you can provide a regular expression as the second parameter to cover additional number formats.
var $validate = array(
'phone' => array(
'rule' => array('phone', null, 'us')
)
);
Postal is used to validate ZIP codes from the U.S. (us), Canada (ca), U.K (uk), Italy (it), Germany (de) and Belgium (be). For other ZIP code formats, you may provide a regular expression as the second parameter.
var $validate = array(
'zipcode' => array(
'rule' => array('postal', null, 'us')
)
);
This rule ensures that the value is in a given range. If no range is supplied, the rule will check to ensure the value is a legal finite on the current platform.
var $validate = array(
'number' => array(
'rule' => array('range', -1, 11),
'message' => 'Please enter a number between 0 and 10'
)
);
The above example will accept any value which is larger than 0 (e.g., 0.01) and less than 10 (e.g., 9.99). Note: The range lower/upper are not inclusive!!!
Ssn validates social security numbers from the U.S. (us), Denmark (dk), and the Netherlands (nl). For other social security number formats, you may provide a regular expression.
var $validate = array(
'ssn' => array(
'rule' => array('ssn', null, 'us')
)
);
This rule checks for valid URL formats. Supports http(s), ftp(s), file, news, and gopher protocols.
var $validate = array(
'website' => array(
'rule' => 'url'
)
);
To ensure that a protocol is in the url, strict mode can be enabled like so.
var $validate = array(
'website' => array(
'rule' => array('url', true)
)
);
If you haven’t found what you need thus far, you can always create your own validation rules. There are two ways you can do this: by defining custom regular expressions, or by creating custom validation methods.
If the validation technique you need to use can be completed by using regular expression matching, you can define a custom expression as a field validation rule.
var $validate = array(
'login' => array(
'rule' => '/^[a-z0-9]{3,}$/i',
'message' => 'Only letters and integers, min 3 characters'
)
);
The example above checks if the login contains only letters and integers, with a minimum of three characters.
The regular expression in the rule
must be delimited by slashes. The
optional trailing ‘i’ after the last slash means the reg-exp is case
insensitive.
Sometimes checking data with regular expression patterns is not enough. For example, if you want to ensure that a promotional code can only be used 25 times, you need to add your own validation function, as shown below:
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'promotion_code' => array(
'rule' => array('limitDuplicates', 25),
'message' => 'This code has been used too many times.'
)
);
function limitDuplicates($check, $limit){
//$check will have value: array('promomotion_code' => 'some-value')
//$limit will have value: 25
$existing_promo_count = $this->find( 'count', array('conditions' => $check, 'recursive' => -1) );
return $existing_promo_count < $limit;
}
}
?>
The current field to be validated is passed into the function as first parameter as an associated array with field name as key and posted data as value.
If you want to pass extra parameters to your validation function, add
elements onto the ‘rule’ array, and handle them as extra params (after
the main $check
param) in your function.
Your validation function can be in the model (as in the example above), or in a behavior that the model implements. This includes mapped methods.
Model/behavior methods are checked first, before looking for a method on
the Validation
class. This means that you can override existing
validation methods (such as alphaNumeric()
) at an application level
(by adding the method to AppModel
), or at model level.
When writing a validation rule which can be used by multiple fields, take care to extract the field value from the $check array. The $check array is passed with the form field name as its key and the field value as its value. The full record being validated is stored in $this->data member variable.
<?php
class Post extends AppModel {
var $name = 'Post';
var $validate = array(
'slug' => array(
'rule' => 'alphaNumericDashUnderscore',
'message' => 'Slug can only be letters, numbers, dash and underscore'
)
);
function alphaNumericDashUnderscore($check) {
// $data array is passed using the form field name as the key
// have to extract the value to make the function generic
$value = array_shift($check);
return preg_match('|^[0-9a-zA-Z_-]*$|', $value);
}
}
?>
Current rule is always passed as the last method marameter, so you can access it:
<?php
class Message extends AppModel {
var $name = 'Message ';
var $validate = array(
'emails' => array(
'rule' => 'emails',
'maxcount' => 20
)
);
function emails($check) {
$rule = func_get_arg(func_num_args()-1);
$field = key($check);
$value = $check[$field];
$emails = $this->getEmails($value);
$errors = array();
$validation =& Validation::getInstance();
foreach($emails as $email) {
if (!$validation->email($email))
$errors []= "Email '$email' is invalid.";
}
if (isset($rule['maxcount']) && count($emails)>$rule['maxcount'])
$errors [] = "Up to {$rule['maxcount']} emails are allowed.";
if (!empty($errors))
return implode("\n", $errors);
return true;
}
While normally you would just use the save method of the model, there may be times where you wish to validate the data without saving it. For example, you may wish to display some additional information to the user before actually saving the data to the database. Validating data requires a slightly different process than just saving the data.
First, set the data to the model:
$this->ModelName->set( $this->data );
Then, to check if the data validates, use the validates method of the model, which will return true if it validates and false if it doesn’t:
if ($this->ModelName->validates()) {
// it validated logic
} else {
// didn't validate logic
}
It may be desirable to validate your model only using a subset of the validations specified in your model. For example say you had a User model with fields for first_name, last_name, email and password. In this instance when creating or editing a user you would want to validate all 4 field rules. Yet when a user logs in you would validate just email and password rules. To do this you can pass an options array specifying the fields to validate. e.g.
if ($this->User->validates(array('fieldList' => array('email', 'password')))) {
// valid
} else {
// invalid
}
The validates method invokes the invalidFields method which populates the validationErrors property of the model. The invalidFields method also returns that data as the result.
$errors = $this->ModelName->invalidFields(); // contains validationErrors array
It is important to note that the data must be set to the model before the data can be validated. This is different from the save method which allows the data to be passed in as a parameter. Also, keep in mind that it is not required to call validates prior to calling save as save will automatically validate the data before actually saving.
To validate multiple models, the following approach should be used:
if ($this->ModelName->saveAll($this->data, array('validate' => 'only'))) {
// validates
} else {
// does not validate
}
If you have validated data before save, you can turn off validation to avoid second check.
if ($this->ModelName->saveAll($this->data, array('validate' => false))) {
// saving wihout validation
}