diff --git a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php index 530e7fc5a6df76c65ac94896624d2496491d8bd7..357c3ab21c314e6a68e305946d8c9e3cd7f5a80b 100644 --- a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php +++ b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php @@ -81,6 +81,28 @@ public function validateDefinition(array $definition, bool $enforce_schemas): bo 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) { diff --git a/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php b/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php index 7c76a1830558981e7fb37b261ab313a93ff46396..d24d2df7989ccf8fd430dcaf1e8d2e1780e49ee8 100644 --- a/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php @@ -65,24 +65,43 @@ public function testValidateDefinitionInvalid(array $definition): void { /** * Data provider with invalid component definitions. * - * @return array - * The data. + * @return \Generator + * Returns the generator with the invalid definitions. */ - public static function dataProviderValidateDefinitionInvalid(): array { + public static function dataProviderValidateDefinitionInvalid(): \Generator { $valid_cta = static::loadComponentDefinitionFromFs('my-cta'); + $cta_with_missing_required = $valid_cta; unset($cta_with_missing_required['path']); + yield 'missing required' => [$cta_with_missing_required]; + $cta_with_invalid_class = $valid_cta; $cta_with_invalid_class['props']['properties']['attributes']['type'] = 'Drupal\Foo\Invalid'; + yield 'invalid class' => [$cta_with_invalid_class]; + $cta_with_invalid_enum = array_merge( $valid_cta, ['extension_type' => 'invalid'], ); - return [ - [$cta_with_missing_required], - [$cta_with_invalid_class], - [$cta_with_invalid_enum], - ]; + yield 'invalid enum' => [$cta_with_invalid_enum]; + + // A list of property types that are not strings, but can be provided via + // YAML. + $non_string_types = [NULL, 123, 123.45, TRUE]; + foreach ($non_string_types as $non_string_type) { + $cta_with_non_string_prop_type = $valid_cta; + $cta_with_non_string_prop_type['props']['properties']['text']['type'] = $non_string_type; + yield "non string type ($non_string_type)" => [$cta_with_non_string_prop_type]; + + // Same, but as a part of the list of allowed types. + $cta_with_non_string_prop_type['props']['properties']['text']['type'] = ['string', $non_string_type]; + yield "non string type ($non_string_type) in a list of types" => [$cta_with_non_string_prop_type]; + } + + // The array is a valid value for the 'type' parameter, but it is not + // allowed as the allowed type. + $cta_with_non_string_prop_type['props']['properties']['text']['type'] = ['string', []]; + yield 'non string type (Array)' => [$cta_with_non_string_prop_type]; } /**