Skip to content

Commit

Permalink
feat: add constructor for DateTimeZone with error support
Browse files Browse the repository at this point in the history
```php
try {
    (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(DateTimeZone::class, 'Jupiter/Europa');
} catch (MappingError $exception) {
    $error = $exception->node()->messages()[0];

    // Value 'Jupiter/Europa' is not a valid timezone.
    echo $error->toString();
}
```
  • Loading branch information
romm committed Nov 17, 2022
1 parent 0dfe115 commit a0a4d63
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/Library/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use CuyZ\Valinor\Mapper\Object\Factory\CollisionObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\DateTimeObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\DateTimeZoneObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\ReflectionObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\StrictTypesObjectBuilderFactory;
Expand Down Expand Up @@ -154,6 +155,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 AttributeObjectBuilderFactory($factory);
$factory = new CollisionObjectBuilderFactory($factory);
Expand Down
77 changes: 77 additions & 0 deletions src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Mapper\Object\Factory;

use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\FunctionObject;
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder;
use CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder;
use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
use CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder;
use CuyZ\Valinor\Type\Types\ClassType;
use DateTimeZone;
use Exception;

use function array_filter;
use function count;

/** @internal */
final class DateTimeZoneObjectBuilderFactory implements ObjectBuilderFactory
{
private ObjectBuilderFactory $delegate;

private FunctionDefinitionRepository $functionDefinitionRepository;

public function __construct(ObjectBuilderFactory $delegate, FunctionDefinitionRepository $functionDefinitionRepository)
{
$this->delegate = $delegate;
$this->functionDefinitionRepository = $functionDefinitionRepository;
}

public function for(ClassDefinition $class): array
{
$builders = $this->delegate->for($class);

if ($class->name() !== DateTimeZone::class) {
return $builders;
}

// Remove `DateTimeZone` native constructors
$builders = array_filter($builders, fn (ObjectBuilder $builder) => ! $builder instanceof NativeConstructorObjectBuilder);

$useDefaultBuilder = true;

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());
}

return $builders;
}

private function defaultBuilder(ClassType $type): FunctionObjectBuilder
{
$constructor = function (string $timezone) {
try {
return new DateTimeZone($timezone);
} catch (Exception $exception) {
throw MessageBuilder::newError('Value {source_value} is not a valid timezone.')->build();
}
};

$function = new FunctionObject($this->functionDefinitionRepository->for($constructor), $constructor);

return new FunctionObjectBuilder($function, $type);
}
}
3 changes: 3 additions & 0 deletions src/Mapper/Tree/Message/DefaultMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ interface DefaultMessage
'Value {source_value} is not a valid integer between {min} and {max}.' => [
'en' => 'Value {source_value} is not a valid integer between {min} and {max}.',
],
'Value {source_value} is not a valid timezone.' => [
'en' => 'Value {source_value} is not a valid timezone.',
],
'Value {source_value} is not a valid class string.' => [
'en' => 'Value {source_value} is not a valid class string.',
],
Expand Down
77 changes: 77 additions & 0 deletions tests/Integration/Mapping/Object/DateTimeZoneMappingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;

use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use DateTimeZone;

final class DateTimeZoneMappingTest extends IntegrationTest
{
public function test_can_map_to_timezone_with_default_constructor(): void
{
try {
$result = (new MapperBuilder())->mapper()->map(DateTimeZone::class, 'Europe/Paris');
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame('Europe/Paris', $result->getName());
}

public function test_constructor_with_one_argument_replaces_default_constructor(): void
{
try {
$result = (new MapperBuilder())
->registerConstructor(
fn (string $europeanCity): DateTimeZone => new DateTimeZone("Europe/$europeanCity")
)
->mapper()
->map(DateTimeZone::class, 'Paris');
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame('Europe/Paris', $result->getName());
}

public function test_constructor_with_two_arguments_does_not_replaces_default_constructor(): void
{
$mapper = (new MapperBuilder())->registerConstructor(
fn (string $continent, string $city): DateTimeZone => new DateTimeZone("$continent/$city")
)->mapper();

try {
$result = $mapper->map(DateTimeZone::class, 'Europe/Paris');

self::assertSame('Europe/Paris', $result->getName());
} catch (MappingError $error) {
$this->mappingFail($error);
}

try {
$result = $mapper->map(DateTimeZone::class, [
'continent' => 'Europe',
'city' => 'Paris',
]);

self::assertSame('Europe/Paris', $result->getName());
} catch (MappingError $error) {
$this->mappingFail($error);
}
}

public function test_invalid_timezone_throws_exception(): void
{
try {
(new MapperBuilder())->mapper()->map(DateTimeZone::class, 'Jupiter/Europa');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

self::assertSame("Value 'Jupiter/Europa' is not a valid timezone.", $error->toString());
}
}
}

0 comments on commit a0a4d63

Please sign in to comment.