2021-11-28 17:43:02 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2021-12-29 00:09:34 +01:00
|
|
|
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
2021-11-28 17:43:02 +01:00
|
|
|
|
|
|
|
use CuyZ\Valinor\Mapper\MappingError;
|
2021-12-17 17:55:17 +01:00
|
|
|
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;
|
|
|
|
|
2022-03-03 13:02:01 +01:00
|
|
|
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
|
|
|
|
{
|
2022-03-03 13:02:01 +01:00
|
|
|
$dateTimeInterface = new DateTimeImmutable('@' . $this->buildRandomTimestamp());
|
|
|
|
$dateTimeImmutable = new DateTimeImmutable('@' . $this->buildRandomTimestamp());
|
|
|
|
$dateTimeFromTimestamp = $this->buildRandomTimestamp();
|
2021-11-28 17:43:02 +01:00
|
|
|
$dateTimeFromTimestampWithFormat = [
|
2022-03-03 13:02:01 +01:00
|
|
|
'datetime' => $this->buildRandomTimestamp(),
|
2021-11-28 17:43:02 +01:00
|
|
|
'format' => 'U',
|
|
|
|
];
|
2022-03-03 13:02:01 +01:00
|
|
|
$dateTimeFromAtomFormat = (new DateTime())->setTimestamp($this->buildRandomTimestamp())->format(DATE_ATOM);
|
2021-11-28 17:43:02 +01:00
|
|
|
$dateTimeFromArray = [
|
2022-03-03 13:02:01 +01:00
|
|
|
'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',
|
|
|
|
];
|
2022-03-03 13:02:01 +01:00
|
|
|
$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,
|
2021-12-17 17:55:17 +01:00
|
|
|
'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);
|
2021-12-17 17:55:17 +01:00
|
|
|
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];
|
|
|
|
|
2021-12-31 13:10:20 +01:00
|
|
|
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];
|
|
|
|
|
2021-12-31 13:10:20 +01:00
|
|
|
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) {
|
2022-01-07 13:21:43 +01:00
|
|
|
$error = $exception->node()->children()['dateTime']->children()['value']->messages()[0];
|
2021-11-28 17:43:02 +01:00
|
|
|
|
2022-01-07 13:21:43 +01:00
|
|
|
self::assertSame('1607027306', $error->code());
|
2021-11-28 17:43:02 +01:00
|
|
|
}
|
|
|
|
}
|
2022-03-03 13:02:01 +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;
|
2021-12-17 17:55:17 +01:00
|
|
|
|
|
|
|
public DateTimeInterface $mysqlDate;
|
|
|
|
|
|
|
|
public DateTimeInterface $pgsqlDate;
|
2021-11-28 17:43:02 +01:00
|
|
|
}
|