Valinor/tests/Integration/Mapping/Object/DateTimeMappingTest.php

147 lines
5.5 KiB
PHP
Raw Normal View History

2021-11-28 17:43:02 +01:00
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
2021-11-28 17:43:02 +01:00
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Object\DateTimeObjectBuilder;
2021-11-28 17:43:02 +01:00
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use function random_int;
2021-11-28 17:43:02 +01:00
final class DateTimeMappingTest extends IntegrationTest
{
public function test_datetime_properties_are_converted_properly(): void
{
$dateTimeInterface = new DateTimeImmutable('@' . $this->buildRandomTimestamp());
$dateTimeImmutable = new DateTimeImmutable('@' . $this->buildRandomTimestamp());
$dateTimeFromTimestamp = $this->buildRandomTimestamp();
2021-11-28 17:43:02 +01:00
$dateTimeFromTimestampWithFormat = [
'datetime' => $this->buildRandomTimestamp(),
2021-11-28 17:43:02 +01:00
'format' => 'U',
];
$dateTimeFromAtomFormat = (new DateTime())->setTimestamp($this->buildRandomTimestamp())->format(DATE_ATOM);
2021-11-28 17:43:02 +01:00
$dateTimeFromArray = [
'datetime' => (new DateTime('@' . $this->buildRandomTimestamp()))->format('Y-m-d H:i:s'),
2021-11-28 17:43:02 +01:00
'format' => 'Y-m-d H:i:s',
];
$mysqlDate = (new DateTime('@' . $this->buildRandomTimestamp()))->format('Y-m-d H:i:s');
$pgsqlDate = (new DateTime('@' . $this->buildRandomTimestamp()))->format('Y-m-d H:i:s.u');
2021-11-28 17:43:02 +01:00
try {
$result = $this->mapperBuilder->mapper()->map(AllDateTimeValues::class, [
'dateTimeInterface' => $dateTimeInterface,
'dateTimeImmutable' => $dateTimeImmutable,
'dateTimeFromTimestamp' => $dateTimeFromTimestamp,
'dateTimeFromTimestampWithFormat' => $dateTimeFromTimestampWithFormat,
'dateTimeFromAtomFormat' => $dateTimeFromAtomFormat,
'dateTimeFromArray' => $dateTimeFromArray,
'mysqlDate' => $mysqlDate,
'pgsqlDate' => $pgsqlDate,
2021-11-28 17:43:02 +01:00
]);
} catch (MappingError $error) {
$this->mappingFail($error);
}
self::assertInstanceOf(DateTimeImmutable::class, $result->dateTimeInterface);
self::assertEquals($dateTimeInterface, $result->dateTimeInterface);
self::assertEquals($dateTimeImmutable, $result->dateTimeImmutable);
self::assertEquals(new DateTimeImmutable("@$dateTimeFromTimestamp"), $result->dateTimeFromTimestamp);
self::assertEquals(new DateTimeImmutable("@{$dateTimeFromTimestampWithFormat['datetime']}"), $result->dateTimeFromTimestampWithFormat);
self::assertEquals(DateTimeImmutable::createFromFormat(DATE_ATOM, $dateTimeFromAtomFormat), $result->dateTimeFromAtomFormat);
self::assertEquals(DateTimeImmutable::createFromFormat($dateTimeFromArray['format'], $dateTimeFromArray['datetime']), $result->dateTimeFromArray);
self::assertEquals(DateTimeImmutable::createFromFormat(DateTimeObjectBuilder::DATE_MYSQL, $mysqlDate), $result->mysqlDate);
self::assertEquals(DateTimeImmutable::createFromFormat(DateTimeObjectBuilder::DATE_PGSQL, $pgsqlDate), $result->pgsqlDate);
2021-11-28 17:43:02 +01:00
}
public function test_invalid_datetime_throws_exception(): void
{
try {
$this->mapperBuilder
->mapper()
->map(SimpleDateTimeValues::class, [
'dateTime' => 'invalid datetime',
]);
} catch (MappingError $exception) {
feat!: add access to root node when error occurs during mapping When an error occurs during mapping, the root instance of `Node` can now be accessed from the exception. This recursive object allows retrieving all needed information through the whole mapping tree: path, values, types and messages, including the issues that caused the exception. It can be used like the following: ```php try { (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, [/* ... */]); } catch (\CuyZ\Valinor\Mapper\MappingError $error) { // Do something with `$error->node()` // See README for more information } ``` This change removes the method `MappingError::describe()` which provided a flattened view of messages of all the errors that were encountered during the mapping. The same behaviour can still be retrieved, see the example below: ```php use CuyZ\Valinor\Mapper\Tree\Message\Message; use CuyZ\Valinor\Mapper\Tree\Node; /** * @implements \IteratorAggregate<string, array<\Throwable&Message>> */ final class MappingErrorList implements \IteratorAggregate { private Node $node; public function __construct(Node $node) { $this->node = $node; } /** * @return \Traversable<string, array<\Throwable&Message>> */ public function getIterator(): \Traversable { yield from $this->errors($this->node); } /** * @return \Traversable<string, array<\Throwable&Message>> */ private function errors(Node $node): \Traversable { $errors = array_filter( $node->messages(), static fn (Message $m) => $m instanceof \Throwable ); if (! empty($errors)) { yield $node->path() => array_values($errors); } foreach ($node->children() as $child) { yield from $this->errors($child); } } } try { (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, [/* ... */]); } catch (\CuyZ\Valinor\Mapper\MappingError $error) { $errors = iterator_to_array(new MappingErrorList($error->node())); } ``` The class `CannotMapObject` is deleted, as it does not provide any value; this means that `MappingError` which was previously an interface becomes a class.
2021-12-16 00:00:45 +01:00
$error = $exception->node()->children()['dateTime']->messages()[0];
self::assertSame('1630686564', $error->code());
self::assertSame('Impossible to convert `invalid datetime` to `DateTime`.', (string)$error);
2021-11-28 17:43:02 +01:00
}
}
public function test_invalid_datetime_from_array_throws_exception(): void
{
try {
$this->mapperBuilder
->mapper()
->map(SimpleDateTimeValues::class, [
'dateTime' => [
'datetime' => 1337,
'format' => 'H',
],
]);
} catch (MappingError $exception) {
feat!: add access to root node when error occurs during mapping When an error occurs during mapping, the root instance of `Node` can now be accessed from the exception. This recursive object allows retrieving all needed information through the whole mapping tree: path, values, types and messages, including the issues that caused the exception. It can be used like the following: ```php try { (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, [/* ... */]); } catch (\CuyZ\Valinor\Mapper\MappingError $error) { // Do something with `$error->node()` // See README for more information } ``` This change removes the method `MappingError::describe()` which provided a flattened view of messages of all the errors that were encountered during the mapping. The same behaviour can still be retrieved, see the example below: ```php use CuyZ\Valinor\Mapper\Tree\Message\Message; use CuyZ\Valinor\Mapper\Tree\Node; /** * @implements \IteratorAggregate<string, array<\Throwable&Message>> */ final class MappingErrorList implements \IteratorAggregate { private Node $node; public function __construct(Node $node) { $this->node = $node; } /** * @return \Traversable<string, array<\Throwable&Message>> */ public function getIterator(): \Traversable { yield from $this->errors($this->node); } /** * @return \Traversable<string, array<\Throwable&Message>> */ private function errors(Node $node): \Traversable { $errors = array_filter( $node->messages(), static fn (Message $m) => $m instanceof \Throwable ); if (! empty($errors)) { yield $node->path() => array_values($errors); } foreach ($node->children() as $child) { yield from $this->errors($child); } } } try { (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, [/* ... */]); } catch (\CuyZ\Valinor\Mapper\MappingError $error) { $errors = iterator_to_array(new MappingErrorList($error->node())); } ``` The class `CannotMapObject` is deleted, as it does not provide any value; this means that `MappingError` which was previously an interface becomes a class.
2021-12-16 00:00:45 +01:00
$error = $exception->node()->children()['dateTime']->messages()[0];
self::assertSame('1630686564', $error->code());
self::assertSame('Impossible to convert `1337` to `DateTime`.', (string)$error);
2021-11-28 17:43:02 +01:00
}
}
public function test_invalid_array_source_throws_exception(): void
{
try {
$this->mapperBuilder
->mapper()
->map(SimpleDateTimeValues::class, [
'dateTime' => [
'invalid key' => '2012-12-21T13:37:42+00:00',
],
]);
} catch (MappingError $exception) {
$error = $exception->node()->children()['dateTime']->children()['value']->messages()[0];
2021-11-28 17:43:02 +01:00
self::assertSame('1607027306', $error->code());
2021-11-28 17:43:02 +01:00
}
}
private function buildRandomTimestamp(): int
{
return random_int(1, 32503726800);
}
2021-11-28 17:43:02 +01:00
}
final class SimpleDateTimeValues
{
public DateTimeInterface $dateTimeInterface;
public DateTimeImmutable $dateTimeImmutable;
public DateTime $dateTime;
}
final class AllDateTimeValues
{
public DateTimeInterface $dateTimeInterface;
public DateTimeImmutable $dateTimeImmutable;
public DateTime $dateTimeFromTimestamp;
public DateTime $dateTimeFromTimestampWithFormat;
public DateTimeInterface $dateTimeFromAtomFormat;
public DateTimeInterface $dateTimeFromArray;
public DateTimeInterface $mysqlDate;
public DateTimeInterface $pgsqlDate;
2021-11-28 17:43:02 +01:00
}