From d8e23c5698ee2764eb2f3e374f5a8e16815e170c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20M=C3=BCller?= Date: Tue, 18 Feb 2025 20:57:16 +0100 Subject: [PATCH] chore: decouple AbstractType and MultipleType --- Classes/Core/Model/AbstractBaseType.php | 219 ++++++++++++++++++++++++ Classes/Core/Model/AbstractType.php | 206 +--------------------- Classes/Core/Model/MultipleType.php | 21 +-- Classes/Core/Model/TypeInterface.php | 22 +-- phpstan.baseline.neon | 26 +-- 5 files changed, 245 insertions(+), 249 deletions(-) create mode 100644 Classes/Core/Model/AbstractBaseType.php diff --git a/Classes/Core/Model/AbstractBaseType.php b/Classes/Core/Model/AbstractBaseType.php new file mode 100644 index 00000000..abaf9ff6 --- /dev/null +++ b/Classes/Core/Model/AbstractBaseType.php @@ -0,0 +1,219 @@ + => + * Also the additional properties added by an event listener are included + * + * @var array + * @internal + */ + protected array $properties = []; + + public function setId($id): self + { + if (! $this->isValidDataTypeForId($id)) { + throw new \InvalidArgumentException( + \sprintf( + 'Value for id has not a valid data type (given: "%s"). Valid types are: null, string, instanceof NodeIdentifierInterface', + \get_debug_type($id), + ), + 1620654936, + ); + } + + if ($id === '') { + $id = null; + } + + if (\is_string($id)) { + $id = new NodeIdentifier($id); + } + + $this->id = $id; + + return $this; + } + + /** + * @param NodeIdentifierInterface|string|null $id + */ + private function isValidDataTypeForId($id): bool + { + return $id === null + || \is_string($id) + || $id instanceof NodeIdentifierInterface; + } + + public function getId(): ?string + { + return $this->id instanceof NodeIdentifierInterface ? $this->id->getId() : null; + } + + public function hasProperty(string $propertyName): bool + { + try { + $this->checkPropertyExists($propertyName); + } catch (\DomainException) { + return false; + } + + return true; + } + + public function getProperty(string $propertyName) + { + $this->checkPropertyExists($propertyName); + + return $this->properties[$propertyName]; + } + + private function checkPropertyExists(string $propertyName): void + { + if (! \array_key_exists($propertyName, $this->properties)) { + $type = $this->getType(); + throw new \DomainException( + \sprintf( + 'Property "%s" is unknown for type "%s"', + $propertyName, + \is_array($type) ? \implode(' / ', $type) : $type, + ), + 1561829996, + ); + } + } + + public function setProperty(string $propertyName, $propertyValue): self + { + $propertyValue = $this->stringifyNumericValue($propertyValue); + $this->checkProperty($propertyName, $propertyValue); + + $this->properties[$propertyName] = $propertyValue; + + return $this; + } + + /** + * If the value is a numeric one, stringify it + * + * @return string|mixed + */ + private function stringifyNumericValue(mixed $value) + { + return \is_numeric($value) ? (string) $value : $value; + } + + /** + * Check, if property name and value are valid + * + * @param string $propertyName The property name + * @param mixed $propertyValue The property value + * + * @throws \DomainException + * @throws \InvalidArgumentException + */ + private function checkProperty(string $propertyName, mixed $propertyValue): void + { + $this->checkPropertyExists($propertyName); + + if (! $this->isValidDataTypeForPropertyValue($propertyValue)) { + throw new \InvalidArgumentException( + \sprintf( + 'Value for property "%s" has not a valid data type (given: "%s"). Valid types are: null, string, int, array, bool, instanceof TypeInterface, instanceof NodeIdentifierInterface', + $propertyName, + \get_debug_type($propertyValue), + ), + 1561830012, + ); + } + } + + /** + * Returns true, if data type of property value is allowed + */ + private function isValidDataTypeForPropertyValue(mixed $propertyValue): bool + { + return $propertyValue === null + || \is_string($propertyValue) + || \is_array($propertyValue) + || \is_bool($propertyValue) + || $propertyValue instanceof NodeIdentifierInterface + || $propertyValue instanceof TypeInterface + || $propertyValue instanceof EnumerationInterface; + } + + public function addProperty(string $propertyName, $propertyValue): self + { + $propertyValue = $this->stringifyNumericValue($propertyValue); + $this->checkProperty($propertyName, $propertyValue); + + if ($this->properties[$propertyName] === null) { + $this->properties[$propertyName] = $propertyValue; + + return $this; + } + + if (\is_array($this->properties[$propertyName])) { + if (! \is_array($propertyValue)) { + $propertyValue = [$propertyValue]; + } + + $this->properties[$propertyName] = \array_merge($this->properties[$propertyName], $propertyValue); + + return $this; + } + + $this->properties[$propertyName] = [ + $this->properties[$propertyName], + $propertyValue, + ]; + + return $this; + } + + public function setProperties(array $properties): self + { + foreach ($properties as $propertyName => $propertyValue) { + $this->setProperty($propertyName, $propertyValue); + } + + return $this; + } + + public function clearProperty(string $propertyName): self + { + $this->checkPropertyExists($propertyName); + + $this->properties[$propertyName] = null; + + return $this; + } + + public function getPropertyNames(): array + { + return \array_keys($this->properties); + } +} diff --git a/Classes/Core/Model/AbstractType.php b/Classes/Core/Model/AbstractType.php index 95e2b004..42797cf7 100644 --- a/Classes/Core/Model/AbstractType.php +++ b/Classes/Core/Model/AbstractType.php @@ -18,7 +18,7 @@ use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Utility\GeneralUtility; -abstract class AbstractType implements TypeInterface +abstract class AbstractType extends AbstractBaseType { /** * The properties of a specific type @@ -32,25 +32,12 @@ abstract class AbstractType implements TypeInterface /** * The getType() method resolves the type depending on the "Type" attribute via reflection. * Once a specific type is resolved, it is cached in this variable. + * * @var array * @internal */ protected static array $resolvedTypes = []; - /** - * The ID of the type (mapped to @id in result) - */ - private ?NodeIdentifierInterface $id = null; - - /** - * The properties of a specific type with their corresponding value: - * => - * Also the additional properties added by an event listener are included - * - * @var array - */ - private array $properties = []; - public function __construct() { $this->initialiseProperties(); @@ -94,194 +81,7 @@ protected function addAdditionalProperties(): void \ksort($this->properties); } - public function getId(): ?string - { - return $this->id instanceof NodeIdentifierInterface ? $this->id->getId() : null; - } - - public function setId($id): self - { - if (! $this->isValidDataTypeForId($id)) { - throw new \InvalidArgumentException( - \sprintf( - 'Value for id has not a valid data type (given: "%s"). Valid types are: null, string, instanceof NodeIdentifierInterface', - \get_debug_type($id), - ), - 1620654936, - ); - } - - if ($id === '') { - $id = null; - } - - if (\is_string($id)) { - $id = new NodeIdentifier($id); - } - - $this->id = $id; - - return $this; - } - - /** - * @param NodeIdentifierInterface|string|null $id - */ - private function isValidDataTypeForId($id): bool - { - return $id === null - || \is_string($id) - || $id instanceof NodeIdentifierInterface; - } - - public function hasProperty(string $propertyName): bool - { - try { - $this->checkPropertyExists($propertyName); - } catch (\DomainException) { - return false; - } - - return true; - } - - public function getProperty(string $propertyName) - { - $this->checkPropertyExists($propertyName); - - return $this->properties[$propertyName]; - } - - private function checkPropertyExists(string $propertyName): void - { - if (! \array_key_exists($propertyName, $this->properties)) { - $type = $this->getType(); - throw new \DomainException( - \sprintf( - 'Property "%s" is unknown for type "%s"', - $propertyName, - \is_array($type) ? \implode(' / ', $type) : $type, - ), - 1561829996, - ); - } - } - - public function setProperty(string $propertyName, $propertyValue): self - { - $propertyValue = $this->stringifyNumericValue($propertyValue); - $this->checkProperty($propertyName, $propertyValue); - - $this->properties[$propertyName] = $propertyValue; - - return $this; - } - - /** - * If the value is a numeric one, stringify it - * - * @return string|mixed - */ - private function stringifyNumericValue(mixed $value) - { - return \is_numeric($value) ? (string) $value : $value; - } - - /** - * Check, if property name and value are valid - * - * @param string $propertyName The property name - * @param mixed $propertyValue The property value - * - * @throws \DomainException - * @throws \InvalidArgumentException - */ - private function checkProperty(string $propertyName, mixed $propertyValue): void - { - $this->checkPropertyExists($propertyName); - - if (! $this->isValidDataTypeForPropertyValue($propertyValue)) { - throw new \InvalidArgumentException( - \sprintf( - 'Value for property "%s" has not a valid data type (given: "%s"). Valid types are: null, string, int, array, bool, instanceof TypeInterface, instanceof NodeIdentifierInterface', - $propertyName, - \get_debug_type($propertyValue), - ), - 1561830012, - ); - } - } - - /** - * Returns true, if data type of property value is allowed - */ - private function isValidDataTypeForPropertyValue(mixed $propertyValue): bool - { - return $propertyValue === null - || \is_string($propertyValue) - || \is_array($propertyValue) - || \is_bool($propertyValue) - || $propertyValue instanceof NodeIdentifierInterface - || $propertyValue instanceof TypeInterface - || $propertyValue instanceof EnumerationInterface; - } - - public function addProperty(string $propertyName, $propertyValue): self - { - $propertyValue = $this->stringifyNumericValue($propertyValue); - $this->checkProperty($propertyName, $propertyValue); - - if ($this->properties[$propertyName] === null) { - $this->properties[$propertyName] = $propertyValue; - - return $this; - } - - if (\is_array($this->properties[$propertyName])) { - if (! \is_array($propertyValue)) { - $propertyValue = [$propertyValue]; - } - - $this->properties[$propertyName] = \array_merge($this->properties[$propertyName], $propertyValue); - - return $this; - } - - $this->properties[$propertyName] = [ - $this->properties[$propertyName], - $propertyValue, - ]; - - return $this; - } - - public function setProperties(array $properties): self - { - foreach ($properties as $propertyName => $propertyValue) { - $this->setProperty($propertyName, $propertyValue); - } - - return $this; - } - - public function clearProperty(string $propertyName): self - { - $this->checkPropertyExists($propertyName); - - $this->properties[$propertyName] = null; - - return $this; - } - - /** - * @return string[] - */ - public function getPropertyNames(): array - { - return \array_keys($this->properties); - } - - public function getType(): string|array + public function getType(): string { if (! isset(static::$resolvedTypes[static::class])) { $reflector = new \ReflectionClass(static::class); diff --git a/Classes/Core/Model/MultipleType.php b/Classes/Core/Model/MultipleType.php index a5521086..0b747b04 100644 --- a/Classes/Core/Model/MultipleType.php +++ b/Classes/Core/Model/MultipleType.php @@ -11,7 +11,7 @@ namespace Brotkrueml\Schema\Core\Model; -final class MultipleType extends AbstractType +final class MultipleType extends AbstractBaseType { /** * @var string[] @@ -23,10 +23,12 @@ public function __construct(TypeInterface ...$type) $this->storeTypeNames($type); $this->checkForSameTypes(); $this->checkNumberOfTypes(); - $this->mergePropertyNamesFromSingleTypes($type); - parent::__construct(); + $this->mergePropertiesFromSingleTypes($type); } + /** + * @param TypeInterface[] $types + */ private function storeTypeNames(array $types): void { $this->typeNames = \array_map( @@ -57,25 +59,24 @@ private function checkNumberOfTypes(): void } } - private function mergePropertyNamesFromSingleTypes(array $types): void + /** + * @param TypeInterface[] $types + */ + private function mergePropertiesFromSingleTypes(array $types): void { $propertyNames = []; foreach ($types as $type) { $propertyNames = \array_merge($propertyNames, $type->getPropertyNames()); } \sort($propertyNames); - self::$propertyNames = \array_unique($propertyNames); - } - protected function addAdditionalProperties(): void - { - // not necessary + $this->properties = \array_fill_keys($propertyNames, null); } /** * @return string[] */ - public function getType(): string|array + public function getType(): array { return $this->typeNames; } diff --git a/Classes/Core/Model/TypeInterface.php b/Classes/Core/Model/TypeInterface.php index 2a98df16..07276c24 100644 --- a/Classes/Core/Model/TypeInterface.php +++ b/Classes/Core/Model/TypeInterface.php @@ -14,7 +14,7 @@ interface TypeInterface extends NodeIdentifierInterface { /** - * Set the id + * Set the id. * * @param NodeIdentifierInterface|string|null $id The id * @return self @@ -22,14 +22,14 @@ interface TypeInterface extends NodeIdentifierInterface public function setId($id); /** - * Check, if a property exists + * Check, if a property exists. * * @param string $propertyName The property name */ public function hasProperty(string $propertyName): bool; /** - * Get the value of a property + * Get the value of a property. * * @param string $propertyName The property name * @return mixed @@ -37,7 +37,7 @@ public function hasProperty(string $propertyName): bool; public function getProperty(string $propertyName); /** - * Set the value of a property + * Set the value of a property. * * @param string $propertyName The property name * @param mixed $propertyValue The value of the property @@ -46,7 +46,7 @@ public function getProperty(string $propertyName); public function setProperty(string $propertyName, mixed $propertyValue); /** - * Adds a value to a property + * Adds a value to a property. * * @param string $propertyName The property name * @param mixed $propertyValue The property value @@ -55,7 +55,7 @@ public function setProperty(string $propertyName, mixed $propertyValue); public function addProperty(string $propertyName, mixed $propertyValue); /** - * Set multiple properties at once + * Set multiple properties at once. * * The method expects the properties in the following format: * key = property name @@ -67,7 +67,7 @@ public function addProperty(string $propertyName, mixed $propertyValue); public function setProperties(array $properties); /** - * Clear a property + * Clear a property. * * @param string $propertyName The property name * @return self @@ -75,15 +75,15 @@ public function setProperties(array $properties); public function clearProperty(string $propertyName); /** - * Get the available property names + * Get the available property names. * - * @return string[] + * @return list */ public function getPropertyNames(): array; /** - * Get the type the model represents - * This can also be an array of types for a multiple type + * Get the type the model represents. + * This can also be an array of types for a multiple type. * * @return string|string[] */ diff --git a/phpstan.baseline.neon b/phpstan.baseline.neon index e962ecee..84a6e0f3 100644 --- a/phpstan.baseline.neon +++ b/phpstan.baseline.neon @@ -10,7 +10,7 @@ parameters: message: '#^Instanceof between mixed and Brotkrueml\\Schema\\Core\\Model\\TypeInterface will always evaluate to false\.$#' identifier: instanceof.alwaysFalse count: 1 - path: Classes/Core/Model/AbstractType.php + path: Classes/Core/Model/AbstractBaseType.php - message: '#^Anonymous function should return string but returns array\\|string\.$#' @@ -18,30 +18,6 @@ parameters: count: 1 path: Classes/Core/Model/MultipleType.php - - - message: '#^Method Brotkrueml\\Schema\\Core\\Model\\MultipleType\:\:getType\(\) never returns string so it can be removed from the return type\.$#' - identifier: return.unusedType - count: 1 - path: Classes/Core/Model/MultipleType.php - - - - message: '#^Method Brotkrueml\\Schema\\Core\\Model\\MultipleType\:\:mergePropertyNamesFromSingleTypes\(\) has parameter \$types with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: Classes/Core/Model/MultipleType.php - - - - message: '#^Method Brotkrueml\\Schema\\Core\\Model\\MultipleType\:\:storeTypeNames\(\) has parameter \$types with no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: Classes/Core/Model/MultipleType.php - - - - message: '#^Static property Brotkrueml\\Schema\\Core\\Model\\AbstractType\:\:\$propertyNames \(list\\) does not accept array\, mixed\>\.$#' - identifier: assign.propertyType - count: 1 - path: Classes/Core/Model/MultipleType.php - - message: '#^Cannot call method setId\(\) on Brotkrueml\\Schema\\Core\\Model\\TypeInterface\|null\.$#' identifier: method.nonObject