When marshalling data into entities, you can validate data. Validating data
allows you to check the type, shape and size of data. By default request data
will be validated before it is converted into entities.
If any validation rules fail, the returned entity will contain errors. The
fields with errors will not be present in the returned entity:

The above would call the validationUpdate() method on the table instance to
build the required rules. By default the validationDefault() method will be
used. An example validator for our articles table would be:

classArticlesTableextendsTable{publicfunctionvalidationUpdate($validator){$validator->add('title','notEmpty',['rule'=>'notEmpty','message'=>__('You need to provide a title'),])->add('body','notEmpty',['rule'=>'notEmpty','message'=>__('A body is required')]);return$validator;}}

You can have as many validation sets as necessary. See the validation
chapter for more information on building
validation rule-sets.

As stated above, by default the validation methods receive an instance of
Cake\Validation\Validator. Instead, if you want your custom validator’s
instance to be used each time, you can use table’s $_validatorClass property:

While basic data validation is done when request data is converted into
entities, many applications also have more complex
validation that should only be applied after basic validation has completed.

Where validation ensures the form or syntax of your data is correct, rules
focus on comparing data against the existing state of your application and/or
network.

These types of rules are often referred to as ‘domain rules’ or ‘application
rules’. CakePHP exposes this concept through ‘RulesCheckers’ which are applied
before entities are persisted. Some example domain rules are:

Ensuring email uniqueness

State transitions or workflow steps (e.g., updating an invoice’s status).

Preventing the modification of soft deleted items.

Enforcing usage/rate limit caps.

Domain rules are checked when calling the Table save() and delete() methods.

Rules checker classes are generally defined by the buildRules() method in your
table class. Behaviors and other event subscribers can use the
Model.buildRules event to augment the rules checker for a given Table
class:

useCake\ORM\RulesChecker;// In a table classpublicfunctionbuildRules(RulesChecker$rules){// Add a rule that is applied for create and update operations$rules->add(function($entity,$options){// Return a boolean to indicate pass/failure},'ruleName');// Add a rule for create.$rules->addCreate(function($entity,$options){// Return a boolean to indicate pass/failure},'ruleName');// Add a rule for update$rules->addUpdate(function($entity,$options){// Return a boolean to indicate pass/failure},'ruleName');// Add a rule for the deleting.$rules->addDelete(function($entity,$options){// Return a boolean to indicate pass/failure},'ruleName');return$rules;}

Your rules functions can expect to get the Entity being checked and an array of
options. The options array will contain errorField, message, and
repository. The repository option will contain the table class the rules
are attached to. Because rules accept any callable, you can also use
instance functions:

$rules->addCreate([$this,'uniqueEmail'],'uniqueEmail');

or callable classes:

$rules->addCreate(newIsUnique(['email']),'uniqueEmail');

When adding rules you can define the field the rule is for and the error
message as options:

$rules->add([$this,'isValidState'],'validState',['errorField'=>'status','message'=>'This invoice cannot be moved to that status.']);

The error will be visible when calling the errors() method on the entity:

Because unique rules are quite common, CakePHP includes a simple Rule class that
allows you to define unique field sets:

useCake\ORM\Rule\IsUnique;// A single field.$rules->add($rules->isUnique(['email']));// A list of fields$rules->add($rules->isUnique(['username','account_id'],'This username & account_id combination has already been used.'));

When setting rules on foreign key fields it is important to remember, that
only the fields listed are used in the rule. This means that setting
$user->account->id will not trigger the above rule.

The fields to check existence against in the related table must be part of the
primary key.

You can enforce existsIn to pass when nullable parts of your composite foreign key
are null:

// Example: A composite primary key within NodesTable is (id, site_id).// A Node may reference a parent Node but does not need to. In latter case, parent_id is null.// Allow this rule to pass, even if fields that are nullable, like parent_id, are null:$rules->add($rules->existsIn(['parent_id','site_id'],// Schema: parent_id NULL, site_id NOT NULL'ParentNodes',['allowNullableNulls'=>true]));// A Node however should in addition also always reference a Site.$rules->add($rules->existsIn(['site_id'],'Sites'));

In most SQL databases multi-column UNIQUE indexes allow multiple null values
to exist as NULL is not equal to itself. While, allowing multiple null
values is the default behavior of CakePHP, you can include null values in your
unique checks using allowMultipleNulls:

// Only one null value can exist in `parent_id` and `site_id`$rules->add($rules->existsIn(['parent_id','site_id'],'ParentNodes',['allowMultipleNulls'=>false]));

New in version 3.3.0: The allowNullableNulls and allowMultipleNulls options were added.

If you need to validate that a property or association contains the correct
number of values, you can use the validCount() rule:

// In the ArticlesTable.php file// No more than 5 tags on an article.$rules->add($rules->validCount('tags',5,'<=','You can only have 5 tags'));

When defining count based rules, the third parameter lets you define the
comparison operator to use. ==, >=, <=, >, <, and !=
are the accepted operators. To ensure a property’s count is within a range, use
two rules:

// In the ArticlesTable.php file// Between 3 and 5 tags$rules->add($rules->validCount('tags',3,'>=','You must have at least 3 tags'));$rules->add($rules->validCount('tags',5,'<=','You must have at most 5 tags'));

Note that validCount returns false if the property is not countable or does not exist:

// The save operation will fail if tags is null.$rules->add($rules->validCount('tags',0,'<=','You must not have any tags'));

You may want to re-use custom domain rules. You can do so by creating your own invokable rule:

useApp\ORM\Rule\IsUniqueWithNulls;// ...publicfunctionbuildRules(RulesChecker$rules){$rules->add(newIsUniqueWithNulls(['parent_id','instance_id','name']),'uniqueNamePerParent',['errorField'=>'name','message'=>'Name must be unique per parent.']);return$rules;}

If your application has rules that are commonly reused, it is helpful to package
those rules into re-usable classes:

// in src/Model/Rule/CustomRule.phpnamespaceApp\Model\Rule;useCake\Datasource\EntityInterface;classCustomRule{publicfunction__invoke(EntityInterface$entity,array$options){// Do workreturnfalse;}}// Add the custom ruleuseApp\Model\Rule\CustomRule;$rules->add(newCustomRule(...),'ruleName');

By creating custom rule classes you can keep your code DRY and make your domain
rules easy to test.

The CakePHP ORM is unique in that it uses a two-layered approach to validation.

The first layer is validation. Validation rules are intended to operate in
a stateless way. They are best leveraged to ensure that the shape, data types
and format of data is correct.

The second layer is application rules. Application rules are best leveraged to
check stateful properties of your entities. For example, validation rules could
ensure that an email address is valid, while an application rule could ensure
that the email address is unique.

As you already discovered, the first layer is done through the Validator
objects when calling newEntity() or patchEntity():

Validation assumes strings or array are passed since that is what is received
from any request:

// In src/Model/Table/UsersTable.phppublicfunctionvalidatePasswords($validator){$validator->add('confirm_password','no-misspelling',['rule'=>['compareWith','password'],'message'=>'Passwords are not equal',]);...return$validator;}

Validation is not triggered when directly setting properties on your
entities:

$userEntity->email='not an email!!';$usersTable->save($userEntity);

In the above example the entity will be saved as validation is only
triggered for the newEntity() and patchEntity() methods. The second
level of validation is meant to address this situation.

Application rules as explained above will be checked whenever save() or
delete() are called:

In certain situations you may want to run the same data validation routines for
data that was both generated by users and inside your application. This could
come up when running a CLI script that directly sets properties on entities: