Commit adfa35d4 authored by Ben Mullins's avatar Ben Mullins
Browse files

Issue #3324150 by phenaproxima, Wim Leers, Gábor Hojtsy: Add validation...

Issue #3324150 by phenaproxima, Wim Leers, Gábor Hojtsy: Add validation constraints to config_entity.dependencies
parent 22a56817
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -240,6 +240,9 @@ config_dependencies_base:
      label: 'Configuration entity dependencies'
      sequence:
        type: string
        constraints:
          NotBlank: []
          ConfigExists: []
    content:
      type: sequence
      label: 'Content entity dependencies'
@@ -250,11 +253,21 @@ config_dependencies_base:
      label: 'Module dependencies'
      sequence:
        type: string
        constraints:
          NotBlank: []
          ExtensionName: []
          ExtensionExists: module
    theme:
      type: sequence
      label: 'Theme dependencies'
      sequence:
        type: string
        constraints:
          NotBlank: []
          ExtensionName: []
          ExtensionExists: theme
  constraints:
    ValidKeys: '<infer>'

config_dependencies:
  type: config_dependencies_base
@@ -263,6 +276,8 @@ config_dependencies:
    enforced:
      type: config_dependencies_base
      label: 'Enforced configuration dependencies'
  constraints:
    ValidKeys: '<infer>'

config_entity:
  type: mapping
+26 −0
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

namespace Drupal\Core\Config\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Checks that the value is the name of an existing config object.
 *
 * @Constraint(
 *   id = "ConfigExists",
 *   label = @Translation("Config exists", context = "Validation")
 * )
 */
class ConfigExistsConstraint extends Constraint {

  /**
   * The error message.
   *
   * @var string
   */
  public string $message = "The '@name' config does not exist.";

}
+51 −0
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

namespace Drupal\Core\Config\Plugin\Validation\Constraint;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Validates that a given config object exists.
 */
class ConfigExistsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

  /**
   * The config factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $configFactory;

  /**
   * Constructs a ConfigExistsConstraintValidator object.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory service.
   */
  public function __construct(ConfigFactoryInterface $config_factory) {
    $this->configFactory = $config_factory;
  }

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

  /**
   * {@inheritdoc}
   */
  public function validate(mixed $name, Constraint $constraint) {
    if (!in_array($name, $this->configFactory->listAll(), TRUE)) {
      $this->context->addViolation($constraint->message, ['@name' => $name]);
    }
  }

}
+50 −0
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

namespace Drupal\Core\Config\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Checks that config dependencies contain specific types of entities.
 *
 * @Constraint(
 *   id = "RequiredConfigDependencies",
 *   label = @Translation("Required config dependency types", context = "Validation")
 * )
 */
class RequiredConfigDependenciesConstraint extends Constraint {

  /**
   * The error message.
   *
   * @var string
   */
  public string $message = 'This @entity_type requires a @dependency_type.';

  /**
   * The IDs of entity types that need to exist in config dependencies.
   *
   * For example, if an entity requires a filter format in its config
   * dependencies, this should contain `filter_format`.
   *
   * @var string[]
   */
  public array $entityTypes = [];

  /**
   * {@inheritdoc}
   */
  public function getRequiredOptions() {
    return ['entityTypes'];
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultOption() {
    return 'entityTypes';
  }

}
+80 −0
Original line number Diff line number Diff line
<?php

declare(strict_types = 1);

namespace Drupal\Core\Config\Plugin\Validation\Constraint;

use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Config\Entity\ConfigEntityTypeInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

/**
 * Validates the RequiredConfigDependencies constraint.
 */
class RequiredConfigDependenciesConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

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

  /**
   * Constructs a RequiredConfigDependenciesConstraintValidator object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   */
  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(mixed $entity, Constraint $constraint) {
    assert($constraint instanceof RequiredConfigDependenciesConstraint);

    // Only config entities can have config dependencies.
    if (!$entity instanceof ConfigEntityInterface) {
      throw new UnexpectedTypeException($entity, ConfigEntityInterface::class);
    }

    $config_dependencies = $entity->getDependencies()['config'] ?? [];

    foreach ($constraint->entityTypes as $entity_type_id) {
      $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);

      if (!$entity_type instanceof ConfigEntityTypeInterface) {
        throw new LogicException("'$entity_type_id' is not a config entity type.");
      }

      // Ensure the current entity type's config prefix is found in the config
      // dependencies of the entity being validated.
      $pattern = sprintf('/^%s\\.\\w+/', $entity_type->getConfigPrefix());
      if (!preg_grep($pattern, $config_dependencies)) {
        $this->context->addViolation($constraint->message, [
          '@entity_type' => $entity->getEntityType()->getSingularLabel(),
          '@dependency_type' => $entity_type->getSingularLabel(),
        ]);
      }
    }
  }

}
Loading