Mar 31 2016
Mar 31

In the previous article of this series we’ve started our dive into the Entity Validation and Typed Data APIs. We’ve seen how DataType plugins interact with data definitions and how various constraints can be added to the latter at multiple levels and extension points.

Drupal 8 logo

In this part, we will cover the aspect of actual validation and violation handling. In addition, we will write our own constraint and validator so that we can use custom behaviors in the data validation process.

Validation and Violation Handling

Even though we don’t yet know exactly how constraints are built, we’ve seen how they can be added to Typed Data definitions, including entity fields. Let us now see how we can validate the entities and handle possible violations we find.

When talking about Typed Data we’ve already seen how the validate() method can be called on the DataType plugin instance which holds a data definition. When it comes to entities, this can happen both at entity and field levels.

For instance, we can validate the entire entity using the validate() method:

$entity->set('title', 'this is too long of a title');
$violations = $entity->validate();

In our previous article, we added the Length constraint to Node titles to prevent title strings longer than 5 characters. If that is still in place and we run the code above, the validation should obviously fail. The $violations object is now, however, an EntityConstraintViolationListInterface instance which provides some helper methods for accessing violation data specific to Drupal content entities. It’s worth looking into that interface for all the helper methods available.

To get a list of Entity level violations we can use the getEntityViolations() method but we can also loop through all of them. Once we have our individual ConstraintViolationInterface instances, we can inspect them for what went wrong. For instance, we can get the error message with getMessage(), the property path that failed with getPropertyPath() and the invalid value with getInvalidValue(), among other useful things.

When it comes to fields, the property path is in the following format: title.0.value. This includes the field name, the key (delta) of the individual field item in the list and the actual property name. This represents the property path of our violation above.

Apart from calling validation on the entire entity (which may be superfluous at times), we can also do so directly on each field:

$entity->set('title', 'this is too long of a title');
$violations = $entity->get('title')->validate();

In this case, $violations is again an instance of ConstraintViolationListInterface and can be looped over to inspect each violation. This time, though, the property path changes to no longer include the field name: 0.value.

And lastly, we can even validate the individual items in the list:

$violations = $entity->get('title')->get(0)->validate();

As we can expect, the difference now is that the property path will only show value in the violation since we know exactly what we are validating: the first data definition in the list.

Constraints and Validators

We only touched upon the aspect of constraints and validators but let us better understand how they work by creating one ourselves. We will create one single constraint and validator but that can be used for both Node entities and their fields. It’s always better to have constraints targeted to the data definition we want but for the sake of brevity, we’ll do it all in one constraint to see how all these options could be handled.

The business case of our example is to have a constraint that we can apply to any string-based content entity field that would force the string to contain a certain alphanumeric code. The latter will be passed on as an option so it can be reused. If it’s applied to a Node entity, we want to make sure the title of the node contains the code. So let’s get started.

First, inside our demo module, which can be found in this git repository, we need to create the Validation folder inside the Plugin folder of our namespace (the src/ directory). Inside that, we need the Constraint folder. This is because constraints are plugins expected to be defined in there.

Second, inside Constraint/, we can create our constraint class:

<?php



namespace Drupal\demo\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\MissingOptionsException;



class HasCodeConstraint extends Constraint {

  
  public $messageNoCode = 'The string <em>%string</em> does not contain the necessary code: %code.';

  
  public $code;

  
  public function __construct($options = NULL) {
    if ($options !== NULL && is_string($options)) {
      parent::__construct(['code' => $options]);
    }

    if ($options !== NULL && is_array($options) && isset($options['code'])) {
      parent::__construct($options);
    }

    if ($this->code === NULL) {
      throw new MissingOptionsException('The code option is required', __CLASS__);
    }
  }
}

As we mentioned earlier, the constraint plugin class is relatively simple. It has the expected annotation which, among boilerplate metadata, also specifies what type of data this constraint can be applied to. We chose both a simple string and the Node entity type. We then declare two public properties: a message to be used when the constraint fails (with placeholders being populated inside the validator) and the actual code to be checked for (populated by the constructor of the parent Constraint class). Inside our constructor, we check if the options passed are a string or array (just to be a bit flexible) and throw an exception if the code parameter is not passed as an option in any shape or form.

One of the tasks of the parent Constraint class is to specify which class will be used to validate this constraint. By default, it is a class named like the constraint itself but with Validate appended at the end (HasCodeConstraintValidator). If we wanted to create a differently named and/or namespaced class, we would have to override the validatedBy() method and return the fully qualified name of the class we want.

Let’s now see the HasCodeConstraintValidator class since for us the default is fine:

<?php



namespace Drupal\demo\Plugin\Validation\Constraint;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;


