In Drupal, validators and constraints are used to ensure that data entered by the user meets specific requirements. Drupal provides a range of built-in validators and constraints, and developers can also create custom validators and constraints to suit their specific needs.

 In Drupal, the built-in validators and constraints can be found in the directory `core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint`

Validators and constraints

Our Development Plan: What We're Building

We are migrating data from Salesforce and need to ensure unique Salesforce IDs per entity. To accomplish this, we will develop a custom unique constraint and validator.

Now, To create the custom unique constraint and validator for the Salesforce ID field, we will follow a three-step process outlined in the following Drupal documentation:

"Defining Constraints and Validations on Entities and/or Fields Using Drupal's Entity Validation API"

Step 1: Define the Constraint

By defining constraints using the annotation provided by the 'Validation' library of Drupal core, we can ensure that any entered data meets specific conditions and adheres to the desired format or structure.

File:- /modules/custom/askhub_base/src/Plugin/Validation/Constraint/UniqueIdConstraint.php

<?php

namespace Drupal\askhub_base\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Checks that the submitted value unique among the eck.
 *
 * @Constraint(
 *   id = "UniqueId",
 *   label = @Translation("Unique Salesforce Id", context = "Validation"),
 *   type = "string"
 * )
 */
class UniqueIdConstraint extends Constraint {

  /**
   * The message that will be shown if the value is not unique.
   */
  public $notUnique = '%value is not unique';

}

In the above annotation

  • id - The constraint plugin ID.
  • label - The human-readable name of the constraint plugin.
  • type - DataType plugin IDs for which this constraint applies.

Step 2: Define Validator for the Constraint

Validators are used to check that a specific field contains valid data, based on criteria such as format, length, or character set. By default, our validator class should be named as [ConstraintClassName]Validator. so in this example, our validator class name will be UniqueIdConstraintValidator.

File:- /modules/custom/askhub_base/src/Plugin/Validation/Constraint/UniqueIdConstraintValidator.php

<?php

namespace Drupal\askhub_base\Plugin\Validation\Constraint;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Drupal\Core\Entity\EntityTypeManagerInterface;

/**
 * Validates the UniqueId constraint.
 */
class UniqueIdConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Creates a new UniqueIdConstraintValidator instance.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function validate($items, Constraint $constraint) {

    foreach ($items as $item) {
      // Next check if the value is unique.
      if (!$this->isUnique($item->value, $items)) {
        $this->context->addViolation($constraint->notUnique, ['%value' => $item->value]);
      }
    }

  }

  /**
   * Is unique?
   *
   * @param string $value
   *   Field value.
   * @param \Drupal\Core\Field\FieldItemList $items
   *   Items list.
   */
  private function isUnique($value, $items) {
    $count = 0;

    try {

      $field_name = $items->getFieldDefinition()->getName();
      $entity = $items->getEntity();
      $entity_type_id = $entity->getEntityTypeId();
      $bundle = $entity->bundle();

      // Query.
      $count = $this->entityTypeManager->getStorage($entity_type_id)
        ->getQuery()
        ->condition('type', $bundle)
        ->condition($field_name, $value)
        ->count()->execute();
    }
    catch (\Exception $e) {
      // Log error.
    }

    return !(bool) $count;
  }

}

Step 3: Set the Constraint to Field it Needs to Validate

We will use hook_entity_bundle_field_info_alter() to add our constraint to the entity field. There are other ways as well, and we can check the documentation above for them.

use Drupal\Core\Entity\EntityTypeInterface;

/**
 * Implements hook_entity_bundle_field_info_alter().
 */
function askhub_base_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() === 'grid_tracks' && $bundle === 'tracks') {
    if (isset($fields['field_salesforce_id'])) {
      // Use the ID as defined in the annotation of the constraint definition.
      $fields['field_salesforce_id']->addConstraint('UniqueId', []);
    }
  }
}

Output

Validators and constraints output