function ComponentValidator::validateDefinition

Same name in this branch
  1. 11.x core/modules/sdc/src/Component/ComponentValidator.php \Drupal\sdc\Component\ComponentValidator::validateDefinition()
Same name in other branches
  1. 10 core/modules/sdc/src/Component/ComponentValidator.php \Drupal\sdc\Component\ComponentValidator::validateDefinition()
  2. 10 core/lib/Drupal/Core/Theme/Component/ComponentValidator.php \Drupal\Core\Theme\Component\ComponentValidator::validateDefinition()

Validates the component metadata file.

A valid component metadata file can be validated against the metadata-author.schema.json, plus the ability of classes and interfaces in the `type` property.

Parameters

array $definition: The definition to validate.

bool $enforce_schemas: TRUE if schema definitions are mandatory.

Return value

bool TRUE if the component is valid.

Throws

\Drupal\Core\Render\Component\Exception\InvalidComponentException

File

core/lib/Drupal/Core/Theme/Component/ComponentValidator.php, line 55

Class

ComponentValidator
Validates a component based on its definition and the component schema.

Namespace

Drupal\Core\Theme\Component

Code

public function validateDefinition(array $definition, bool $enforce_schemas) : bool {
    // First ensure there are no name collisions between props and slots.
    $prop_names = array_keys($definition['props']['properties'] ?? []);
    $slot_names = array_keys($definition['slots'] ?? []);
    $collisions = array_intersect($prop_names, $slot_names);
    if ($collisions) {
        $message = sprintf('The component "%s" declared [%s] both as a prop and as a slot. Make sure to use different names.', $definition['id'], implode(', ', $collisions));
        throw new InvalidComponentException($message);
    }
    // If the validator isn't set, then the validation library is not installed.
    if (!$this->validator) {
        return TRUE;
    }
    // Detect the props with a type class, and validate that the class exists.
    $schema = $definition['props'] ?? NULL;
    if (!$schema) {
        if ($enforce_schemas) {
            throw new InvalidComponentException(sprintf('The component "%s" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.', $definition['id']));
        }
        return TRUE;
    }
    // If there are no props, force casting to object instead of array.
    if (($schema['properties'] ?? NULL) === []) {
        $schema['properties'] = new \stdClass();
    }
    // Ensure that all property types are strings. For example, a null value
    // will not automatically convert to 'null', which will lead to a PHP error
    // that is hard to trace back to the property.
    $non_string_props = [];
    \array_walk($prop_names, function (string $prop) use (&$non_string_props, $schema) {
        $type = $schema['properties'][$prop]['type'];
        $types = !\is_array($type) ? [
            $type,
        ] : $type;
        $non_string_types = \array_filter($types, static fn(mixed $type) => !\is_string($type));
        if ($non_string_types) {
            $non_string_props[] = $prop;
        }
    });
    if ($non_string_props) {
        throw new InvalidComponentException(\sprintf('The component "%s" uses non-string types for properties: %s.', $definition['id'], \implode(', ', $non_string_props)));
    }
    $classes_per_prop = $this->getClassProps($schema);
    $missing_class_errors = [];
    foreach ($classes_per_prop as $prop_name => $class_types) {
        // For each possible type, check if it is a class.
        $missing_classes = array_filter($class_types, static fn(string $class) => !class_exists($class) && !interface_exists($class));
        $missing_class_errors = [
            $missing_class_errors,
            array_map(static fn(string $class) => sprintf('Unable to find class/interface "%s" specified in the prop "%s" for the component "%s".', $class, $prop_name, $definition['id']), $missing_classes),
        ];
    }
    // Remove the non JSON Schema types for validation down below.
    $definition['props'] = $this->nullifyClassPropsSchema($schema, $classes_per_prop);
    $definition_object = Validator::arrayToObjectRecursive($definition);
    $this->validator
        ->validate($definition_object, (object) [
        '$ref' => 'file://' . dirname(__DIR__, 5) . '/assets/schemas/v1/metadata-full.schema.json',
    ]);
    if (empty($missing_class_errors) && $this->validator
        ->isValid()) {
        return TRUE;
    }
    $message_parts = array_map(static fn(array $error): string => sprintf("[%s] %s", $error['property'], $error['message']), $this->validator
        ->getErrors());
    $message_parts = [
        $message_parts,
        $missing_class_errors,
    ];
    $message = implode("/n", $message_parts);
    // Throw the exception with the error message.
    throw new InvalidComponentException($message);
}

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