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;
|
|
|
|
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
|
|
|
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
|
|
|
|
use stdClass;
|
|
|
|
|
|
|
|
final class ShapedArrayValuesMappingTest extends IntegrationTest
|
|
|
|
{
|
|
|
|
public function test_values_are_mapped_properly(): void
|
|
|
|
{
|
|
|
|
$source = [
|
2021-12-02 22:33:25 +01:00
|
|
|
'basicShapedArrayWithExcessiveKey' => [
|
|
|
|
'foo' => 'foo',
|
|
|
|
'bar' => 42,
|
|
|
|
],
|
2021-11-29 12:15:26 +01:00
|
|
|
'basicShapedArrayWithStringKeys' => [
|
|
|
|
'foo' => 'fiz',
|
|
|
|
'bar' => 42,
|
|
|
|
],
|
|
|
|
'basicShapedArrayWithIntegerKeys' => [
|
|
|
|
0 => 'fiz',
|
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
|
|
|
1 => 42.404,
|
2021-11-28 17:43:02 +01:00
|
|
|
],
|
|
|
|
'shapedArrayWithObject' => [
|
|
|
|
'foo' => ['value' => 'bar'],
|
|
|
|
],
|
|
|
|
'shapedArrayWithOptionalValue' => [
|
|
|
|
'optionalString' => 'some value',
|
|
|
|
],
|
2021-11-30 12:50:10 +01:00
|
|
|
'shapedArrayOnSeveralLines' => [
|
|
|
|
'foo' => 'fiz',
|
|
|
|
'bar' => 42,
|
|
|
|
],
|
2021-11-28 17:43:02 +01:00
|
|
|
'advancedShapedArray' => [
|
|
|
|
'mandatoryString' => 'bar',
|
|
|
|
1337,
|
|
|
|
'42.404',
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ([ShapedArrayValues::class, ShapedArrayValuesWithConstructor::class] as $class) {
|
|
|
|
try {
|
|
|
|
$result = $this->mapperBuilder->mapper()->map($class, $source);
|
|
|
|
} catch (MappingError $error) {
|
|
|
|
$this->mappingFail($error);
|
|
|
|
}
|
|
|
|
|
2021-12-02 22:33:25 +01:00
|
|
|
self::assertSame(['foo' => 'foo'], $result->basicShapedArrayWithExcessiveKey);
|
2021-11-29 12:15:26 +01:00
|
|
|
self::assertSame($source['basicShapedArrayWithStringKeys'], $result->basicShapedArrayWithStringKeys);
|
|
|
|
self::assertSame($source['basicShapedArrayWithIntegerKeys'], $result->basicShapedArrayWithIntegerKeys);
|
2022-02-19 15:27:30 +01:00
|
|
|
self::assertInstanceOf(SimpleObject::class, $result->shapedArrayWithObject['foo']); // @phpstan-ignore-line
|
2021-11-28 17:43:02 +01:00
|
|
|
self::assertSame($source['shapedArrayWithOptionalValue'], $result->shapedArrayWithOptionalValue);
|
2021-11-30 12:50:10 +01:00
|
|
|
self::assertSame($source['shapedArrayOnSeveralLines'], $result->shapedArrayOnSeveralLines);
|
2021-11-28 17:43:02 +01:00
|
|
|
self::assertSame('bar', $result->advancedShapedArray['mandatoryString']);
|
|
|
|
self::assertSame(1337, $result->advancedShapedArray[0]);
|
|
|
|
self::assertSame(42.404, $result->advancedShapedArray[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_value_that_cannot_be_casted_throws_exception(): void
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$this->mapperBuilder->mapper()->map(ShapedArrayValues::class, [
|
2021-11-29 12:15:26 +01:00
|
|
|
'basicShapedArrayWithStringKeys' => [
|
|
|
|
'foo' => new stdClass(),
|
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
|
|
|
'bar' => 42,
|
2021-11-29 12:15:26 +01:00
|
|
|
],
|
2021-11-28 17:43:02 +01:00
|
|
|
]);
|
|
|
|
} 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()['basicShapedArrayWithStringKeys']->children()['foo']->messages()[0];
|
|
|
|
|
2021-12-31 13:10:20 +01:00
|
|
|
self::assertSame('1618736242', $error->code());
|
|
|
|
self::assertSame('Cannot cast value of type `stdClass` to `string`.', (string)$error);
|
2021-11-28 17:43:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ShapedArrayValues
|
|
|
|
{
|
2021-12-02 22:33:25 +01:00
|
|
|
/** @var array{foo: string} */
|
|
|
|
public array $basicShapedArrayWithExcessiveKey;
|
|
|
|
|
2021-11-29 12:15:26 +01:00
|
|
|
/** @var array{foo: string, bar: int} */
|
|
|
|
public array $basicShapedArrayWithStringKeys;
|
|
|
|
|
|
|
|
/** @var array{0: string, 1: float} */
|
|
|
|
public array $basicShapedArrayWithIntegerKeys;
|
2021-11-28 17:43:02 +01:00
|
|
|
|
|
|
|
/** @var array{foo: SimpleObject} */
|
|
|
|
public array $shapedArrayWithObject;
|
|
|
|
|
|
|
|
/** @var array{optionalString?: string} */
|
|
|
|
public array $shapedArrayWithOptionalValue;
|
|
|
|
|
2021-11-30 12:50:10 +01:00
|
|
|
/**
|
|
|
|
* @var array{
|
|
|
|
* foo: string,
|
|
|
|
* bar: int
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
public array $shapedArrayOnSeveralLines;
|
|
|
|
|
2021-11-29 12:15:26 +01:00
|
|
|
/** @var array{0: int, float, optionalString?: string, mandatoryString: string} */
|
2021-11-28 17:43:02 +01:00
|
|
|
public array $advancedShapedArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ShapedArrayValuesWithConstructor extends ShapedArrayValues
|
|
|
|
{
|
|
|
|
/**
|
2021-12-02 22:33:25 +01:00
|
|
|
* @param array{foo: string} $basicShapedArrayWithExcessiveKey
|
2021-11-29 12:15:26 +01:00
|
|
|
* @param array{foo: string, bar: int} $basicShapedArrayWithStringKeys
|
|
|
|
* @param array{0: string, 1: float} $basicShapedArrayWithIntegerKeys
|
2021-11-28 17:43:02 +01:00
|
|
|
* @param array{foo: SimpleObject} $shapedArrayWithObject
|
|
|
|
* @param array{optionalString?: string} $shapedArrayWithOptionalValue
|
2021-11-30 12:50:10 +01:00
|
|
|
* @param array{
|
|
|
|
* foo: string,
|
|
|
|
* bar: int
|
|
|
|
* } $shapedArrayOnSeveralLines
|
2021-11-29 12:15:26 +01:00
|
|
|
* @param array{0: int, float, optionalString?: string, mandatoryString: string} $advancedShapedArray
|
2021-11-28 17:43:02 +01:00
|
|
|
*/
|
|
|
|
public function __construct(
|
2021-12-02 22:33:25 +01:00
|
|
|
array $basicShapedArrayWithExcessiveKey,
|
2021-11-29 12:15:26 +01:00
|
|
|
array $basicShapedArrayWithStringKeys,
|
|
|
|
array $basicShapedArrayWithIntegerKeys,
|
2021-11-28 17:43:02 +01:00
|
|
|
array $shapedArrayWithObject,
|
|
|
|
array $shapedArrayWithOptionalValue,
|
2021-11-30 12:50:10 +01:00
|
|
|
array $shapedArrayOnSeveralLines,
|
2021-11-28 17:43:02 +01:00
|
|
|
array $advancedShapedArray
|
|
|
|
) {
|
2021-12-02 22:33:25 +01:00
|
|
|
$this->basicShapedArrayWithExcessiveKey = $basicShapedArrayWithExcessiveKey;
|
2021-11-29 12:15:26 +01:00
|
|
|
$this->basicShapedArrayWithStringKeys = $basicShapedArrayWithStringKeys;
|
|
|
|
$this->basicShapedArrayWithIntegerKeys = $basicShapedArrayWithIntegerKeys;
|
2021-11-28 17:43:02 +01:00
|
|
|
$this->shapedArrayWithObject = $shapedArrayWithObject;
|
|
|
|
$this->shapedArrayWithOptionalValue = $shapedArrayWithOptionalValue;
|
2021-11-30 12:50:10 +01:00
|
|
|
$this->shapedArrayOnSeveralLines = $shapedArrayOnSeveralLines;
|
2021-11-28 17:43:02 +01:00
|
|
|
$this->advancedShapedArray = $advancedShapedArray;
|
|
|
|
}
|
|
|
|
}
|