From f0b4dd2ba4ffcc0966ffb9a824ae8c6f1084e0d4 Mon Sep 17 00:00:00 2001 From: Lee Rowlands Date: Thu, 24 Oct 2024 11:45:49 +1000 Subject: [PATCH] Issue #3462156 by niklan, vensires, smustgrave, quietone, wim leers: Throw an exception if a component property's `type` contains non-string values --- .../Theme/Component/ComponentValidator.php | 22 ++++++++++++ .../Component/ComponentValidatorTest.php | 35 ++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php index 530e7fc5a6d..357c3ab21c3 100644 --- a/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php +++ b/core/lib/Drupal/Core/Theme/Component/ComponentValidator.php @@ -81,6 +81,28 @@ class ComponentValidator { 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 7c76a183055..d24d2df7989 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 @@ class ComponentValidatorTest extends TestCase { /** * 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]; } /**