Skip to content

Commit

Permalink
fix: allow overriding of supported datetime formats
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed Jul 10, 2023
1 parent 7a7826e commit 1c70c2d
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/Library/Container.php
Expand Up @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions src/Library/Settings.php
Expand Up @@ -13,6 +13,9 @@
/** @internal */
final class Settings
{
/** @var non-empty-array<non-empty-string> */
public const DEFAULT_SUPPORTED_DATETIME_FORMATS = [DATE_ATOM, 'U'];

/** @var array<class-string|interface-string, callable> */
public array $inferredMapping = [];

Expand All @@ -28,6 +31,9 @@ final class Settings
/** @var CacheInterface<mixed> */
public CacheInterface $cache;

/** @var non-empty-array<non-empty-string> */
public array $supportedDateFormats = self::DEFAULT_SUPPORTED_DATETIME_FORMATS;

public bool $enableFlexibleCasting = false;

public bool $allowSuperfluousKeys = false;
Expand Down
22 changes: 8 additions & 14 deletions src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php
Expand Up @@ -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;
Expand All @@ -23,6 +24,8 @@ final class DateTimeObjectBuilderFactory implements ObjectBuilderFactory
{
public function __construct(
private ObjectBuilderFactory $delegate,
/** @var non-empty-array<non-empty-string> */
private array $supportedDateFormats,
private FunctionDefinitionRepository $functionDefinitionRepository
) {}

Expand All @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions src/MapperBuilder.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down
16 changes: 16 additions & 0 deletions tests/Integration/Mapping/Object/DateTimeMappingTest.php
Expand Up @@ -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);
}
}
}
2 changes: 2 additions & 0 deletions tests/Unit/MapperBuilderTest.php
Expand Up @@ -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);
Expand All @@ -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
Expand Down

0 comments on commit 1c70c2d

Please sign in to comment.