From 1c70c2d29ad9a70acc952031e5726e4ea492bad3 Mon Sep 17 00:00:00 2001 From: Romain Canon Date: Mon, 10 Jul 2023 17:16:49 +0200 Subject: [PATCH] fix: allow overriding of supported datetime formats --- src/Library/Container.php | 2 +- src/Library/Settings.php | 6 +++++ .../Factory/DateTimeObjectBuilderFactory.php | 22 +++++++------------ src/MapperBuilder.php | 6 +++-- .../Mapping/Object/DateTimeMappingTest.php | 16 ++++++++++++++ tests/Unit/MapperBuilderTest.php | 2 ++ 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Library/Container.php b/src/Library/Container.php index dfa2f9c2..3fc9618f 100644 --- a/src/Library/Container.php +++ b/src/Library/Container.php @@ -165,7 +165,7 @@ public function __construct(Settings $settings) $factory = new ReflectionObjectBuilderFactory(); $factory = new ConstructorObjectBuilderFactory($factory, $settings->nativeConstructors, $constructors); $factory = new DateTimeZoneObjectBuilderFactory($factory, $this->get(FunctionDefinitionRepository::class)); - $factory = new DateTimeObjectBuilderFactory($factory, $this->get(FunctionDefinitionRepository::class)); + $factory = new DateTimeObjectBuilderFactory($factory, $settings->supportedDateFormats, $this->get(FunctionDefinitionRepository::class)); $factory = new CollisionObjectBuilderFactory($factory); if (! $settings->allowPermissiveTypes) { diff --git a/src/Library/Settings.php b/src/Library/Settings.php index e18ebde2..143eac2f 100644 --- a/src/Library/Settings.php +++ b/src/Library/Settings.php @@ -13,6 +13,9 @@ /** @internal */ final class Settings { + /** @var non-empty-array */ + public const DEFAULT_SUPPORTED_DATETIME_FORMATS = [DATE_ATOM, 'U']; + /** @var array */ public array $inferredMapping = []; @@ -28,6 +31,9 @@ final class Settings /** @var CacheInterface */ public CacheInterface $cache; + /** @var non-empty-array */ + public array $supportedDateFormats = self::DEFAULT_SUPPORTED_DATETIME_FORMATS; + public bool $enableFlexibleCasting = false; public bool $allowSuperfluousKeys = false; diff --git a/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php b/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php index b3960b61..f1c80bc2 100644 --- a/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php +++ b/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php @@ -7,6 +7,7 @@ use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\FunctionObject; use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository; +use CuyZ\Valinor\Library\Settings; use CuyZ\Valinor\Mapper\Object\DateTimeFormatConstructor; use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder; use CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder; @@ -23,6 +24,8 @@ final class DateTimeObjectBuilderFactory implements ObjectBuilderFactory { public function __construct( private ObjectBuilderFactory $delegate, + /** @var non-empty-array */ + private array $supportedDateFormats, private FunctionDefinitionRepository $functionDefinitionRepository ) {} @@ -39,27 +42,18 @@ public function for(ClassDefinition $class): array // Remove `DateTime` & `DateTimeImmutable` native constructors $builders = array_filter($builders, fn (ObjectBuilder $builder) => ! $builder instanceof NativeConstructorObjectBuilder); - $useDefaultBuilder = true; + $buildersWithOneArgument = array_filter($builders, fn (ObjectBuilder $builder) => count($builder->describeArguments()) === 1); - foreach ($builders as $builder) { - if (count($builder->describeArguments()) === 1) { - $useDefaultBuilder = false; - // @infection-ignore-all - break; - } - } - - if ($useDefaultBuilder) { - // @infection-ignore-all / Ignore memoization - $builders[] = $this->defaultBuilder($class->type()); + if (count($buildersWithOneArgument) === 0 || $this->supportedDateFormats !== Settings::DEFAULT_SUPPORTED_DATETIME_FORMATS) { + $builders[] = $this->internalDateTimeBuilder($class->type()); } return $builders; } - private function defaultBuilder(ClassType $type): FunctionObjectBuilder + private function internalDateTimeBuilder(ClassType $type): FunctionObjectBuilder { - $constructor = new DateTimeFormatConstructor(DATE_ATOM, 'U'); + $constructor = new DateTimeFormatConstructor(...$this->supportedDateFormats); $function = new FunctionObject($this->functionDefinitionRepository->for($constructor), $constructor); return new FunctionObjectBuilder($function, $type); diff --git a/src/MapperBuilder.php b/src/MapperBuilder.php index db4719f4..6ff7e1fc 100644 --- a/src/MapperBuilder.php +++ b/src/MapperBuilder.php @@ -7,7 +7,6 @@ use CuyZ\Valinor\Library\Container; use CuyZ\Valinor\Library\Settings; use CuyZ\Valinor\Mapper\ArgumentsMapper; -use CuyZ\Valinor\Mapper\Object\DateTimeFormatConstructor; use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage; use CuyZ\Valinor\Mapper\TreeMapper; use Psr\SimpleCache\CacheInterface; @@ -235,7 +234,10 @@ public function registerConstructor(callable|string ...$constructors): self */ public function supportDateFormats(string $format, string ...$formats): self { - return $this->registerConstructor(new DateTimeFormatConstructor($format, ...$formats)); + $clone = clone $this; + $clone->settings->supportedDateFormats = [$format, ...$formats]; + + return $clone; } /** diff --git a/tests/Integration/Mapping/Object/DateTimeMappingTest.php b/tests/Integration/Mapping/Object/DateTimeMappingTest.php index 0fb5bf42..b9803a04 100644 --- a/tests/Integration/Mapping/Object/DateTimeMappingTest.php +++ b/tests/Integration/Mapping/Object/DateTimeMappingTest.php @@ -118,4 +118,20 @@ public function test_registered_date_constructor_with_invalid_source_throws_exce self::assertSame("Value 'invalid datetime' does not match any of the following formats: `Y/m/d`.", (string)$error); } } + + public function test_date_constructor_with_overridden_format_source_throws_exception(): void + { + try { + (new MapperBuilder()) + ->supportDateFormats('Y/m/d') + ->supportDateFormats('d/m/Y') + ->mapper() + ->map(DateTimeInterface::class, '1971-11-08'); + } catch (MappingError $exception) { + $error = $exception->node()->messages()[0]; + + self::assertSame('1630686564', $error->code()); + self::assertSame("Value '1971-11-08' does not match any of the following formats: `d/m/Y`.", (string)$error); + } + } } diff --git a/tests/Unit/MapperBuilderTest.php b/tests/Unit/MapperBuilderTest.php index 503990d7..85744784 100644 --- a/tests/Unit/MapperBuilderTest.php +++ b/tests/Unit/MapperBuilderTest.php @@ -34,6 +34,7 @@ public function test_builder_methods_return_clone_of_builder_instance(): void $builderG = $builderA->allowPermissiveTypes(); $builderH = $builderA->filterExceptions(fn () => new FakeErrorMessage()); $builderI = $builderA->withCache(new FakeCache()); + $builderJ = $builderA->supportDateFormats('Y-m-d'); self::assertNotSame($builderA, $builderB); self::assertNotSame($builderA, $builderC); @@ -43,6 +44,7 @@ public function test_builder_methods_return_clone_of_builder_instance(): void self::assertNotSame($builderA, $builderG); self::assertNotSame($builderA, $builderH); self::assertNotSame($builderA, $builderI); + self::assertNotSame($builderA, $builderJ); } public function test_mapper_instance_is_the_same(): void