Score:2

How to do dependency injection for ConstraintValidator?

cn flag

After upgrading to Drupal 10, running the coding standards check suggested that I could use DI in my validation constraints.

For example, this simple one:

<?php

declare(strict_types=1);

namespace Drupal\my_module\Plugin\Validation\Constraint;

use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Validates the DateTimeMidnightOnly constraint.
 */
class DateTimeMidnightOnlyValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate(mixed $value, Constraint $constraint): void {
    foreach ($value as $item) {
      if (!$this->isMidnight($item->value)) {
        $item_value = $item->value;
        \Drupal::logger('my_custom_error_channel')->error("Invalid midnight timestamp! $item_value");
        $this->context->addViolation($constraint->notMidnight, ['%value' => $item_value]);
      }
    }
  }

  /**
   * Is the DateTime string midnight?
   *
   * @param string $value
   *   The DateTime string.
   *
   * @returns bool
   *   TRUE if it is a midnight string.
   */
  private function isMidnight(string $value): bool {
    return str_ends_with($value, 'T00:00:00');
  }

}

So I tried injecting the logger:

  /**
   * Logger factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected LoggerChannelFactoryInterface $loggerFactory;

  /**
   * Constructs DateTimeMidnightOnlyValidator.
   *
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory.
   */
  public function __construct(LoggerChannelFactoryInterface $logger_factory) {
    $this->loggerFactory = $logger_factory;
  }

This results in an error:

ArgumentCountError: Too few arguments to function Drupal\custom_module\Plugin\Validation\Constraint\DateTimeMidnightOnlyValidator::__construct(), 0 passed in /var/www/html/web/core/lib/Drupal/Core/DependencyInjection/ClassResolver.php on line 31 and exactly 1 expected

So I need to pass the argument-- how do I do that? For custom services I know I can use mymodule.services.yml, and for totally overriding core services I can use MyModuleServiceProvider.php, but what about validation constraints?

Score:4
cn flag

To pass the argument implement ContainerInjectionInterface::create().

For example:

class TaxonomyTermHierarchyConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

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

  /**
   * Creates a new TaxonomyTermHierarchyConstraintValidator 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')
    );
  }

...

Example from /core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraintValidator.php

I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.