From 156ff491e1e4580790ea0a386a2bd5756af8cfe6 Mon Sep 17 00:00:00 2001 From: Helmut Hummel Date: Thu, 23 Mar 2023 11:35:08 +0700 Subject: [PATCH] [FEATURE] Add strict component argument validation --- Classes/Exception/InvalidTypeException.php | 7 +++ .../Fluid/ViewHelper/ComponentRenderer.php | 46 ++++++++++++++++++- .../Utility/ComponentArgumentConverter.php | 29 ++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 Classes/Exception/InvalidTypeException.php diff --git a/Classes/Exception/InvalidTypeException.php b/Classes/Exception/InvalidTypeException.php new file mode 100644 index 0000000..3676e6f --- /dev/null +++ b/Classes/Exception/InvalidTypeException.php @@ -0,0 +1,7 @@ +componentNamespace = $componentNamespace; + foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['fluid_components']['strict'] ?? [] as $strictNamespace) { + if (str_starts_with($componentNamespace, $strictNamespace)) { + $this->componentNamespaceIsStrict = true; + break; + } + } return $this; } @@ -200,8 +214,28 @@ public function render() // Provide supplied arguments from component call to renderer foreach ($this->arguments as $name => $argument) { $argumentType = $this->argumentDefinitions[$name]->getType(); - - $argument = $this->componentArgumentConverter->convertValueToType($argument, $argumentType); + $argumentIsRequired = $this->argumentDefinitions[$name]->isRequired(); + $argumentDefaultValue = $this->argumentDefinitions[$name]->getDefaultValue(); + if (!$argumentIsRequired) { + $argument ??= $argumentDefaultValue; + } + try { + $argument = $this->componentArgumentConverter->convertValueToType($argument, $argumentType); + } catch (InvalidTypeException $e) { + if ($this->componentNamespaceIsStrict && ($argumentIsRequired || $argument !== $this->argumentDefinitions[$name]->getDefaultValue())) { + throw new Exception( + sprintf( + 'The argument "%s" defined in "%s" has declared type "%s", but "%s" was given.', + $name, + $this->getComponentNamespace(), + $argumentType, + is_object($argument) ? get_class($argument) : gettype($argument) + ), + 1677141778, + $e + ); + } + } // Provide component namespace to certain data structures if ($argument instanceof ComponentAware) { @@ -428,6 +462,14 @@ protected function initializeComponentParams() foreach ($paramNode->getArguments() as $argumentName => $argumentNode) { $param[$argumentName] = $argumentNode->evaluate($renderingContext); } + if ($this->componentNamespaceIsStrict && !$this->componentArgumentConverter->isValidType($param['type'])) { + throw new Exception(sprintf( + 'The argument "%s" defined in "%s" has an invalid type declaration "%s".', + $param['name'], + $this->getComponentNamespace(), + $param['type'] + ), 1677141778); + } // Use tag content as default value instead of attribute if (!isset($param['default'])) { diff --git a/Classes/Utility/ComponentArgumentConverter.php b/Classes/Utility/ComponentArgumentConverter.php index c2f92b5..44e2042 100644 --- a/Classes/Utility/ComponentArgumentConverter.php +++ b/Classes/Utility/ComponentArgumentConverter.php @@ -2,6 +2,7 @@ namespace SMS\FluidComponents\Utility; +use SMS\FluidComponents\Exception\InvalidTypeException; use SMS\FluidComponents\Interfaces\ConstructibleFromArray; use SMS\FluidComponents\Interfaces\ConstructibleFromDateTime; use SMS\FluidComponents\Interfaces\ConstructibleFromDateTimeImmutable; @@ -16,6 +17,14 @@ class ComponentArgumentConverter implements \TYPO3\CMS\Core\SingletonInterface { + private const NATIVE_TYPES = [ + 'integer', + 'float', + 'string', + 'boolean', + 'array', + ]; + /** * List of interfaces that provide conversion methods between scalar/compound * variable types and complex data structures, @@ -217,6 +226,14 @@ public function convertValueToType($value, string $toType) // Skip if the type can't be converted if (!$this->canTypeBeConvertedToType($givenType, $toType)) { + if ($givenType !== $toType && !is_a($value, $toType)) { + throw new InvalidTypeException(sprintf( + 'The argument value is of type "%s", but "%s" is given.', + $toType, + $givenType + ), 1677142681); + + } return $value; } @@ -234,6 +251,18 @@ public function convertValueToType($value, string $toType) return $toType::$constructor($value); } + public function isValidType(string $type): bool + { + if (in_array($type, self::NATIVE_TYPES, true)) { + return true; + } + $type = $this->resolveTypeAlias($type); + if ($this->isCollectionType($type)) { + return $this->isValidType($this->extractCollectionItemType($type)); + } + return class_exists($type); + } + /** * Checks if the provided type describes a collection of values *