class HasCodeConstraintValidator extends ConstraintValidator {

  
  protected $context;

  
  public function validate($data, Constraint $constraint) {
    if (!$constraint instanceof HasCodeConstraint) {
      throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\HasCodeConstraint');
    }

       if ($data instanceof NodeInterface) {
      $this->validateNodeEntity($data, $constraint);
      return;
    }

       if ($data instanceof FieldItemListInterface) {
      foreach ($data as $key => $item) {
        $violation = $this->validateString($item->value, $constraint);
        if ($violation instanceof ConstraintViolationBuilderInterface) {
          $violation->atPath('title')->addViolation();
        }
      }
      return;
    }

       if (is_string($data)) {
      $violation = $this->validateString($data, $constraint);
      if ($violation instanceof ConstraintViolationBuilderInterface) {
        $violation->addViolation();
        return;
      }
    }
  }

  
  protected function validateNodeEntity($node, $constraint) {
    foreach ($node->title as $item) {
      $violation = $this->validateString($item->value, $constraint);
      if ($violation instanceof ConstraintViolationBuilderInterface) {
        $violation->atPath('title')
          ->addViolation();
      }
    }
  }

  
  protected function validateString($string, $constraint) {
    if (strpos($string, $constraint->code) === FALSE) {
      return $this->context->buildViolation($constraint->messageNoCode, array('%string' => $string, '%code' => $constraint->code));
    }
  }

}

The main job of this class is to implement the validate() method and build violations onto the current execution context if the data passed to it is somehow invalid. The latter can be more than one type of data, depending on what the constraint is applied to.

In our example, we use the constraint for entities, field item lists and primitive data as well. This is just to save us some space. But the logic is that if a Node entity is passed, the code is checked in its title while for the the field items an iteration is performed to check the values of the fields. And of course, the constraint can also be added to individual field items in which case the $data variable would be the value of the data definition.

The $context property has a handy buildViolation() method which allows us to specify a number of things related to what actually failed (path, message, etc).

So if we want to make use of this constraint, we can apply what we learned earlier and do one of the following 3 things:

Inside hook_entity_type_alter():

$node = $entity_types['node'];
$node->addConstraint('HasCode', ['code' => 'UPK']);

This adds the constraint to the Node entities so all node titles now have to contain the code UPK.

Inside hook_entity_base_field_info_alter() or hook_entity_bundle_field_info_alter(), one of the following two lines:

$title->addConstraint('HasCode', ['code' => 'UPK']);
$title->addPropertyConstraints('value', ['HasCode' => ['code' => 'UPK']]);

Where $title is a field definition and the first case adds the constraint to the entire list of items while the latter adds it straight to the individual item.

These three possibilities cover the three cases the constraint validator handles in our example. And that is pretty much it. Not too difficult.

In this article, we’ve continued our dive into the Entity Validation API by looking at two things: validation and violation handling, and the creation of custom constraints. We’ve seen that once applied onto entities or field data definitions, constraints can be very easily validated and inspected for violations. This API borrows a lot from Symfony and makes it a breeze to decouple validation from the Form API.

Also easy, as we’ve seen, is to extend the existing pool of validation criteria available. This is done via constraint plugins, all coupled with their own validators and which can be written in very reusable ways. It’s very interesting to play around with these and see how all the pieces interact. And it is also a great learning experience.

Daniel Sipos

Meet the author

Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.
Jul 15 2010
Jul 15

Ajax form validation is one of those features typically tasked to a Drupal Developer. Here is how it is accomplished in Drupal 6.

Let's say we were adding Ajax form validation to the user registration page. You need to create a jQuery event listener in a javascript file.

$(document).ready(function(){   
  $("#edit-name").blur(function () {
    $.post('modulename/validate', { name: this.value }, function(data) {
      $('#edit-name-wrapper .description').append(data);
    });
  });
});

What this does is when the user clicks away from the username field it takes the value of that field and does an HTTP POST to modulename/validator (we will actually set up this url in a minute). If the name isn't valid, or we need to display some sort of error message, we append this to the description, or you could just define a new div to place stuff into.

Now, let's set up our custom module. You need to create a modulename directory in sites/all/modules then inside that directory create 2 files modulename.module and modulename.info with a little bit of information about your module. See http://drupal.org/node/206756 for reference. Now open up your .module file and let's create the url.

/**
* Implementation of hook_menu().
*/
function modulename_menu () {
  $items['modulename/validate'] = array(
    'page callback' => "modulename_ajax_validate",
    'page arguments' => array(2),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}

function modulename_ajax_validate ($string = '') {
  if ($string) {
    //do your validation here...say we didn't want to allow underscores in the username
    if(strstr($string,"_")) {
      $errors[] = t("Underscores in your username aren't allowed.");
    }
    foreach ($errors as $error) {
      print "$error <br />";
    }
  }
}

What this does is creates a path for the validation (modulename/validate) and then calls a function (there's nothing special about this function name, you could have called it foo_validateme) passing it the string parameter parameter. Then if the validation fails, it adds a string to the errors array. If there are errors, loop through the errors and print them out. This string gets passed back to our jQuery handler we wrote above as the variable name: data and gets printed out and appended to the description of the username field.

You could try this example, but it also might be prudent to have a look at the Ajax Project on Drupal.org.

Dear fellow Drupal Developer, you are now armed with the awesomeness of Ajax. Happy validating!

UPDATE: Several readers have recommended using the jQuery Validation plugin. This is a great plugin and have used it many times. I suggest you check it out. Also, there is a good example of doing AHAH in the examples module on drupal.org. Another reader commented that it would be a great idea to use the same validation function name so that the form would be validated by the same code using Ajax or just normal form submission. For this to work, you would need to get the name right using the Drupal Form API. Here is an example of how to do that.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web