Drupal 8 custom validation of multiple entity fields
In a recent Drupal 8.6 project, I was using the media entity type to add custom fields to pdf documents. These pdf files contain two fields: a select list and a text field. When a specific select option is chosen, the text field becomes required. I could have used a form alter function to add my custom validation for these fields, but this would limit my validation to just one form. This wouldn't work for entities that were created with JSON API or other methods.
I began by reading a few articles online that nudged me in the right direction:
- https://www.drupal.org/docs/8/api/entity-validation-api/providing-a-cus…
- https://www.bluestatedigital.com/ideas/drupal8-data-validation/
These were great in helping me to get my constraints setup within my custom module. However, they were a bit limiting because they were written for validating individual fields and didn't have the multiple field dependency that I needed.
My search continued and I came upon this posting, https://drupal.stackexchange.com/a/215280, which led me to the comment module in core. Here, I found the CommentNameConstraint.php and CommentNameConstraintValidator.php files. A working example of dependent fields being validated.
Using this as a model, I wrote my constraint and validator classes within my custom module.
namespace Drupal\mymodule\Plugin\Validation\Constraint;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
/**
* Verify that the order number is valid.
*
* @Constraint(
* id = "OrderNumber",
* label = @Translation("Order Number", context = "Validation"),
* type = "entity:media"
* )
*/
class OrderNumberConstraint extends CompositeConstraintBase {
/**
* @var string
*/
public $orderNumberRequired = 'The order number is required when the resolution is entered.';
/**
* @var string
*/
public $orderNumberEmpty = 'The order number should not contain a value.';
/**
* {@inheritdoc}
*/
public function coversFields() {
return ['field_order_number', 'field_resolution'];
}
}
<?php
namespace Drupal\mymodule\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Validates the Order Number constraint.
*/
class OrderNumberConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
/**
* Validator 2.5 and upwards compatible execution context.
*
* @var \Symfony\Component\Validator\Context\ExecutionContextInterface
*/
protected $context;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('entity.manager')->getStorage('media'));
}
/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
if ($entity->hasField('field_resolution') && $entity->hasField('field_order_number')) {
$resolution = $entity->get('field_resolution')->getValue();
$resolution = (count($resolution) == 1 ? $resolution[0]['value'] : FALSE);
$ordernumber = $entity->get('field_order_number')->getValue();
$ordernumber = (count($ordernumber) == 1 ? $ordernumber[0]['value'] : FALSE);
// If the resolution is other, require the order number.
if ($resolution == 'other') {
// If there is nothing in the order number, add violation.
if (!strlen($ordernumber)) {
$this->context->buildViolation($constraint->orderNumberRequired)
->atPath('field_order_number')
->addViolation();
}
}
else {
// If the resolution is not other, clear the order number.
if (strlen($ordernumber)) {
$this->context->buildViolation($constraint->orderNumberEmpty)
->atPath('field_order_number')
->addViolation();
}
}
}
// If the fields do not exist, do not validate anything.
return NULL;
}
}
So, my constraints have been created. However, we need to attach the custom constraint to the entity. To do this, we needed to implement hook_entity_type_build().
/**
* Implements hook_entity_type_build().
*/
function mymodule_entity_type_build(array &$entity_types) {
// Add our custom validation to the order number.
$entity_types['media']->addConstraint('OrderNumber');
}
There it is, a working constraint based on two fields that will work with both using the GUI to create the entity or programmatically adding the entity.
Next step, writing automated tests to validate this is working properly.
Allowed memory error on entity validation
Submitted by Ryan (not verified) on Fri, 12/06/2019 - 15:20
Allowed memory error on entity validation
I've followed these steps and my dev site is recognizing that the custom validator exists. However, when I submit the form to update an entity, my site white screens and the error log reports that the memory limit of 512MB has been exceeded by 400MB. Did you encounter this issue or is something else gone wrong for me?
Allowed memory error on entity validation - User error
Submitted by Ryan (not verified) on Fri, 12/06/2019 - 17:19
Allowed memory error on entity validation - User error
A-ha! I had an errant print_r($entity) from debugging that I had failed to remove. That's what caused the memory error. Thanks for the guide!