2022-01-07 13:35:10 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
|
|
|
|
|
|
|
use CuyZ\Valinor\Mapper\MappingError;
|
2022-05-22 20:43:01 +02:00
|
|
|
use CuyZ\Valinor\MapperBuilder;
|
2022-01-07 13:35:10 +01:00
|
|
|
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
|
|
|
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\NativeUnionOfObjects;
|
|
|
|
|
|
|
|
final class UnionOfObjectsMappingTest extends IntegrationTest
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @requires PHP >= 8
|
|
|
|
*/
|
|
|
|
public function test_object_type_is_narrowed_correctly_for_simple_case(): void
|
|
|
|
{
|
|
|
|
try {
|
2022-05-22 20:43:01 +02:00
|
|
|
$resultFoo = (new MapperBuilder())->mapper()->map(NativeUnionOfObjects::class, [
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
'foo' => 'foo',
|
2022-01-07 13:35:10 +01:00
|
|
|
]);
|
2022-05-22 20:43:01 +02:00
|
|
|
$resultBar = (new MapperBuilder())->mapper()->map(NativeUnionOfObjects::class, [
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
'bar' => 'bar',
|
2022-01-07 13:35:10 +01:00
|
|
|
]);
|
|
|
|
} catch (MappingError $error) {
|
|
|
|
$this->mappingFail($error);
|
|
|
|
}
|
|
|
|
|
|
|
|
self::assertInstanceOf(\CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SomeFooObject::class, $resultFoo->object);
|
|
|
|
self::assertInstanceOf(\CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SomeBarObject::class, $resultBar->object);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_object_type_is_narrowed_correctly_for_simple_array_case(): void
|
|
|
|
{
|
|
|
|
try {
|
2022-05-22 20:43:01 +02:00
|
|
|
$result = (new MapperBuilder())->mapper()->map(UnionOfFooAndBar::class, [
|
2022-01-07 13:35:10 +01:00
|
|
|
'foo' => ['foo' => 'foo'],
|
|
|
|
'bar' => ['bar' => 'bar'],
|
|
|
|
]);
|
|
|
|
} catch (MappingError $error) {
|
|
|
|
$this->mappingFail($error);
|
|
|
|
}
|
|
|
|
|
|
|
|
self::assertInstanceOf(SomeFooObject::class, $result->objects['foo']);
|
|
|
|
self::assertInstanceOf(SomeBarObject::class, $result->objects['bar']);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function test_objects_sharing_one_property_are_resolved_correctly(): void
|
|
|
|
{
|
|
|
|
try {
|
2022-05-22 20:43:01 +02:00
|
|
|
$result = (new MapperBuilder())->mapper()->map(UnionOfFooAndBarAndFoo::class, [
|
2022-01-07 13:35:10 +01:00
|
|
|
['foo' => 'foo'],
|
|
|
|
['foo' => 'foo', 'bar' => 'bar'],
|
|
|
|
]);
|
|
|
|
} catch (MappingError $error) {
|
|
|
|
$this->mappingFail($error);
|
|
|
|
}
|
|
|
|
|
|
|
|
self::assertInstanceOf(SomeFooObject::class, $result->objects[0]);
|
|
|
|
self::assertInstanceOf(SomeFooAndBarObject::class, $result->objects[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @dataProvider mapping_error_when_cannot_resolve_union_data_provider
|
|
|
|
*
|
|
|
|
* @param class-string $className
|
|
|
|
* @param mixed[] $source
|
|
|
|
*/
|
|
|
|
public function test_mapping_error_when_cannot_resolve_union(string $className, array $source): void
|
|
|
|
{
|
|
|
|
try {
|
2022-05-22 20:43:01 +02:00
|
|
|
(new MapperBuilder())->mapper()->map($className, $source);
|
2022-01-07 13:35:10 +01:00
|
|
|
|
|
|
|
self::fail('No mapping error when one was expected');
|
|
|
|
} catch (MappingError $exception) {
|
|
|
|
$error = $exception->node()->children()['objects']->children()[0]->messages()[0];
|
|
|
|
|
2022-02-15 00:02:32 +01:00
|
|
|
self::assertSame('1642787246', $error->code());
|
2022-01-07 13:35:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function mapping_error_when_cannot_resolve_union_data_provider(): iterable
|
|
|
|
{
|
|
|
|
yield [
|
|
|
|
'className' => UnionOfFooAndBar::class,
|
|
|
|
'source' => [['foo' => 'foo', 'bar' => 'bar']],
|
|
|
|
];
|
|
|
|
yield [
|
|
|
|
'className' => UnionOfFooAndAnotherFoo::class,
|
|
|
|
'source' => [['foo' => 'foo']],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class UnionOfFooAndBar
|
|
|
|
{
|
|
|
|
/** @var array<SomeFooObject|SomeBarObject> */
|
|
|
|
public array $objects;
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class UnionOfFooAndAnotherFoo
|
|
|
|
{
|
|
|
|
/** @var array<SomeFooObject|SomeOtherFooObject> */
|
|
|
|
public array $objects;
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class UnionOfFooAndBarAndFoo
|
|
|
|
{
|
|
|
|
/** @var array<SomeFooAndBarObject|SomeFooObject> */
|
|
|
|
public array $objects;
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class SomeFooObject
|
|
|
|
{
|
|
|
|
public string $foo;
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class SomeOtherFooObject
|
|
|
|
{
|
|
|
|
public string $foo;
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class SomeBarObject
|
|
|
|
{
|
|
|
|
public string $bar;
|
|
|
|
}
|
|
|
|
|
feat: introduce automatic named constructor resolution
An object may have several ways of being created — in such cases it is
common to use so-called named constructors, also known as static factory
methods. If one or more are found, they can be called during the mapping
to create an instance of the object.
What defines a named constructor is a method that:
1. is public
2. is static
3. returns an instance of the object
4. has one or more arguments
```php
final class Color
{
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
private function __construct(
public readonly int $red,
public readonly int $green,
public readonly int $blue
) {}
/**
* @param int<0, 255> $red
* @param int<0, 255> $green
* @param int<0, 255> $blue
*/
public static function fromRgb(
int $red,
int $green,
int $blue,
): self {
return new self($red, $green, $blue);
}
/**
* @param non-empty-string $hex
*/
public static function fromHex(string $hex): self
{
if (strlen($hex) !== 6) {
throw new DomainException('Must be 6 characters long');
}
/** @var int<0, 255> $red */
$red = hexdec(substr($hex, 0, 2));
/** @var int<0, 255> $green */
$green = hexdec(substr($hex, 2, 2));
/** @var int<0, 255> $blue */
$blue = hexdec(substr($hex, 4, 2));
return new self($red, $green, $blue);
}
}
```
2022-01-21 19:14:00 +01:00
|
|
|
// @PHP8.1 Readonly properties
|
2022-01-07 13:35:10 +01:00
|
|
|
final class SomeFooAndBarObject
|
|
|
|
{
|
|
|
|
public string $foo;
|
|
|
|
|
|
|
|
public string $bar;
|
|
|
|
}
|