Validation
The validation package in CakePHP provides features to build validators that canvalidate arbitrary arrays of data with ease. You can find a list of availableValidation rules in the API.
Creating Validators
Validator objects define the rules that apply to a set of fields.Validator objects contain a mapping between fields and validation sets. Inturn, the validation sets contain a collection of rules that apply to the fieldthey are attached to. Creating a validator is simple:
- use Cake\Validation\Validator;
- $validator = new Validator();
Once created, you can start defining sets of rules for the fields you want tovalidate:
- $validator
- ->requirePresence('title')
- ->notEmptyString('title', 'Please fill this field')
- ->add('title', [
- 'length' => [
- 'rule' => ['minLength', 10],
- 'message' => 'Titles need to be at least 10 characters long',
- ]
- ])
- ->allowEmptyDateTime('published')
- ->add('published', 'boolean', [
- 'rule' => 'boolean'
- ])
- ->requirePresence('body')
- ->add('body', 'length', [
- 'rule' => ['minLength', 50],
- 'message' => 'Articles must have a substantial body.'
- ]);
As seen in the example above, validators are built with a fluent interface thatallows you to define rules for each field you want to validate.
There were a few methods called in the example above, so let’s go over thevarious features. The add()
method allows you to add new rules toa validator. You can either add rules individually or in groups as seen above.
Requiring Field Presence
The requirePresence()
method requires the field to be present in anyvalidated array. If the field is absent, validation will fail. TherequirePresence()
method has 4 modes:
true
The field’s presence is always required.false
The field’s presence is not required.create
The field’s presence is required when validating a createoperation.update
The field’s presence is required when validating an updateoperation.
By default, true
is used. Key presence is checked by usingarray_key_exists()
so that null values will count as present. You can setthe mode using the second parameter:
- $validator->requirePresence('author_id', 'create');
If you have multiple fields that are required, you can define them as a list:
- // Define multiple fields for create
- $validator->requirePresence(['author_id', 'title'], 'create');
- // Define multiple fields for mixed modes
- $validator->requirePresence([
- 'author_id' => [
- 'mode' => 'create',
- 'message' => 'An author is required.',
- ],
- 'published' => [
- 'mode' => 'update',
- 'message' => 'The published state is required.',
- ]
- ]);
Allowing Empty Fields
Validators offer several methods to control which fields accept empty values andwhich empty values are accepted and not forwarded to other validation rules forthe named field. CakePHP provides empty value support for five different shapesof data:
allowEmptyString()
Should be used when you want to only acceptan empty string.allowEmptyArray()
Should be used when you want to accept an array.allowEmptyDate()
Should be used when you want to accept an empty string,or an array that is marshalled into a date field.allowEmptyTime()
Should be used when you want to accept an empty string,or an array that is marshalled into a time field.allowEmptyDateTime()
Should be used when you want to accept an emptystring or an array that is marshalled into a datetime or timestamp field.allowEmptyFile()
Should be used when you want to accept an array thatis contains an empty uploaded file.You can also usenotEmpty()
to mark a field invalid if any ‘empty’ value isused. In general, it is recommended that you do not usenotEmpty()
and use morespecific validators instead:notEmptyString()
,notEmptyArray()
,notEmptyFile()
,notEmptyDate()
,notEmptyTime()
,notEmptyDateTime()
.
The allowEmpty*
methods support a when
parameter that allows you to controlwhen a field can or cannot be empty:
false
The field is not allowed to be empty.create
The field can be empty when validating a createoperation.update
The field can be empty when validating an updateoperation.- A callback that returns
true
orfalse
to indicate whether a field isallowed to be empty. See the Conditional Validation section for examples onhow to use this parameter.
An example of these methods in action is:
- $validator->allowEmptyDateTime('published')
- ->allowEmptyString('title', 'Title cannot be empty', false)
- ->allowEmptyString('body', 'Body cannot be empty', 'update')
- ->allowEmptyFile('header_image', 'update');
- ->allowEmptyDateTime('posted', 'update');
Adding Validation Rules
The Validator
class provides methods that make building validators simpleand expressive. For example adding validation rules to a username could looklike:
- $validator = new Validator();
- $validator
- ->email('username')
- ->ascii('username')
- ->lengthBetween('username', [4, 8]);
See the Validator API documentation for thefull set of validator methods.
Using Custom Validation Rules
In addition to using methods on the Validator
, and coming from providers, youcan also use any callable, including anonymous functions, as validation rules:
- // Use a global function
- $validator->add('title', 'custom', [
- 'rule' => 'validate_title',
- 'message' => 'The title is not valid'
- ]);
- // Use an array callable that is not in a provider
- $validator->add('title', 'custom', [
- 'rule' => [$this, 'method'],
- 'message' => 'The title is not valid'
- ]);
- // Use a closure
- $extra = 'Some additional value needed inside the closure';
- $validator->add('title', 'custom', [
- 'rule' => function ($value, $context) use ($extra) {
- // Custom logic that returns true/false
- },
- 'message' => 'The title is not valid'
- ]);
- // Use a rule from a custom provider
- $validator->add('title', 'custom', [
- 'rule' => 'customRule',
- 'provider' => 'custom',
- 'message' => 'The title is not unique enough'
- ]);
Closures or callable methods will receive 2 arguments when called. The firstwill be the value for the field being validated. The second is a context arraycontaining data related to the validation process:
- data: The original data passed to the validation method, useful if youplan to create rules comparing values.
- providers: The complete list of rule provider objects, useful if youneed to create complex rules by calling multiple providers.
- newRecord: Whether the validation call is for a new record ora preexisting one.
If you need to pass additional data to your validation methods such as thecurrent user’s id, you can use a custom dynamic provider from your controller.
- $this->Examples->validator('default')->provider('passed', [
- 'count' => $countFromController,
- 'userid' => $this->Auth->user('id')
- ]);
Then ensure that your validation method has the second context parameter.
- public function customValidationMethod($check, array $context)
- {
- $userid = $context['providers']['passed']['userid'];
- }
Closures should return boolean true if the validation passes. If it fails,return boolean false or for a custom error message return a string, see theConditional/Dynamic Error Messagessection for further details.
Conditional/Dynamic Error Messages
Validation rule methods, being it custom callables,or methods supplied by providers, can eitherreturn a boolean, indicating whether the validation succeeded, or they can returna string, which means that the validation failed, and that the returned stringshould be used as the error message.
Possible existing error messages defined via the message
option will beoverwritten by the ones returned from the validation rule method:
- $validator->add('length', 'custom', [
- 'rule' => function ($value, $context) {
- if (!$value) {
- return false;
- }
- if ($value < 10) {
- return 'Error message when value is less than 10';
- }
- if ($value > 20) {
- return 'Error message when value is greater than 20';
- }
- return true;
- },
- 'message' => 'Generic error message used when `false` is returned'
- ]);
Conditional Validation
When defining validation rules, you can use the on
key to define whena validation rule should be applied. If left undefined, the rule will always beapplied. Other valid values are create
and update
. Using one of thesevalues will make the rule apply to only create or update operations.
Additionally, you can provide a callable function that will determine whether ornot a particular rule should be applied:
- $validator->add('picture', 'file', [
- 'rule' => ['mimeType', ['image/jpeg', 'image/png']],
- 'on' => function ($context) {
- return !empty($context['data']['show_profile_picture']);
- }
- ]);
You can access the other submitted field values using the $context['data']
array. The above example will make the rule for ‘picture’ optional depending onwhether the value for show_profile_picture
is empty. You could also use theuploadedFile
validation rule to create optional file upload inputs:
- $validator->add('picture', 'file', [
- 'rule' => ['uploadedFile', ['optional' => true]],
- ]);
The allowEmpty*
, notEmpty()
and requirePresence()
methods will alsoaccept a callback function as their last argument. If present, the callbackdetermines whether or not the rule should be applied. For example, a field issometimes allowed to be empty:
- $validator->allowEmptyString('tax', function ($context) {
- return !$context['data']['is_taxable'];
- });
Likewise, a field can be required to be populated when certain conditions aremet:
- $validator->notEmpty('email_frequency', 'This field is required', function ($context) {
- return !empty($context['data']['wants_newsletter']);
- });
In the above example, the email_frequency
field cannot be left empty if thethe user wants to receive the newsletter.
Further it’s also possible to require a field to be present under certainconditions only:
- $validator->requirePresence('full_name', function ($context) {
- if (isset($context['data']['action'])) {
- return $context['data']['action'] === 'subscribe';
- }
- return false;
- });
- $validator->requirePresence('email');
This would require the full_name
field to be present only in case the userwants to create a subscription, while the email
field would always berequired.
The $context
parameter passed to custom conditional callbacks contains thefollowing keys:
data
The data being validated.newRecord
a boolean indicating whether a new or existing record is beingvalidated.field
The current field being validated.providers
The validation providers attached to the current validator.
Marking Rules as the Last to Run
When fields have multiple rules, each validation rule will be run even if theprevious one has failed. This allows you to collect as many validation errors asyou can in a single pass. However, if you want to stop execution aftera specific rule has failed, you can set the last
option to true
:
- $validator = new Validator();
- $validator
- ->add('body', [
- 'minLength' => [
- 'rule' => ['minLength', 10],
- 'last' => true,
- 'message' => 'Comments must have a substantial body.'
- ],
- 'maxLength' => [
- 'rule' => ['maxLength', 250],
- 'message' => 'Comments cannot be too long.'
- ]
- ]);
If the minLength rule fails in the example above, the maxLength rule will not berun.
Adding Validation Providers
The Validator
, ValidationSet
and ValidationRule
classes do notprovide any validation methods themselves. Validation rules come from‘providers’. You can bind any number of providers to a Validator object.Validator instances come with a ‘default’ provider setup automatically. Thedefault provider is mapped to the Validation\Validation
class. This makes it simple to use the methods on that class as validationrules. When using Validators and the ORM together, additional providers areconfigured for the table and entity objects. You can use the setProvider()
method to add any additional providers your application needs:
- $validator = new Validator();
- // Use an object instance.
- $validator->setProvider('custom', $myObject);
- // Use a class name. Methods must be static.
- $validator->setProvider('custom', 'App\Model\Validation');
Validation providers can be objects, or class names. If a class name is used themethods must be static. To use a provider other than ‘default’, be sure to setthe provider
key in your rule:
- // Use a rule from the table provider
- $validator->add('title', 'custom', [
- 'rule' => 'customTableMethod',
- 'provider' => 'table'
- ]);
If you wish to add a provider
to all Validator
objects that are createdin the future, you can use the addDefaultProvider()
method as follows:
- use Cake\Validation\Validator;
- // Use an object instance.
- Validator::addDefaultProvider('custom', $myObject);
- // Use a class name. Methods must be static.
- Validator::addDefaultProvider('custom', 'App\Model\Validation');
Note
DefaultProviders must be added before the Validator
object is createdtherefore config/bootstrap.php is the best place to set up yourdefault providers.
You can use the Localized plugin toget providers based on countries. With this plugin, you’ll be able to validatemodel fields, depending on a country, ie:
- namespace App\Model\Table;
- use Cake\ORM\Table;
- use Cake\Validation\Validator;
- class PostsTable extends Table
- {
- public function validationDefault(Validator $validator)
- {
- // add the provider to the validator
- $validator->setProvider('fr', 'Localized\Validation\FrValidation');
- // use the provider in a field validation rule
- $validator->add('phoneField', 'myCustomRuleNameForPhone', [
- 'rule' => 'phone',
- 'provider' => 'fr'
- ]);
- return $validator;
- }
- }
The localized plugin uses the two letter ISO code of the countries forvalidation, like en, fr, de.
There are a few methods that are common to all classes, defined through theValidationInterface interface:
- phone() to check a phone number
- postal() to check a postal code
- personId() to check a country specific person ID
Nesting Validators
When validating Modelless Forms with nested data, or when workingwith models that contain array data types, it is necessary to validate thenested data you have. CakePHP makes it simple to add validators to specificattributes. For example, assume you are working with a non-relational databaseand need to store an article and its comments:
- $data = [
- 'title' => 'Best article',
- 'comments' => [
- ['comment' => '']
- ]
- ];
To validate the comments you would use a nested validator:
- $validator = new Validator();
- $validator->add('title', 'not-blank', ['rule' => 'notBlank']);
- $commentValidator = new Validator();
- $commentValidator->add('comment', 'not-blank', ['rule' => 'notBlank']);
- // Connect the nested validators.
- $validator->addNestedMany('comments', $commentValidator);
- // Get all errors including those from nested validators.
- $validator->validate($data);
You can create 1:1 ‘relationships’ with addNested()
and 1:N ‘relationships’with addNestedMany()
. With both methods, the nested validator’s errors willcontribute to the parent validator’s errors and influence the final result.Like other validator features, nested validators support error messages andconditional application:
- $validator->addNestedMany(
- 'comments',
- $commentValidator,
- 'Invalid comment',
- 'create'
- );
The error message for a nested validator can be found in the _nested
key.
Creating Reusable Validators
While defining validators inline where they are used makes for good examplecode, it doesn’t lead to maintainable applications. Instead, you shouldcreate Validator
sub-classes for your reusable validation logic:
- // In src/Model/Validation/ContactValidator.php
- namespace App\Model\Validation;
- use Cake\Validation\Validator;
- class ContactValidator extends Validator
- {
- public function __construct()
- {
- parent::__construct();
- // Add validation rules here.
- }
- }
Validating Data
Now that you’ve created a validator and added the rules you want to it, you canstart using it to validate data. Validators are able to validate arraydata. For example, if you wanted to validate a contact form before creating andsending an email you could do the following:
- use Cake\Validation\Validator;
- $validator = new Validator();
- $validator
- ->requirePresence('email')
- ->add('email', 'validFormat', [
- 'rule' => 'email',
- 'message' => 'E-mail must be valid'
- ])
- ->requirePresence('name')
- ->notEmpty('name', 'We need your name.')
- ->requirePresence('comment')
- ->notEmpty('comment', 'You need to give a comment.');
- $errors = $validator->validate($this->request->getData());
- if (empty($errors)) {
- // Send an email.
- }
The errors()
method will return a non-empty array when there are validationfailures. The returned array of errors will be structured like:
- $errors = [
- 'email' => ['E-mail must be valid']
- ];
If you have multiple errors on a single field, an array of error messages willbe returned per field. By default the errors()
method applies rules forthe ‘create’ mode. If you’d like to apply ‘update’ rules you can do thefollowing:
- $errors = $validator->validate($this->request->getData(), false);
- if (empty($errors)) {
- // Send an email.
- }
Note
If you need to validate entities you should use methods likeORM\Table::newEntity()
,ORM\Table::newEntities()
,ORM\Table::patchEntity()
,ORM\Table::patchEntities()
orORM\Table::save()
as they are designed for that.
Validating Entities
While entities are validated as they are saved, you may also want to validateentities before attempting to do any saving. Validating entities beforesaving is done automatically when using the newEntity()
, newEntities()
,patchEntity()
or patchEntities()
:
- // In the ArticlesController class
- $article = $this->Articles->newEntity($this->request->getData());
- if ($article->errors()) {
- // Do work to show error messages.
- }
Similarly, when you need to pre-validate multiple entities at a time, you canuse the newEntities()
method:
- // In the ArticlesController class
- $entities = $this->Articles->newEntities($this->request->getData());
- foreach ($entities as $entity) {
- if (!$entity->errors()) {
- $this->Articles->save($entity);
- }
- }
The newEntity()
, patchEntity()
, newEntities()
and patchEntities()
methods allow you to specify which associations are validated, and whichvalidation sets to apply using the options
parameter:
- $valid = $this->Articles->newEntity($article, [
- 'associated' => [
- 'Comments' => [
- 'associated' => ['User'],
- 'validate' => 'special',
- ]
- ]
- ]);
Validation is commonly used for user-facing forms or interfaces, and thus it isnot limited to only validating columns in the table schema. However,maintaining integrity of data regardless where it came from is important. Tosolve this problem CakePHP offers a second level of validation which is called“application rules”. You can read more about them in theApplying Application Rules section.
Core Validation Rules
CakePHP provides a basic suite of validation methods in the Validation
class. The Validation class contains a variety of static methods that providevalidators for several common validation situations.
The API documentation for theValidation
class provides a good list of the validation rules that areavailable, and their basic usage.
Some of the validation methods accept additional parameters to define boundaryconditions or valid options. You can provide these boundary conditions andoptions as follows:
- $validator = new Validator();
- $validator
- ->add('title', 'minLength', [
- 'rule' => ['minLength', 10]
- ])
- ->add('rating', 'validValue', [
- 'rule' => ['range', 1, 5]
- ]);
Core rules that take additional parameters should have an array for therule
key that contains the rule as the first element, and the additionalparameters as the remaining parameters.