Entity validation in Drupal 8 - part 1 - how validation works

Drupal 8 has its entity validation separate and decoupled from the typical validation given by its form API. This is done for a lot of reasons. For one, entities might get added from other non UI means, like via the REST API, or programmatically, while importing data from an external source. Under these circumstances, the entity validation API comes in handy.

Drupal 8's validation API uses the Symfony validator component.Each validation mechanism can be at the entity level(composite), field level or entity property level. Validation can be specified by multiple means.

1.While creating the entity as a part of the annotation.

Ex: the Comment entity has a validation constraint which imposes a restriction where the name of the anonymous comment author cannot match the name of any registered user. This is implemented using CommentNameConstraint and specified in the Comment entity annotation.

 *   bundle_entity_type = "comment_type",
 *   field_ui_base_route  = "entity.comment_type.edit_form",
 *   constraints = {
 *     "CommentName" = {}
 *   }
 * )
 */
class Comment extends ContentEntityBase implements CommentInterface {

2.Inside the entity class's baseFieldDefinitions().

Ex: The User entity has a constraint where each user name should be a unique value.

$fields['name'] = BaseFieldDefinition::create('string')
  ->setLabel(t('Name'))
  ->setDescription(t('The name of this user.'))
  ->setRequired(TRUE)
  ->setConstraints(array(
    // No Length constraint here because the UserName constraint also covers
    // that.
    'UserName' => array(),
    'UserNameUnique' => array(),
  ));

We will see what BaseFieldDefinition means in a future post. For now, all you have to understand is, the above line places a validation constraint that the name property of every user object should be unique.

3.Entity validation constraints can be placed on existing entities from other modules via hooks.

This implements hook_entity_type_alter.

function my_module_name_entity_type_alter(array &$entity_types) {
  $node = $entity_types['node'];
  $node->addConstraint('CustomPluginName', ['plugin', 'options']);
}

We shall be creating one such validation constraint on the node entity shortly.

A validation component consists of 2 parts.

The constraint contains the metadata/rules required for the validation, the messages to show as to what exactly got invalidated, and a pointer to the validation class, whose default value is a "Validator" string appended to the fully qualified constraint class name.

/**
 * Returns the name of the class that validates this constraint.
 *
 * By default, this is the fully qualified name of the constraint class
 * suffixed with "Validator". You can override this method to change that
 * behaviour.
 *
 * @return string
 */
public function validatedBy()
{
    return get_class($this).'Validator';
}

The validation class contains the actual validation implementation. For example, a "unique name" constraint's validator will iterate through all entities in the database to ensure that the name of the entity being validated is not used by any other entity. The validator class also has access to the constraint class metadata, messages etc. It should, at minimum, implement the validate method, which takes in the object to be validated(string, entity etc.) and the associated constraint. Upon failing the validation, this method returns an object of type ConstraintViolationInterface. This gives all the information as to why the validation failed, where exactly it failed, the invalid value etc.

Let's see how a node can be validated and the validation errors consumed with the below example.

use Drupal\node\Entity\Node;

$node = Node::create([ 'title' => 'New article', 'type' => 'article']);
$node->field_email = 'foobar';
$violations = $node->validate();
if ($violations->count() > 0) {
  foreach($violations as $violation) {
    print_r($violation->getMessage()->render());
    print("\n");
    print_r($violation->getPropertyPath());
    print("\n");
    print_r($violation->getInvalidValue());
    print("\n");
  }
 }

Assuming you have an email field which goes by the machine name field_email, if you run this code using drush scr command in a Drupal 8 setup, your output should be very similar to this.

$ drush scr node-validate.php
This value is not a valid email address.
field_email.0.value
foobar

The getPropertyPath give the field name and the delta as to where the violation occurs.

Now that we got a hang of how entity validation works, let's create our own validation constraint in the next post.

Drupal 8 module development

Like what you read?

Then you will definitely love my new book about Drupal 8 module development.