On this page
- Step 1: Define the Constraint
- Step 2: Create the Validator for the Constraint
- Step 3: Set the Constraint to the Entity or Field it Needs to Validate
- Method 1 - Adding a Constraint to a Base Field of an Entity Type Defined by Your Module
- Method 2 - Adding a Constraint to An Entity Type Defined by Your Module
- Method 3 - Adding a Constraint to a Bundle Field of an Entity Type Not Defined by Your Module
- Method 4 - Adding a Constraint to an Entity Type Not Defined by Your Module
- Using Options
- Using Different Error Messages for Singular and Plural
- Targeting Specific Properties in Complex Field Widgets
- Example
Defining Constraints (Validations) on Entities and/or Fields.
This documentation needs work. See "Help improve this page" in the sidebar.
This documentation provides a concise overview of constraints on entities and/or fields. They are used to validate user input and display error messages or warnings when invalid data is entered. Constraints play a crucial role in ensuring data integrity, validating input, and enforcing business rules.
Creating a custom constraint requires three steps:
- Define the constraint
- Create the validation for the constraint
- Set the constraint to the entity type or field it needs to validate
Automatically generating constraints using Drush
Drush can be used to quickly generate custom constraints. Execute drush generate plugin:constraint or drush generate constraint and answer a few basic questions to generate boilerplate code for a constraint. If you are unsure how to answer the question "Type of data to validate" pick any value and manually add your business logic to public function validate(...) in YourCustomConstraintValidator.php.
Step 1: Define the Constraint
The constraint definition will go in the namespace Drupal\[MODULENAME]\Plugin\Validation\Constraint, and will extend Symfony\Component\Validator\Constraint. In this class, the types of constraint violations are defined, along with the error messages that will be displayed for the given violations.
Note that the validation callback function is defined in the custom ConstraintValidator class, not in this Constraint class. (See Step 2.)
<?php
namespace Drupal\[MODULENAME]\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Checks that the submitted value is a unique integer.
*
* @Constraint(
* id = "UniqueInteger",
* label = @Translation("Unique Integer", context = "Validation"),
* type = "string"
* )
*/
class UniqueIntegerConstraint extends Constraint {
// The message that will be shown if the value is not an integer.
public $notInteger = '%value is not an integer';
// The message that will be shown if the value is not unique.
public $notUnique = '%value is not unique';
}
Step 2: Create the Validator for the Constraint
The next step is to create the class that will validate the constraint. The constraint validation will go in the namespace Drupal\[MODULENAME]\Plugin\Validation\Constraint, and will extend Symfony\Component\Validator\ConstraintValidator. In this class, the submitted values will be returned, and any violations will be registered.
Please note, that the name of your validator class is expected to be ${ConstraintClassName}Validator by default. If you want to use a different name, you may overwrite the validatedBy() method of the Constraint class you created in step 1.
<?php
namespace Drupal\[MODULENAME]\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the UniqueInteger constraint.
*/
class UniqueIntegerConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
foreach ($value as $item) {
// First check if the value is an integer.
if (!is_int($item->value)) {
// The value is not an integer, so a violation, aka error, is applied.
// The type of violation applied comes from the constraint description
// in step 1.
$this->context->addViolation($constraint->notInteger, ['%value' => $item->value]);
}
// Next check if the value is unique.
if (!$this->isUnique($item->value)) {
$this->context->addViolation($constraint->notUnique, ['%value' => $item->value]);
}
}
}
/**
* Is unique?
*
* @param string $value
*/
private function isUnique($value) {
// Here is where the check for a unique value would happen.
}
}Step 3: Set the Constraint to the Entity or Field it Needs to Validate
The method of applying the constraint differs depending on whether or not the field is a base field on an Entity you've defined in a custom module.
Method 1 - Adding a Constraint to a Base Field of an Entity Type Defined by Your Module
This method is used to add a constraint to a base field on an entity type defined by code in your custom module. Constraints on base fields are added using BaseFieldDefinition::addConstraint() in overrides of ContentEntityBase::baseFieldDefinitions():
public static function baseFieldDefinitions(EntityTypeInterface $entityType) {
$fields['unique_number'] = BaseFieldDefinition::create('integer')
->setLabel(t('Unique Number'))
// Use the ID of the constraint as it was defined
// in the annotation of the constraint definition
->addConstraint('UniqueInteger')
// Multiple constraints can be defined using setConstraints.
// ->setConstraints([
// 'UniqueInteger' => [],
// 'StartEndTimeConstraint' => [],
// ]);
return $fields;
}Method 2 - Adding a Constraint to An Entity Type Defined by Your Module
This method is used to add a constraint to an entity type defined by code in your custom module. Add the constraint to the entity type annotation on the entity class. If the constraint plugin is configurable, the options can be set there. If it is not, specify an empty array with {}.
/**
* @MyEntityType(
* constraints = {
* "GroupContentCardinality" = {}
* }
* )
*/
Method 3 - Adding a Constraint to a Bundle Field of an Entity Type Not Defined by Your Module
This method is used to add constraints to a field on an entity type defined in a module other than your own, or if the field is not a base field of your custom entity that is created with BaseFieldDefinition::create. This could be a field that was added to your entity manually in the Drupal backend and not in the code of your custom entity.
In [MODULENAME].module, implement either
hook_entity_base_field_info_alter() (for base fields defined by the entity), or
hook_entity_bundle_field_info_alter() (for fields added to a bundle of the entity):
/**
* Implements hook_entity_bundle_field_info_alter().
*/
function MODULENAME_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
if ($entity_type->id() === 'the_entity_type' && $bundle === 'the_bundle') {
if (isset($fields['unique_number'])) {
// Use the ID as defined in the annotation of the constraint definition
$fields['unique_number']->addConstraint('UniqueInteger', []);
}
}
}
Method 4 - Adding a Constraint to an Entity Type Not Defined by Your Module
This method is used to add constraints to an entity type defined in a module other than your own. In [MODULENAME].module, implement hook_entity_type_alter() :
/**
* Implements hook_entity_type_alter().
*
* @param array $entity_types
*/
function MODULENAME_entity_type_alter(array &$entity_types) {
// Add validation constraint to the node entity
$entity_types['node']->addConstraint('special_validation_constraint');
}
A cache clear is required for this to take effect but an update function is not.
Using Options
It is possible to have so-called options for your constraint. To do so, first add a public property for your new option to the constraint class. E.g., if you add a new public property $count, the name of your option would be 'count'. You may also implement the methods Constraint::getRequiredOptions() and Constraint::getDefaultOption(), if needed.
Once you have done that, you can pass values for these options to the constraint when you add a new constraint to a field or an entity. The second parameter of the addConstraint() method shown in step 3 is an array of option values keyed by option name. For example, to pass a count option with value 3 to a constraint: addConstraint('example_constraint', ['count' => 3]);
To access the values of any constraint options in your constraint validator, just access the property you created, e.g. $constraint->count for the count option.
Using Different Error Messages for Singular and Plural
If you need to show different error messages for singular and plural of a value, first adjust the error message of your constraint so that it uses a pipe between the singular and the plural form (syntax: <singular>|<plural>): '%field_name must have at least %count value.|%field_name must have at least %count values.'. Then, in your constraint validator, use the following code to add your violation:
if (count($value) < $constraint->count) {
$this->context->buildViolation($constraint->errorMessage)
->setParameter('%field_name', $value->getFieldDefinition()->label())
->setParameter('%count', $constraint->count)
// We will set the value, that is used to determine, if the error message should be shown
// in singular or plural form using ConstraintViolationBuilderInterface::setPlural().
->setPlural((int) $constraint->count)
->addViolation();
}Targeting Specific Properties in Complex Field Widgets
If you are working with fields/field widgets using multiple values or properties and you want to target a specific property or your constraint is validated and fails correctly, but the error message is not showing up on the form or the field marked is not the expected field, take a look at the property path of your violation. For constraints validating fields, this is just the field name by default. You may need to extend that to target a specific delta or property in the widget. Use ConstraintViolationBuilderInterface::atPath() to do that. Check out ValidReferenceConstraintValidator::validate() for an example.
Example
This example demonstrates the addition of a complex constraint that validates one field based on the value selected in another. In this custom entity, there are two date fields showing start time and end time. The constraint ensures that the value of the end time field is later than the selected start time.
Create the file StartEndTimeConstraint.php defining the constraint class in your custom module's /src/Plugin/Validation/Constraint/ directory. Assign your constraint a unique name. For example MyModuleStartEndTimeConstraint.
<?php
namespace Drupal\MY_MODULE\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Provides a StartEndTimeConstraint constraint.
*
* @Constraint(
* id = "MyModuleStartEndTimeConstraint",
* label = @Translation("StartEndTimeConstraint", context = "Validation"),
* )
*/
class StartEndTimeConstraint extends Constraint {
/**
* Constraint error message.
*
* @var string
*/
public $errorMessage = 'End time must be later than start time.';
}Create the file StartEndTimeConstraintValidator.php defining the constraint validation class in your custom module's /src/Plugin/Validation/Constraint/ directory.
<?php
namespace Drupal\MY_MODULE\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the StartEndTimeConstraint constraint.
*/
class StartEndTimeConstraintValidator extends ConstraintValidator {
/**
* Checks if end date is later than start date.
*
* @param mixed $field
* The field/value that should be validated.
* @param \Symfony\Component\Validator\Constraint $constraint
* The constraint for the validation.
*/
public function validate($field, Constraint $constraint) {
// Get field's parent entity to access other fields on this entity.
/** @var \Drupal\MY_MODULE\Entity\MY_CUSTOM_ENTITY_TYPE $entity */
$entity = $field->getEntity();
if ($entity &&
$entity->hasField('field_datetime_start') &&
$entity->hasField('field_datetime_end')
) {
// Get the datetime object in correct timezone of the starting time.
/** @var \Drupal\Core\Datetime\DrupalDateTime $dateTimeBegin */
$dateTimeBegin = $entity->field_datetime_start->date;
// Get the datetime object in correct timezone of the end time.
/** @var \Drupal\Core\Datetime\DrupalDateTime $dateTimeEnd */
$dateTimeEnd = $entity->field_datetime_end->date;
// Display an error message, if end time is not later than start time.
if ($dateTimeEnd <= $dateTimeBegin) {
$this->context->buildViolation($constraint->errorMessage)
->atPath('field_datetime_end')
->addViolation();
}
}
}
}
Finally use hook_entity_bundle_field_info_alter to add the constraint to the end time field in your module's .module file, e. g. MY_MODULE.module.
/**
* Alter bundle field definitions.
*
* Implements hook_entity_bundle_field_info_alter.
* This hook is only called when cache is rebuilt (drush cr).
*
* @param \Drupal\Core\Field\FieldDefinitionInterface[] $fields
* The array of bundle field definitions.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param string $bundle
* The bundle.
*
* @todo WARNING: This hook will be changed in
* https://www.drupal.org/node/2346347.
*/
function MY_MODULE_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
if ($bundle === 'MY_ENTITY_TYPE_ID') {
if (!empty($fields['field_datetime_end'])) {
// Add custom constraint to this field, that checks if end time is later than start time.
// The parameter to addConstraint() is not the class name, but the ID defined in the
// class annotation in StartEndTimeConstraint.php
$fields['field_datetime_end']->addConstraint(
MyModuleStartEndTimeConstraint::class
);
}
}
}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.