Entity Validation API overview
In Drupal 8, Entity validation is moved to a separate Entity validation API and decoupled from form validation. Decoupling entity validation from forms allows validation entities to be independent of form submissions, such as when they are changed via a RESTful web service. This new validation API has been implemented based on the Symfony validator.
Validation is triggered when an entity is about to be saved. Calling $entity->validate() returns a list of violations. If none, saving is allowed.
Custom validation logic should live in constraint/validator classes, and those constraints are attached to the entity type or field definitions (or altered via hooks)
Overview
Validation is controlled by a set of constraints, such as the maximum length of some text or restriction to a set of allowed values. Symfony comes with many useful constraints, which Drupal extends with more Drupal-specific constraints. Drupal Symfony validator has been integrated with the Typed Data API, such that validation constraints can be used and specified as part of Entity field definitions and, generally, any typed data definition.
How the Entity Validation API Works
Drupal’s Entity Validation API integrates with Symfony’s Validation component.
Validation ensures that entity and field data meet defined rules before being saved to the database.
When validation happens
- Validation automatically runs whenever an entity is being saved (
$entity->save()). - You can also manually validate an entity at any time using
$entity->validate(). - Form submissions that create or edit entities trigger the same validation automatically.
What runs during validation
- Each entity and field can define one or more constraints.
- Constraints point to validator classes that contain the actual logic.
- The validation system loops through every constraint, runs its validator, and collects any violations.
What happens next
- If violations exist, they are shown as form errors (if applicable) or can be handled in custom code.
- If there are no violations, the entity is saved normally.
Interacting With the Entity Field Validation API
To enable an entity or field to be validated, Drupal needs to know which constraints apply to which field or entity type. This is done by using the API to add and modify constraints. Once you've gathered your requirements, you need to attach your validation constraints to your entity types and fields. How and where you do this depends on the type of constraint, whether you define the entity type in your code, the type of field, and the nature of the constraint. Before we address these, let's take a quick look at the anatomy of a constraint.
Constraints are Plugins
Constraints are defined as plugins. Helpfully, core comes with a plethora of existing constraints, such as:
- NotNull;
- Length (supporting minimum and maximum);
- Count;
- Range (for numeric values);
- IsNull;
- Email;
- AllowedValues;
- ComplexData (more on that later).
In many cases, you’ll be able to implement your validation logic by combining the existing constraint plugins in core. However, if you do need to create a custom constraint, all that is required is a new constraint plugin.
Using the API
Validation can be invoked by calling the validate() method on any typed data object, as in the following example:
$definition = DataDefinition::create('integer')
->addConstraint('Range', ['min' => 5]);
$typed_data = \Drupal::typedDataManager()->create($definition, 10);
$violations = $typed_data->validate();
This returns a list of violations if passed an empty validation:
if ($violations->count() > 0) {
// Validation failed.
}
Violations are objects, which provide a translated violation message to the caller:
print $violations[0]->getMessage();
In this example, a Range constraint has been specified as part of the data definition. However, some additional constraints might be auto-generated by the class, or added in as default based on the data type. For example, an e-mail type would add the constraints to ensure the value is a string and a valid e-mail address. All applied constraints can be retrieved by calling $typed_data->getConstraints().
Calling validate() on a typed data object is a shortcut to obtain a Symfony validator and validate the data, whereas this Symfony validator object has been configured as necessary for validating typed data. $typed_data->validate() is equivalent to:
return \Drupal::typedDataManager()->getValidator()->validate($typed_data);
Validating entities
Entity fields and field items are typed data objects, and can be validated as in this example:
$violations = $entity->field_text->validate();
Here is an example of validating an entity as a whole:
$violations = $entity->validate();
Violations contain the property path to the property that failed validation, relative to the object where validation began. For example, if the example field's text (which resides in $field[0]->value) fails validation $violation->getPropertyPath() the property path would be "0.value" in the first example and "field_text.0.value" in the second.
Putting constraints on field item properties
Entity field definitions ease putting constraints on individual field item properties via the setPropertyConstraints() method. In the following example, the field definition puts a maximum length constraint on the field item's value property ($field[0]->value):
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setPropertyConstraints('value', ['Length' => ['max' => 32]]);
Relationship to Symfony validator
Drupal makes use of Symfony constraint classes and their validation logic (ConstraintValidator classes) but integrates them via the Drupal 8 plugin system so that they are discovered via the usual annotation-based plugin discovery. As a result, references to constraints in typed data definitions are plugin IDs. For example, Range in the example above refers to the Range constraint plugin (class).
Symfony validator is configured to use a Drupal translator class, such that violation messages correctly run through t(). While Symfony constraint messages use a {{ key }} syntax for message placeholders, those messages are converted to use regular Drupal placeholders in the form of %key. For consistency, this approach should be followed if further Symfony constraints are exposed to Drupal.
Documentation
For further details, read about Symfony validator:
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion
Still on Drupal 7? Security support for Drupal 7 ended on 5 January 2025. Please visit our Drupal 7 End of Life resources page to review all of your options.