function ValidKeysConstraintValidator::validate

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidKeysConstraintValidator.php \Drupal\Core\Validation\Plugin\Validation\Constraint\ValidKeysConstraintValidator::validate()

File

core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidKeysConstraintValidator.php, line 23

Class

ValidKeysConstraintValidator
Validates the ValidKeys constraint.

Namespace

Drupal\Core\Validation\Plugin\Validation\Constraint

Code

public function validate(mixed $value, Constraint $constraint) {
  assert($constraint instanceof ValidKeysConstraint);
  if (!is_array($value)) {
    // If the value is NULL, then the `NotNull` constraint validator will
    // set the appropriate validation error message.
    // @see \Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraintValidator
    if ($value === NULL) {
      return;
    }
    throw new UnexpectedTypeException($value, 'array');
  }
  // Indexed arrays are invalid by definition. array_is_list() returns TRUE
  // for empty arrays, so only do this check if $value is not empty.
  if ($value && array_is_list($value)) {
    $this->context
      ->addViolation($constraint->indexedArrayMessage);
    return;
  }
  $mapping = $this->context
    ->getObject();
  assert($mapping instanceof Mapping);
  $resolved_type = $mapping->getDataDefinition()
    ->getDataType();
  $valid_keys = $constraint->getAllowedKeys($this->context);
  $dynamically_valid_keys = $mapping->getDynamicallyValidKeys();
  $all_dynamically_valid_keys = array_merge(...array_values($dynamically_valid_keys));
  // Statically valid: keys that are valid for all possible types matching the
  // type definition of this mapping.
  // For example, `block.block.*:settings` has the following statically valid
  // keys: id, label, label_display, provider, status, info, view_mode and
  // context_mapping.
  // @see \Drupal\KernelTests\Config\Schema\MappingTest::providerMappingInterpretation()
  $invalid_keys = array_diff(array_keys($value), $valid_keys, $all_dynamically_valid_keys);
  foreach ($invalid_keys as $key) {
    $this->context
      ->buildViolation($constraint->invalidKeyMessage)
      ->setParameter('@key', $key)
      ->atPath((string) $key)
      ->setInvalidValue($key)
      ->addViolation();
  }
  // Dynamically valid: keys that are valid not for all possible types, but
  // for the actually resolved type definition of this mapping (in addition to
  // the statically valid keys).
  // @see \Drupal\Core\Config\Schema\Mapping::getDynamicallyValidKeys()
  if (!empty($all_dynamically_valid_keys)) {
    // For example, `block.block.*:settings` has the following dynamically valid
    // keys when the block plugin is `system_branding_block`:
    // - use_site_logo
    // - use_site_name
    // - use_site_slogan
    // @see \Drupal\KernelTests\Config\Schema\MappingTest::providerMappingInterpretation()
    $resolved_type_dynamically_valid_keys = $dynamically_valid_keys[$resolved_type] ?? [];
    // But if the `local_tasks_block` plugin is being used, then the
    // dynamically valid keys are:
    // - primary
    // - secondary
    // And for the `block.settings.search_form_block` plugin the dynamically
    // valid keys are:
    // - page_id
    // To help determine which keys are dynamically invalid, gather all keys
    // except for those for the actual resolved type of this mapping.
    // @see \Drupal\Core\Config\Schema\Mapping::getPossibleTypes()
    $other_types_valid_keys = array_diff($all_dynamically_valid_keys, $resolved_type_dynamically_valid_keys);
    $dynamically_invalid_keys = array_intersect(array_keys($value), $other_types_valid_keys);
    foreach ($dynamically_invalid_keys as $key) {
      $this->context
        ->addViolation($constraint->dynamicInvalidKeyMessage, [
        '@key' => $key,
      ] + self::getDynamicMessageParameters($mapping));
    }
  }
  // All keys are optional by default (meaning they can be omitted). This is
  // unintuitive and contradicts Drupal core's documentation.
  // @see https://www.drupal.org/node/2264179
  // To gradually evolve configuration schemas in the Drupal ecosystem to be
  // validatable, this needs to be clarified in a non-disruptive way. Any
  // config schema type definition — that is, a top-level entry in a
  // *.schema.yml file — can opt into stricter behavior, whereby a key is
  // required unless it specifies `requiredKey: false`, by adding
  // `FullyValidatable` as a top-level validation constraint.
  // @see https://www.drupal.org/node/3364108
  // @see https://www.drupal.org/node/3364109
  $root_type = $this->context
    ->getObject()
    ->getRoot()
    ->getDataDefinition()
    ->getDataType();
  $root_type_has_opted_in = FALSE;
  foreach ($this->context
    ->getObject()
    ->getRoot()
    ->getConstraints() as $c) {
    if ($c instanceof FullyValidatableConstraint) {
      $root_type_has_opted_in = TRUE;
      break;

    }
  }
  // Return early: do not generate validation errors for keys that are
  // required.
  if (!$root_type_has_opted_in) {
    return;
  }
  // If this is a dynamically typed property path, then not only must the
  // (absolute) root type be considered, but also the (relative) static root
  // type: the resolved type.
  // For example, `block.block.*:settings` has a dynamic type defined:
  // `block.settings.[%parent.plugin]`, but `block.block.*:plugin` does not.
  // Consequently, the value at the `plugin` property path depends only on the
  // `block.block.*` config schema type and hence only that config schema type
  // must have the `FullyValidatable` constraint, because it defines which
  // keys are required.
  // In contrast, the `block.block.*:settings` property path depends on
  // whichever dynamic type `block.settings.[%parent.plugin]` resolved to, to
  // be able to know which keys are required. Therefore that resolved type
  // determines which keys are required and whether it is fully validatable.
  // So for example the `block.settings.system_branding_block` config schema
  // type would also need to have the `FullyValidatable` constraint to
  // consider its schema-defined keys to be required:
  // - use_site_logo
  // - use_site_name
  // - use_site_slogan
  $static_type_root = TypedConfigManager::getStaticTypeRoot($this->context
    ->getObject());
  $static_type_root_type = $static_type_root->getDataDefinition()
    ->getDataType();
  if ($root_type !== $static_type_root_type) {
    $root_type_has_opted_in = FALSE;
    foreach ($static_type_root->getConstraints() as $c) {
      if ($c instanceof FullyValidatableConstraint) {
        $root_type_has_opted_in = TRUE;
        break;

      }
    }
    // Return early: do not generate validation errors for keys that are
    // required.
    if (!$root_type_has_opted_in) {
      return;
    }
  }
  $required_keys = array_intersect($mapping->getRequiredKeys(), $constraint->getAllowedKeys($this->context));
  // Statically required: same principle as for "statically valid" above, but
  // this time restricted to the subset of statically valid keys that do not
  // have `requiredKey: false`.
  $statically_required_keys = array_diff($required_keys, $all_dynamically_valid_keys);
  $missing_keys = array_diff($statically_required_keys, array_keys($value));
  foreach ($missing_keys as $key) {
    $this->context
      ->addViolation($constraint->missingRequiredKeyMessage, [
      '@key' => $key,
    ]);
  }
  // Dynamically required: same principle as for "dynamically valid" above,
  // but this time restricted to the subset of dynamically valid keys that do
  // not have `requiredKey: false`.
  $dynamically_required_keys = array_intersect($required_keys, $all_dynamically_valid_keys);
  $missing_dynamically_required_keys = array_diff($dynamically_required_keys, array_keys($value));
  foreach ($missing_dynamically_required_keys as $key) {
    $this->context
      ->addViolation($constraint->dynamicMissingRequiredKeyMessage, [
      '@key' => $key,
    ] + self::getDynamicMessageParameters($mapping));
  }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.