From 23be380016ac05b9003ba913e696760e1e1930b0 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 12:02:42 -0500 Subject: [PATCH 1/8] Refactor traits and namespaces for better organization. Moved several traits to new namespaces under more relevant subdirectories. Updated references throughout the codebase to reflect these changes for improved structure and maintainability. --- clover.xml | 148 +++++++++--------- src/Concerns/BaseData.php | 2 +- src/Concerns/CloneableData.php | 2 +- .../Traits => Contexts/Concerns}/HasTypes.php | 2 +- src/Contexts/PropertyContext.php | 16 +- src/Contexts/TypeContext.php | 14 +- .../Concerns}/HasNormalizers.php | 2 +- .../Concerns}/HasSerializers.php | 4 +- .../Serializers/HasSerializersDummyClass.php | 2 +- .../DoesNotHaveAdditionalNormalizersDummy.php | 2 +- tests/Dummies/Support/HasNormalizersDummy.php | 2 +- 11 files changed, 102 insertions(+), 94 deletions(-) rename src/{Support/Traits => Contexts/Concerns}/HasTypes.php (94%) rename src/{Support/Traits => Normalizers/Concerns}/HasNormalizers.php (96%) rename src/{Support/Traits => Serializers/Concerns}/HasSerializers.php (98%) diff --git a/clover.xml b/clover.xml index 51124f8..8db148b 100644 --- a/clover.xml +++ b/clover.xml @@ -1,6 +1,6 @@ - - + + @@ -488,6 +488,17 @@ + + + + + + + + + + + @@ -569,6 +580,12 @@ + + + + + + @@ -996,6 +1013,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1318,6 +1359,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1375,73 +1450,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/src/Concerns/BaseData.php b/src/Concerns/BaseData.php index 0b2ec52..f9cc34a 100644 --- a/src/Concerns/BaseData.php +++ b/src/Concerns/BaseData.php @@ -6,9 +6,9 @@ use Nuxtifyts\PhpDto\Exceptions\DataCreationException; use Nuxtifyts\PhpDto\Exceptions\DeserializeException; use Nuxtifyts\PhpDto\Exceptions\SerializeException; +use Nuxtifyts\PhpDto\Normalizers\Concerns\HasNormalizers; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipeline; use Nuxtifyts\PhpDto\Pipelines\DeserializePipeline\DeserializePipelinePassable; -use Nuxtifyts\PhpDto\Support\Traits\HasNormalizers; use ReflectionClass; use Throwable; diff --git a/src/Concerns/CloneableData.php b/src/Concerns/CloneableData.php index 44fbb57..d069362 100644 --- a/src/Concerns/CloneableData.php +++ b/src/Concerns/CloneableData.php @@ -4,7 +4,7 @@ use Nuxtifyts\PhpDto\Contexts\ClassContext; use Nuxtifyts\PhpDto\Exceptions\DataCreationException; -use Nuxtifyts\PhpDto\Support\Traits\HasNormalizers; +use Nuxtifyts\PhpDto\Normalizers\Concerns\HasNormalizers; use ReflectionClass; use Throwable; diff --git a/src/Support/Traits/HasTypes.php b/src/Contexts/Concerns/HasTypes.php similarity index 94% rename from src/Support/Traits/HasTypes.php rename to src/Contexts/Concerns/HasTypes.php index 7ae90a0..3b5f8c2 100644 --- a/src/Support/Traits/HasTypes.php +++ b/src/Contexts/Concerns/HasTypes.php @@ -1,6 +1,6 @@ Date: Tue, 31 Dec 2024 13:54:42 -0500 Subject: [PATCH 2/8] Refactor configuration and serializers logic Introduce `DataConfiguration` and `SerializersConfiguration` for managing serializers and configurations dynamically. Centralize serializer registration and improve type handling across contexts while replacing hardcoded lists with configurable options. Add `DataConfigurationException` for robust error handling. --- src/Configuration/Configuration.php | 18 +++++ src/Configuration/DataConfiguration.php | 37 ++++++++++ .../SerializersConfiguration.php | 69 +++++++++++++++++++ src/Contexts/PropertyContext.php | 3 + src/Contexts/TypeContext.php | 5 ++ src/Exceptions/DataConfigurationException.php | 15 ++++ src/Serializers/ArraySerializer.php | 1 - src/Serializers/Concerns/HasSerializers.php | 32 +++------ .../Concerns/SerializesArrayOfItems.php | 4 +- .../Contracts/SerializesArrayOfItems.php | 4 +- src/Support/Arr.php | 20 ++++++ 11 files changed, 181 insertions(+), 27 deletions(-) create mode 100644 src/Configuration/Configuration.php create mode 100644 src/Configuration/DataConfiguration.php create mode 100644 src/Configuration/SerializersConfiguration.php create mode 100644 src/Exceptions/DataConfigurationException.php create mode 100644 src/Support/Arr.php diff --git a/src/Configuration/Configuration.php b/src/Configuration/Configuration.php new file mode 100644 index 0000000..14c511e --- /dev/null +++ b/src/Configuration/Configuration.php @@ -0,0 +1,18 @@ + $config + * + * @throws DataConfigurationException + */ + public static function getInstance( + ?array $config = null, + bool $forceCreate = false + ): self; +} diff --git a/src/Configuration/DataConfiguration.php b/src/Configuration/DataConfiguration.php new file mode 100644 index 0000000..6913417 --- /dev/null +++ b/src/Configuration/DataConfiguration.php @@ -0,0 +1,37 @@ + $config + * + * @throws DataConfigurationException + */ + public static function getInstance( + ?array $config = null, + bool $forceCreate = false + ): self { + if (self::$instance && !$forceCreate) { + return self::$instance; + } + + return self::$instance = new self( + serializers: SerializersConfiguration::getInstance( + Arr::getArray($config ?? [], 'serializers'), + $forceCreate + ) + ); + } +} diff --git a/src/Configuration/SerializersConfiguration.php b/src/Configuration/SerializersConfiguration.php new file mode 100644 index 0000000..02feed6 --- /dev/null +++ b/src/Configuration/SerializersConfiguration.php @@ -0,0 +1,69 @@ +> $baseSerializers + */ + protected function __construct( + protected(set) array $baseSerializers = [ + ArraySerializer::class, + DataSerializer::class, + DateTimeSerializer::class, + BackedEnumSerializer::class, + ScalarTypeSerializer::class, + ], + ) { + } + + /** + * @param ?array $config + * + * @throws DataConfigurationException + */ + public static function getInstance( + ?array $config = null, + bool $forceCreate = false + ): self { + if (self::$instance && !$forceCreate) { + return self::$instance; + } + + $baseSerializers = $config['baseSerializers'] ?? [ + ArraySerializer::class, + DataSerializer::class, + DateTimeSerializer::class, + BackedEnumSerializer::class, + ScalarTypeSerializer::class, + ]; + + if ( + !is_array($baseSerializers) + || array_any( + $baseSerializers, + static fn (mixed $baseSerializer): bool => + !is_string($baseSerializer) + || !is_subclass_of($baseSerializer, Serializer::class) + ) + ) { + throw DataConfigurationException::invalidBaseSerializers(); + } + /** @var array> $baseSerializers */ + + return self::$instance = new self( + baseSerializers: $baseSerializers + ); + } +} diff --git a/src/Contexts/PropertyContext.php b/src/Contexts/PropertyContext.php index f6ce792..dfc3701 100644 --- a/src/Contexts/PropertyContext.php +++ b/src/Contexts/PropertyContext.php @@ -15,6 +15,7 @@ use Nuxtifyts\PhpDto\DataCiphers\CipherConfig; use Nuxtifyts\PhpDto\DataRefiners\DataRefiner; use Nuxtifyts\PhpDto\Enums\Property\Type; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; use Nuxtifyts\PhpDto\Exceptions\DataCreationException; use Nuxtifyts\PhpDto\Exceptions\DeserializeException; use Nuxtifyts\PhpDto\Exceptions\SerializeException; @@ -173,6 +174,7 @@ public function getFilteredSubTypeContexts(Type $type, Type ...$additionalTypes) * @return list * * @throws UnknownTypeException + * @throws DataConfigurationException */ protected function resolveSerializers(): array { @@ -184,6 +186,7 @@ protected function resolveSerializers(): array * * @throws DeserializeException * @throws UnknownTypeException + * @throws DataConfigurationException */ public function deserializeFrom(array $value): mixed { diff --git a/src/Contexts/TypeContext.php b/src/Contexts/TypeContext.php index 9956123..e863afd 100644 --- a/src/Contexts/TypeContext.php +++ b/src/Contexts/TypeContext.php @@ -10,6 +10,8 @@ use Nuxtifyts\PhpDto\Contracts\BaseData as BaseDataContract; use Nuxtifyts\PhpDto\Data; use Nuxtifyts\PhpDto\Enums\Property\Type; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; +use Nuxtifyts\PhpDto\Exceptions\UnknownTypeException; use Nuxtifyts\PhpDto\Exceptions\UnsupportedTypeException; use Nuxtifyts\PhpDto\Serializers\Concerns\HasSerializers; use Nuxtifyts\PhpDto\Serializers\Serializer; @@ -212,6 +214,9 @@ private static function getPropertyStringTypes(ReflectionProperty $property): ar /** * @return list + * + * @throws DataConfigurationException + * @throws UnknownTypeException */ protected function resolveSerializers(): array { diff --git a/src/Exceptions/DataConfigurationException.php b/src/Exceptions/DataConfigurationException.php new file mode 100644 index 0000000..7a10cec --- /dev/null +++ b/src/Exceptions/DataConfigurationException.php @@ -0,0 +1,15 @@ +subTypeSerializers() as $serializer) { try { if ($serializer instanceof SerializesArrayOfItems) { - // @phpstan-ignore-next-line return $serializer->deserializeArrayOfItems($property, $item); } } catch (Exception) { diff --git a/src/Serializers/Concerns/HasSerializers.php b/src/Serializers/Concerns/HasSerializers.php index 9f7092f..a32cb93 100644 --- a/src/Serializers/Concerns/HasSerializers.php +++ b/src/Serializers/Concerns/HasSerializers.php @@ -2,16 +2,13 @@ namespace Nuxtifyts\PhpDto\Serializers\Concerns; +use Nuxtifyts\PhpDto\Configuration\DataConfiguration; use Nuxtifyts\PhpDto\Contexts\PropertyContext; use Nuxtifyts\PhpDto\Contexts\TypeContext; use Nuxtifyts\PhpDto\Enums\Property\Type; use Nuxtifyts\PhpDto\Exceptions\UnknownTypeException; -use Nuxtifyts\PhpDto\Serializers\ArraySerializer; -use Nuxtifyts\PhpDto\Serializers\BackedEnumSerializer; -use Nuxtifyts\PhpDto\Serializers\DataSerializer; -use Nuxtifyts\PhpDto\Serializers\DateTimeSerializer; -use Nuxtifyts\PhpDto\Serializers\ScalarTypeSerializer; use Nuxtifyts\PhpDto\Serializers\Serializer; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; trait HasSerializers { @@ -24,6 +21,7 @@ trait HasSerializers * @return list * * @throws UnknownTypeException + * @throws DataConfigurationException */ protected function getSerializersFromPropertyContext( PropertyContext $propertyContext @@ -35,7 +33,7 @@ protected function getSerializersFromPropertyContext( array_column($propertyContext->types, 'value'), array_column($serializer::supportedTypes(), 'value') )) ? new $serializer() : null, - self::serializersList() + DataConfiguration::getInstance()->serializers->baseSerializers ))) ?: throw UnknownTypeException::unknownType(...$propertyContext->types); } @@ -43,6 +41,9 @@ protected function getSerializersFromPropertyContext( * @param TypeContext $typeContext * * @return list + * + * @throws UnknownTypeException + * @throws DataConfigurationException */ protected function getSerializersFromTypeContext( TypeContext $typeContext, @@ -54,28 +55,15 @@ protected function getSerializersFromTypeContext( array_column($typeContext->arrayElementTypes, 'value'), array_column($serializer::supportedTypes(), 'value') )) ? new $serializer() : null, - self::serializersList() - ))); - } - - /** - * @return list> - */ - protected static function serializersList(): array - { - return [ - ArraySerializer::class, - DataSerializer::class, - DateTimeSerializer::class, - BackedEnumSerializer::class, - ScalarTypeSerializer::class, - ]; + DataConfiguration::getInstance()->serializers->baseSerializers + ))) ?: throw UnknownTypeException::unknownType(...$typeContext->arrayElementTypes); } /** * @return list * * @throws UnknownTypeException + * @throws DataConfigurationException */ public function serializers(): array { diff --git a/src/Serializers/Concerns/SerializesArrayOfItems.php b/src/Serializers/Concerns/SerializesArrayOfItems.php index fc936f0..cb7712d 100644 --- a/src/Serializers/Concerns/SerializesArrayOfItems.php +++ b/src/Serializers/Concerns/SerializesArrayOfItems.php @@ -13,7 +13,7 @@ trait SerializesArrayOfItems { /** - * @return array> + * @return array> * * @throws SerializeException */ @@ -38,7 +38,7 @@ public function serializeArrayOfItems( } /** - * @param array $data + * @param array $data * * @return ?array * diff --git a/src/Serializers/Contracts/SerializesArrayOfItems.php b/src/Serializers/Contracts/SerializesArrayOfItems.php index adedfe5..4909d67 100644 --- a/src/Serializers/Contracts/SerializesArrayOfItems.php +++ b/src/Serializers/Contracts/SerializesArrayOfItems.php @@ -9,7 +9,7 @@ interface SerializesArrayOfItems { /** - * @return array> + * @return array> * * @throws SerializeException */ @@ -19,7 +19,7 @@ public function serializeArrayOfItems( ): array; /** - * @param array $data + * @param array $data * * @return ?array * diff --git a/src/Support/Arr.php b/src/Support/Arr.php new file mode 100644 index 0000000..aa9211f --- /dev/null +++ b/src/Support/Arr.php @@ -0,0 +1,20 @@ + $array + * @param string $key + * @param array $default + * + * @return array + */ + public static function getArray(array $array, string $key, array $default = []): array + { + $value = $array[$key] ?? null; + + return is_array($value) ? $value : $default; + } +} From c109a8d00ec72ddb157e3e1b2c897eff361ed3d4 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 14:20:12 -0500 Subject: [PATCH 3/8] Add resetConfig method to UnitCase and update clover.xml Introduce the `resetConfig` method in `UnitCase` to reset `DataConfiguration` for test isolation. Additionally, update `clover.xml` with new coverage details and metrics. --- clover.xml | 367 ++++++++++-------- .../Serializers/HasSerializersDummyClass.php | 9 + tests/Unit/Contexts/PropertyContextTest.php | 9 +- tests/Unit/UnitCase.php | 9 + 4 files changed, 235 insertions(+), 159 deletions(-) diff --git a/clover.xml b/clover.xml index 8db148b..e705a84 100644 --- a/clover.xml +++ b/clover.xml @@ -1,6 +1,6 @@ - - + + @@ -261,6 +261,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -300,192 +350,192 @@ - - - - - + + + + - - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - + + + + - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + - + - - - - - - - - - - + + + + + + + + + + - - - + + + - - + + + + - - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + - - + + - - - + + + - - - + + + - - - + + + - - - - - + + + + + + - + - - - - + + + + - - - - - - + + + + + + - - - - - - + + + + + - - + + - - - - + + + + + + @@ -580,12 +630,6 @@ - - - - - - @@ -722,6 +766,14 @@ + + + + + + + + @@ -1178,13 +1230,13 @@ - - - + + + + - - + @@ -1361,37 +1413,29 @@ - + - + + + - - - - - + + - - - - - - - - - - - + + + + @@ -1421,6 +1465,15 @@ + + + + + + + + + @@ -1450,6 +1503,6 @@ - + diff --git a/tests/Dummies/Serializers/HasSerializersDummyClass.php b/tests/Dummies/Serializers/HasSerializersDummyClass.php index ff4e3e1..b07e72b 100644 --- a/tests/Dummies/Serializers/HasSerializersDummyClass.php +++ b/tests/Dummies/Serializers/HasSerializersDummyClass.php @@ -2,7 +2,9 @@ namespace Nuxtifyts\PhpDto\Tests\Dummies\Serializers; +use Nuxtifyts\PhpDto\Configuration\DataConfiguration; use Nuxtifyts\PhpDto\Contexts\PropertyContext; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; use Nuxtifyts\PhpDto\Exceptions\UnknownTypeException; use Nuxtifyts\PhpDto\Serializers\Concerns\HasSerializers; use Nuxtifyts\PhpDto\Serializers\DateTimeSerializer; @@ -14,11 +16,18 @@ class HasSerializersDummyClass /** * @throws UnknownTypeException + * @throws DataConfigurationException * * @return list */ public static function testGetSerializersFromPropertyContext(PropertyContext $propertyContext): array { + DataConfiguration::getInstance([ + 'serializers' => [ + 'baseSerializers' => self::serializersList() + ], + ], true); + return new self()->getSerializersFromPropertyContext($propertyContext); } diff --git a/tests/Unit/Contexts/PropertyContextTest.php b/tests/Unit/Contexts/PropertyContextTest.php index 4859d9d..4d2c517 100644 --- a/tests/Unit/Contexts/PropertyContextTest.php +++ b/tests/Unit/Contexts/PropertyContextTest.php @@ -119,8 +119,13 @@ public function __construct( $reflectionProperty = new ReflectionProperty($object::class, 'value'); $propertyContext = PropertyContext::getInstance($reflectionProperty); - self::expectException(UnknownTypeException::class); - HasSerializersDummyClass::testGetSerializersFromPropertyContext($propertyContext); + try { + HasSerializersDummyClass::testGetSerializersFromPropertyContext($propertyContext); + } catch (Throwable $t) { + self::assertInstanceOf(UnknownTypeException::class, $t); + } + + self::resetConfig(); } /** diff --git a/tests/Unit/UnitCase.php b/tests/Unit/UnitCase.php index 2a59986..46d6949 100644 --- a/tests/Unit/UnitCase.php +++ b/tests/Unit/UnitCase.php @@ -2,8 +2,17 @@ namespace Nuxtifyts\PhpDto\Tests\Unit; +use Nuxtifyts\PhpDto\Configuration\DataConfiguration; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; use PHPUnit\Framework\TestCase; abstract class UnitCase extends Testcase { + /** + * @throws DataConfigurationException + */ + protected static function resetConfig(): void + { + DataConfiguration::getInstance(forceCreate: true); + } } From c976cf188708191a58b6c0bcb6ca3503bf2a70a1 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 15:40:53 -0500 Subject: [PATCH 4/8] Add NormalizersConfiguration and enhance configuration handling Introduced `NormalizersConfiguration` to manage normalizers setup. Refactored existing code to leverage shared utility methods like `Arr::isArrayOfClassStrings` for validation. Updated configurations and exception handling to support normalizers alongside serializers. --- clover.xml | 102 +++++++++++------- src/Concerns/CloneableData.php | 2 +- src/Configuration/DataConfiguration.php | 5 + .../NormalizersConfiguration.php | 62 +++++++++++ .../SerializersConfiguration.php | 8 +- src/Exceptions/DataConfigurationException.php | 7 ++ src/Normalizers/Concerns/HasNormalizers.php | 17 ++- src/Support/Arr.php | 13 +++ tests/Dummies/Support/HasNormalizersDummy.php | 7 +- 9 files changed, 168 insertions(+), 55 deletions(-) create mode 100644 src/Configuration/NormalizersConfiguration.php diff --git a/clover.xml b/clover.xml index e705a84..fe479d5 100644 --- a/clover.xml +++ b/clover.xml @@ -1,6 +1,6 @@ - - + + @@ -267,48 +267,69 @@ - + - - - - - + + + + - + + + + + + - - - + + + - - + + + - + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - + + + @@ -768,11 +789,13 @@ - + - - - + + + + + @@ -1068,7 +1091,7 @@ - + @@ -1076,17 +1099,14 @@ - - - + - - - - + + + @@ -1467,12 +1487,18 @@ - + - + + + + + + + @@ -1503,6 +1529,6 @@ - + diff --git a/src/Concerns/CloneableData.php b/src/Concerns/CloneableData.php index d069362..be763b7 100644 --- a/src/Concerns/CloneableData.php +++ b/src/Concerns/CloneableData.php @@ -36,7 +36,7 @@ public function with(mixed ...$args): static ? $this->cloneInstanceWithConstructorCall($context, $value) : $this->cloneInstanceWithoutConstructorCall($context, $value); } catch (Throwable $t) { - throw DataCreationException::unableToCloneInstanceWithNewData($t); + throw DataCreationException::unableToCloneInstanceWithNewData(static::class, $t); } } diff --git a/src/Configuration/DataConfiguration.php b/src/Configuration/DataConfiguration.php index 6913417..306d785 100644 --- a/src/Configuration/DataConfiguration.php +++ b/src/Configuration/DataConfiguration.php @@ -11,6 +11,7 @@ class DataConfiguration implements Configuration protected function __construct( protected(set) SerializersConfiguration $serializers, + protected(set) NormalizersConfiguration $normalizers, ) { } @@ -31,6 +32,10 @@ public static function getInstance( serializers: SerializersConfiguration::getInstance( Arr::getArray($config ?? [], 'serializers'), $forceCreate + ), + normalizers: NormalizersConfiguration::getInstance( + Arr::getArray($config ?? [], 'normalizers'), + $forceCreate ) ); } diff --git a/src/Configuration/NormalizersConfiguration.php b/src/Configuration/NormalizersConfiguration.php new file mode 100644 index 0000000..7a74b88 --- /dev/null +++ b/src/Configuration/NormalizersConfiguration.php @@ -0,0 +1,62 @@ +> $baseNormalizers + */ + protected function __construct( + protected(set) array $baseNormalizers = [ + JsonStringNormalizer::class, + StdClassNormalizer::class, + ArrayAccessNormalizer::class, + ArrayNormalizer::class, + ] + ) { + } + + /** + * @param ?array $config + * + * @throws DataConfigurationException + */ + public static function getInstance( + ?array $config = null, + bool $forceCreate = false + ): self { + if (self::$instance && !$forceCreate) { + return self::$instance; + } + + $baseNormalizers = $config['baseNormalizers'] ?? [ + JsonStringNormalizer::class, + StdClassNormalizer::class, + ArrayAccessNormalizer::class, + ArrayNormalizer::class, + ]; + + if ( + !is_array($baseNormalizers) + || !Arr::isArrayOfClassStrings($baseNormalizers, Normalizer::class) + ) { + throw DataConfigurationException::invalidBaseNormalizers(); + } + /** @var array> $baseNormalizers */ + + return self::$instance = new self( + baseNormalizers: $baseNormalizers + ); + } +} diff --git a/src/Configuration/SerializersConfiguration.php b/src/Configuration/SerializersConfiguration.php index 02feed6..64af6d7 100644 --- a/src/Configuration/SerializersConfiguration.php +++ b/src/Configuration/SerializersConfiguration.php @@ -9,6 +9,7 @@ use Nuxtifyts\PhpDto\Serializers\DateTimeSerializer; use Nuxtifyts\PhpDto\Serializers\ScalarTypeSerializer; use Nuxtifyts\PhpDto\Serializers\Serializer; +use Nuxtifyts\PhpDto\Support\Arr; class SerializersConfiguration implements Configuration { @@ -51,12 +52,7 @@ public static function getInstance( if ( !is_array($baseSerializers) - || array_any( - $baseSerializers, - static fn (mixed $baseSerializer): bool => - !is_string($baseSerializer) - || !is_subclass_of($baseSerializer, Serializer::class) - ) + || !Arr::isArrayOfClassStrings($baseSerializers, Serializer::class) ) { throw DataConfigurationException::invalidBaseSerializers(); } diff --git a/src/Exceptions/DataConfigurationException.php b/src/Exceptions/DataConfigurationException.php index 7a10cec..e29841e 100644 --- a/src/Exceptions/DataConfigurationException.php +++ b/src/Exceptions/DataConfigurationException.php @@ -8,8 +8,15 @@ class DataConfigurationException extends Exception { protected const int INVALID_BASE_SERIALIZERS = 10000; + protected const int INVALID_BASE_NORMALIZERS = 20000; + public static function invalidBaseSerializers(): self { return new self('Invalid base serializers', self::INVALID_BASE_SERIALIZERS); } + + public static function invalidBaseNormalizers(): self + { + return new self('Invalid base normalizers', self::INVALID_BASE_NORMALIZERS); + } } diff --git a/src/Normalizers/Concerns/HasNormalizers.php b/src/Normalizers/Concerns/HasNormalizers.php index 279c697..9486b63 100644 --- a/src/Normalizers/Concerns/HasNormalizers.php +++ b/src/Normalizers/Concerns/HasNormalizers.php @@ -2,12 +2,10 @@ namespace Nuxtifyts\PhpDto\Normalizers\Concerns; +use Nuxtifyts\PhpDto\Configuration\DataConfiguration; use Nuxtifyts\PhpDto\Data; -use Nuxtifyts\PhpDto\Normalizers\ArrayAccessNormalizer; -use Nuxtifyts\PhpDto\Normalizers\ArrayNormalizer; -use Nuxtifyts\PhpDto\Normalizers\JsonStringNormalizer; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; use Nuxtifyts\PhpDto\Normalizers\Normalizer; -use Nuxtifyts\PhpDto\Normalizers\StdClassNormalizer; trait HasNormalizers { @@ -15,6 +13,8 @@ trait HasNormalizers * @param class-string $class * * @return array|false + * + * @throws DataConfigurationException */ protected static function normalizeValue(mixed $value, string $class): array|false { @@ -30,16 +30,15 @@ protected static function normalizeValue(mixed $value, string $class): array|fal } /** - * @return non-empty-array, class-string> + * @return list> + * + * @throws DataConfigurationException */ final protected static function allNormalizer(): array { return array_values(array_unique([ ...static::normalizers(), - JsonStringNormalizer::class, - StdClassNormalizer::class, - ArrayAccessNormalizer::class, - ArrayNormalizer::class, + ...DataConfiguration::getInstance()->normalizers->baseNormalizers, ])); } diff --git a/src/Support/Arr.php b/src/Support/Arr.php index aa9211f..0a83032 100644 --- a/src/Support/Arr.php +++ b/src/Support/Arr.php @@ -17,4 +17,17 @@ public static function getArray(array $array, string $key, array $default = []): return is_array($value) ? $value : $default; } + + /** + * @param array $array + * @param class-string $classString + */ + public static function isArrayOfClassStrings(array $array, string $classString): bool + { + return array_all( + $array, + static fn (mixed $value): bool => is_string($value) + && is_subclass_of($value, $classString) + ); + } } diff --git a/tests/Dummies/Support/HasNormalizersDummy.php b/tests/Dummies/Support/HasNormalizersDummy.php index 3ef4620..906b5c3 100644 --- a/tests/Dummies/Support/HasNormalizersDummy.php +++ b/tests/Dummies/Support/HasNormalizersDummy.php @@ -3,6 +3,7 @@ namespace Nuxtifyts\PhpDto\Tests\Dummies\Support; use Nuxtifyts\PhpDto\Data; +use Nuxtifyts\PhpDto\Exceptions\DataConfigurationException; use Nuxtifyts\PhpDto\Normalizers\Concerns\HasNormalizers; use Nuxtifyts\PhpDto\Normalizers\Normalizer; use Nuxtifyts\PhpDto\Tests\Dummies\Normalizers\DummyNormalizer; @@ -15,6 +16,8 @@ final class HasNormalizersDummy * @param class-string $class * * @return array|false + * + * @throws DataConfigurationException */ public static function testNormalizeValue(mixed $value, string $class): array|false { @@ -22,7 +25,9 @@ public static function testNormalizeValue(mixed $value, string $class): array|fa } /** - * @return non-empty-array, class-string> + * @return list> + * + * @throws DataConfigurationException */ public static function getAllNormalizer(): array { From 27a16fbd6a32f2624779a081c5efcd66dc9308ef Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 15:56:46 -0500 Subject: [PATCH 5/8] Add `ArrTest` and relocate `HasNormalizersTest` Introduce `ArrTest` to cover unit tests for the `Arr` helper class. Additionally, rename and move `HasNormalizersTest` to a more appropriate namespace to better reflect its functionality. --- .../HasNormalizersTest.php | 8 +- tests/Unit/Support/ArrTest.php | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+), 4 deletions(-) rename tests/Unit/{Support/Traits => Normalizers}/HasNormalizersTest.php (97%) create mode 100644 tests/Unit/Support/ArrTest.php diff --git a/tests/Unit/Support/Traits/HasNormalizersTest.php b/tests/Unit/Normalizers/HasNormalizersTest.php similarity index 97% rename from tests/Unit/Support/Traits/HasNormalizersTest.php rename to tests/Unit/Normalizers/HasNormalizersTest.php index cb1f90d..d9579a4 100644 --- a/tests/Unit/Support/Traits/HasNormalizersTest.php +++ b/tests/Unit/Normalizers/HasNormalizersTest.php @@ -1,20 +1,20 @@ $parameters + */ + #[Test] + #[DataProvider('get_arr_provider')] + #[DataProvider('is_array_of_class_strings_provider')] + public function test_arr_helper_functions( + string $functionName, + array $parameters, + mixed $expected + ): void { + self::assertTrue(method_exists(Arr::class, $functionName)); + self::assertEquals( + $expected, + Arr::{$functionName}(...$parameters) + ); + } + + /** + * @return array + */ + public static function get_arr_provider(): array + { + return [ + 'get array existing key, invalid value' => [ + 'getArray', + [ + 'array' => ['key' => 'value'], + 'key' => 'key', + ], + [], + ], + 'get array existing key, valid value' => [ + 'getArray', + [ + 'array' => ['key' => ['value']], + 'key' => 'key', + ], + ['value'], + ], + 'get array non-existing key' => [ + 'getArray', + [ + 'array' => ['key' => 'value'], + 'key' => 'nonExistingKey', + ], + [], + ], + ]; + } + + /** + * @return array + */ + public static function is_array_of_class_strings_provider(): array + { + return [ + 'is array of class strings, valid' => [ + 'isArrayOfClassStrings', + [ + 'array' => [ + ScalarTypeSerializer::class, + BackedEnumSerializer::class, + ], + 'classString' => Serializer::class, + ], + true, + ], + 'is array of class strings, invalid' => [ + 'isArrayOfClassStrings', + [ + 'array' => [ + ScalarTypeSerializer::class, + BackedEnumSerializer::class, + 'invalid', + ], + 'classString' => Serializer::class, + ], + false, + ], + ]; + } +} From 1f08a1b51e2e070d0175eafd795f8a89369defe9 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 16:10:28 -0500 Subject: [PATCH 6/8] Add unit tests for DataConfiguration and improve coverage Introduced `DataConfigurationTest` to validate configuration logic, including instance creation, overriding, and exceptions for invalid data. Minor renaming in `ArrTest` for clarity. Updated coverage metrics reflect increased test effectiveness. --- clover.xml | 144 +++++++++++++-------------- tests/Unit/DataConfigurationTest.php | 95 ++++++++++++++++++ tests/Unit/Support/ArrTest.php | 2 +- 3 files changed, 168 insertions(+), 73 deletions(-) create mode 100644 tests/Unit/DataConfigurationTest.php diff --git a/clover.xml b/clover.xml index fe479d5..05321c4 100644 --- a/clover.xml +++ b/clover.xml @@ -1,6 +1,6 @@ - - + + @@ -267,69 +267,69 @@ - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + + + - + - - - - + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -789,13 +789,13 @@ - + - - - - - + + + + + @@ -1487,18 +1487,18 @@ - + - - - - - - - - - - + + + + + + + + + + @@ -1529,6 +1529,6 @@ - + diff --git a/tests/Unit/DataConfigurationTest.php b/tests/Unit/DataConfigurationTest.php new file mode 100644 index 0000000..f815621 --- /dev/null +++ b/tests/Unit/DataConfigurationTest.php @@ -0,0 +1,95 @@ +normalizers->baseNormalizers; + + $config = DataConfiguration::getInstance([ + 'normalizers' => [ + 'baseNormalizers' => [ + ArrayNormalizer::class, + ], + ], + ], forceCreate: true); + + self::assertNotEquals($baseNormalizers, $config->normalizers->baseNormalizers); + self::assertEquals([ArrayNormalizer::class], $config->normalizers->baseNormalizers); + + self::resetConfig(); + } + + /** + * @throws Throwable + */ + #[Test] + public function will_throw_an_exception_if_invalid_normalizers_are_provided(): void + { + self::expectException(DataConfigurationException::class); + + DataConfiguration::getInstance([ + 'normalizers' => [ + 'baseNormalizers' => [ + 'invalidNormalizer', + ], + ], + ], forceCreate: true); + } + + /** + * @throws Throwable + */ + #[Test] + public function will_throw_an_exception_if_invalid_serializers_are_provided(): void + { + self::expectException(DataConfigurationException::class); + + DataConfiguration::getInstance([ + 'serializers' => [ + 'baseSerializers' => [ + 'invalidSerializer', + ], + ], + ], forceCreate: true); + } +} diff --git a/tests/Unit/Support/ArrTest.php b/tests/Unit/Support/ArrTest.php index 5054152..633b923 100644 --- a/tests/Unit/Support/ArrTest.php +++ b/tests/Unit/Support/ArrTest.php @@ -20,7 +20,7 @@ final class ArrTest extends UnitCase #[Test] #[DataProvider('get_arr_provider')] #[DataProvider('is_array_of_class_strings_provider')] - public function test_arr_helper_functions( + public function arr_helper_functions( string $functionName, array $parameters, mixed $expected From a196f1a5278b9e44deeaf597e626ee8ce36ff61f Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 16:14:01 -0500 Subject: [PATCH 7/8] Ensure DataConfiguration links to Normalizers and Serializers Add assertions to verify that DataConfiguration properly points to the instances of NormalizersConfiguration and SerializersConfiguration. This strengthens the test coverage and ensures configuration consistency. --- tests/Unit/DataConfigurationTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/Unit/DataConfigurationTest.php b/tests/Unit/DataConfigurationTest.php index f815621..9be82eb 100644 --- a/tests/Unit/DataConfigurationTest.php +++ b/tests/Unit/DataConfigurationTest.php @@ -26,6 +26,12 @@ public function it_can_create_an_instance_and_override_it_if_needed(): void $config = DataConfiguration::getInstance(); self::assertInstanceOf(DataConfiguration::class, $config); + $normalizersConfig = NormalizersConfiguration::getInstance(); + self::assertSame($config->normalizers, $normalizersConfig); + + $serializersConfig = SerializersConfiguration::getInstance(); + self::assertSame($config->serializers, $serializersConfig); + $sameConfig = DataConfiguration::getInstance(); self::assertSame($config, $sameConfig); From 6e524abfac2bd6f314a9446466615ab1610021c3 Mon Sep 17 00:00:00 2001 From: Fa-BRAIK Date: Tue, 31 Dec 2024 16:35:04 -0500 Subject: [PATCH 8/8] Add DataConfiguration documentation and improve doc notes Introduces a new `DataConfiguration.md` file to explain configuration handling. Updates existing documentation with improved note formatting for clarity. Adjusts code structure in configuration-related files for better serialization and normalization setup. --- docs/CloneableData.md | 4 +- docs/DataConfiguration.md | 43 +++++++++++++++++++ docs/DefaultValues.md | 2 +- docs/EmptyData.md | 2 +- docs/Normalizers.md | 7 ++- docs/Quickstart.md | 1 + src/Configuration/DataConfiguration.php | 10 ++--- .../NormalizersConfiguration.php | 7 +-- .../SerializersConfiguration.php | 8 +--- 9 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 docs/DataConfiguration.md diff --git a/docs/CloneableData.md b/docs/CloneableData.md index 3f3143a..004ca7f 100644 --- a/docs/CloneableData.md +++ b/docs/CloneableData.md @@ -53,11 +53,11 @@ $todoWithDueDate = $todo->with( ); ``` -> We are using the `empty` method +> **Note:** We are using the `empty` method > from [Empty Data](https://github.com/nuxtifyts/php-dto/blob/main/docs/EmptyData.md) > here -> `emptyTodo`, `todo` and `todoWithDueDate` are all different instances. +> **Important:** `emptyTodo`, `todo` and `todoWithDueDate` are all different instances. Computed properties - diff --git a/docs/DataConfiguration.md b/docs/DataConfiguration.md new file mode 100644 index 0000000..2e73108 --- /dev/null +++ b/docs/DataConfiguration.md @@ -0,0 +1,43 @@ +Data Configuration += + +The library uses a configuration based approach to define, load and save data. +It uses a `DataConfiguration` object to define many things: + +```php +use Nuxtifyts\PhpDto\Configuration\DataConfiguration; + +$config = DataConfiguration::getInstance(); +``` + +The function's signature is: + +| Argument | Type | Description | +|-------------|---------------|-------------------------------------------------------------------------------------------------| +| config | array \| null | The configuration array to load, by default it's `null`, which means switch to default configs. | +| forceCreate | bool | If `true`, it will create a new instance of `DataConfiguration` even if it's already created. | + +If nothing is passed, it will be the equivalent of: + +```php +DataConfiguration::getInstance([ + 'normalizers' => [ + 'baseNormalizers' => [ + JsonStringNormalizer::class, + StdClassNormalizer::class, + ArrayAccessNormalizer::class, + ArrayNormalizer::class, + ], + ], + + 'serializers' => [ + 'baseSerializers' => [ + ArraySerializer::class, + DataSerializer::class, + DateTimeSerializer::class, + BackedEnumSerializer::class, + ScalarTypeSerializer::class, + ] + ] +]) +``` diff --git a/docs/DefaultValues.md b/docs/DefaultValues.md index 0ff45e9..e076321 100644 --- a/docs/DefaultValues.md +++ b/docs/DefaultValues.md @@ -70,7 +70,7 @@ class UserConfigDataFallbackResolver implements FallbackResolver } ``` ->! When using `DefaultsTo` attribute, priority is given to the attribute instead of the parameter's default value. +> When using `DefaultsTo` attribute, priority is given to the attribute instead of the parameter's default value. If ever needed to create a new instance of a DTO with complex default value, using the constructor is no longer possible, instead, you can make use of the diff --git a/docs/EmptyData.md b/docs/EmptyData.md index e83acdb..ed40dc4 100644 --- a/docs/EmptyData.md +++ b/docs/EmptyData.md @@ -36,7 +36,7 @@ By calling the `empty()` method, we can create a new instance of the `TodoData` $emptyTodo = TodoData::empty(); ``` -> This is really useful with [Cloneable Data](https://github.com/nuxtifyts/php-dto/blob/main/docs/CloneableData.md) +> **Note:** This is really useful with [Cloneable Data](https://github.com/nuxtifyts/php-dto/blob/main/docs/CloneableData.md) The `$emptyTodo` variable will contain the following data: diff --git a/docs/Normalizers.md b/docs/Normalizers.md index a08c39c..7d83418 100644 --- a/docs/Normalizers.md +++ b/docs/Normalizers.md @@ -7,10 +7,15 @@ data into (preferable) an `array`/`ArrayAccess`. By default, there are 4 normalizers: - **JsonStringNormalizer** will cast json string. -- **StrClassNormalizer** will cast stdObject. +- **StdClassNormalizer** will cast stdObject. - **ArrayAccessNormalizer** will cast ArrayAccess. - **ArrayNormalizer** will cast array. +> **Note:** In order to adjust there default normalizers, for example, you don't need +> to use the `StdClassNormalizer` and you want to simply remove it, you need +> to [configure](https://github.com/nuxtifyts/php-dto/blob/main/docs/DataConfiguration.md) +> the `DataConfiguration` object. + Custom normalizers: = diff --git a/docs/Quickstart.md b/docs/Quickstart.md index 9378613..51d25e4 100644 --- a/docs/Quickstart.md +++ b/docs/Quickstart.md @@ -81,3 +81,4 @@ can be found here: - [Data Refiners](https://github.com/nuxtifyts/php-dto/blob/main/docs/DataRefiners.md) - [Empty Data](https://github.com/nuxtifyts/php-dto/blob/main/docs/EmptyData.md) - [Cloneable Data](https://github.com/nuxtifyts/php-dto/blob/main/docs/CloneableData.md) +- [Data Configuration](https://github.com/nuxtifyts/php-dto/blob/main/docs/DataConfiguration.md) diff --git a/src/Configuration/DataConfiguration.php b/src/Configuration/DataConfiguration.php index 306d785..cfc2046 100644 --- a/src/Configuration/DataConfiguration.php +++ b/src/Configuration/DataConfiguration.php @@ -10,8 +10,8 @@ class DataConfiguration implements Configuration protected static ?self $instance = null; protected function __construct( - protected(set) SerializersConfiguration $serializers, protected(set) NormalizersConfiguration $normalizers, + protected(set) SerializersConfiguration $serializers, ) { } @@ -29,13 +29,13 @@ public static function getInstance( } return self::$instance = new self( - serializers: SerializersConfiguration::getInstance( - Arr::getArray($config ?? [], 'serializers'), - $forceCreate - ), normalizers: NormalizersConfiguration::getInstance( Arr::getArray($config ?? [], 'normalizers'), $forceCreate + ), + serializers: SerializersConfiguration::getInstance( + Arr::getArray($config ?? [], 'serializers'), + $forceCreate ) ); } diff --git a/src/Configuration/NormalizersConfiguration.php b/src/Configuration/NormalizersConfiguration.php index 7a74b88..27edf61 100644 --- a/src/Configuration/NormalizersConfiguration.php +++ b/src/Configuration/NormalizersConfiguration.php @@ -18,12 +18,7 @@ class NormalizersConfiguration implements Configuration * @param array> $baseNormalizers */ protected function __construct( - protected(set) array $baseNormalizers = [ - JsonStringNormalizer::class, - StdClassNormalizer::class, - ArrayAccessNormalizer::class, - ArrayNormalizer::class, - ] + protected(set) array $baseNormalizers ) { } diff --git a/src/Configuration/SerializersConfiguration.php b/src/Configuration/SerializersConfiguration.php index 64af6d7..647af9c 100644 --- a/src/Configuration/SerializersConfiguration.php +++ b/src/Configuration/SerializersConfiguration.php @@ -19,13 +19,7 @@ class SerializersConfiguration implements Configuration * @param array> $baseSerializers */ protected function __construct( - protected(set) array $baseSerializers = [ - ArraySerializer::class, - DataSerializer::class, - DateTimeSerializer::class, - BackedEnumSerializer::class, - ScalarTypeSerializer::class, - ], + protected(set) array $baseSerializers, ) { }