mirror of
https://github.com/danog/Valinor.git
synced 2024-11-26 20:24:40 +01:00
feat!: allow mapping to any type
Previously, the method `TreeMapper::map` would allow mapping only to an object. It is now possible to map to any type handled by the library. It is for instance possible to map to an array of objects: ```php $objects = (new \CuyZ\Valinor\MapperBuilder())->mapper()->map( 'array<' . SomeClass::class . '>', [/* … */] ); ``` For simple use-cases, an array shape can be used: ```php $array = (new \CuyZ\Valinor\MapperBuilder())->mapper()->map( 'array{foo: string, bar: int}', [/* … */] ); echo strtolower($array['foo']); echo $array['bar'] * 2; ``` This new feature changes the possible behaviour of the mapper, meaning static analysis tools need help to understand the types correctly. An extension for PHPStan and a plugin for Psalm are now provided and can be included in a project to automatically increase the type coverage.
This commit is contained in:
parent
33167d28d0
commit
b2e810e3ce
@ -2,7 +2,8 @@
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()->in([
|
||||
'./src',
|
||||
'./tests'
|
||||
'./tests',
|
||||
'./qa',
|
||||
]);
|
||||
|
||||
if (PHP_VERSION_ID < 8_00_00) {
|
||||
|
125
README.md
125
README.md
@ -125,6 +125,44 @@ public function getThread(int $id): Thread
|
||||
}
|
||||
```
|
||||
|
||||
### Mapping advanced types
|
||||
|
||||
Although it is recommended to map an input to a value object, in some cases
|
||||
mapping to another type can be easier/more flexible.
|
||||
|
||||
It is for instance possible to map to an array of objects:
|
||||
|
||||
```php
|
||||
try {
|
||||
$objects = (new \CuyZ\Valinor\MapperBuilder())
|
||||
->mapper()
|
||||
->map(
|
||||
'array<' . SomeClass::class . '>',
|
||||
[/* … */]
|
||||
);
|
||||
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
|
||||
// Do something…
|
||||
}
|
||||
```
|
||||
|
||||
For simple use-cases, an array shape can be used:
|
||||
|
||||
```php
|
||||
try {
|
||||
$array = (new \CuyZ\Valinor\MapperBuilder())
|
||||
->mapper()
|
||||
->map(
|
||||
'array{foo: string, bar: int}',
|
||||
[/* … */]
|
||||
);
|
||||
|
||||
echo $array['foo'];
|
||||
echo $array['bar'] * 2;
|
||||
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
|
||||
// Do something…
|
||||
}
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
The source given to a mapper can never be trusted, this is actually the very
|
||||
@ -225,7 +263,7 @@ map(new \CuyZ\Valinor\Mapper\Source\FileSource(
|
||||
### Construction strategy
|
||||
|
||||
During the mapping, instances of the objects are created and hydrated with the
|
||||
correct values. construction strategies will determine what values are needed
|
||||
correct values. Construction strategies will determine what values are needed
|
||||
and how an object is built.
|
||||
|
||||
An object can provide either…
|
||||
@ -370,6 +408,91 @@ final class SomeClass
|
||||
}
|
||||
```
|
||||
|
||||
## Static analysis
|
||||
|
||||
To help static analysis of a codebase using this library, an extension for
|
||||
[PHPStan] and a plugin for [Psalm] are provided. They enable these tools to
|
||||
better understand the behaviour of the mapper.
|
||||
|
||||
Considering at least one of those tools are installed on a project, below are
|
||||
examples of the kind of errors that would be reported.
|
||||
|
||||
**Mapping to an array of classes**
|
||||
|
||||
```php
|
||||
final class SomeClass
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $foo,
|
||||
public readonly int $bar,
|
||||
) {}
|
||||
}
|
||||
|
||||
$objects = (new \CuyZ\Valinor\MapperBuilder())
|
||||
->mapper()
|
||||
->map(
|
||||
'array<' . SomeClass::class . '>',
|
||||
[/* … */]
|
||||
);
|
||||
|
||||
foreach ($objects as $object) {
|
||||
// ✅
|
||||
echo $object->foo;
|
||||
|
||||
// ✅
|
||||
echo $object->bar * 2;
|
||||
|
||||
// ❌ Cannot perform operation between `string` and `int`
|
||||
echo $object->foo * $object->bar;
|
||||
|
||||
// ❌ Property `SomeClass::$fiz` is not defined
|
||||
echo $object->fiz;
|
||||
}
|
||||
```
|
||||
|
||||
**Mapping to a shaped array**
|
||||
|
||||
```php
|
||||
$array = (new \CuyZ\Valinor\MapperBuilder())
|
||||
->mapper()
|
||||
->map(
|
||||
'array{foo: string, bar: int}',
|
||||
[/* … */]
|
||||
);
|
||||
|
||||
// ✅
|
||||
echo $array['foo'];
|
||||
|
||||
// ❌ Expected `string` but got `int`
|
||||
echo strtolower($array['bar']);
|
||||
|
||||
// ❌ Cannot perform operation between `string` and `int`
|
||||
echo $array['foo'] * $array['bar'];
|
||||
|
||||
// ❌ Offset `fiz` does not exist on array
|
||||
echo $array['fiz'];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
To activate this feature, the configuration must be updated for the installed
|
||||
tool(s):
|
||||
|
||||
**PHPStan**
|
||||
|
||||
```yaml
|
||||
includes:
|
||||
- vendor/cuyz/valinor/qa/PHPStan/valinor-phpstan-configuration.php
|
||||
```
|
||||
|
||||
**Psalm**
|
||||
|
||||
```xml
|
||||
<plugins>
|
||||
<plugin filename="vendor/cuyz/valinor/qa/Psalm/Plugin/TreeMapperPsalmPlugin.php"/>
|
||||
</plugins>
|
||||
```
|
||||
|
||||
[PHPStan]: https://phpstan.org/
|
||||
|
||||
[Psalm]: https://psalm.dev/
|
||||
|
@ -36,7 +36,8 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"CuyZ\\Valinor\\Tests\\": "tests"
|
||||
"CuyZ\\Valinor\\Tests\\": "tests",
|
||||
"CuyZ\\Valinor\\QA\\": "qa"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -1,4 +1,5 @@
|
||||
includes:
|
||||
- qa/PHPStan/valinor-phpstan-configuration.php
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
|
||||
parameters:
|
||||
@ -6,6 +7,7 @@ parameters:
|
||||
paths:
|
||||
- src
|
||||
- tests
|
||||
- qa/PHPStan
|
||||
ignoreErrors:
|
||||
# \PHPStan\Rules\BooleansInConditions
|
||||
- '#Only booleans are allowed in .* given#'
|
||||
@ -15,6 +17,10 @@ parameters:
|
||||
- '#Construct empty\(\) is not allowed\. Use more strict comparison\.#'
|
||||
|
||||
- '#Method [\w\\:]+_data_provider\(\) return type has no value type specified in iterable type#'
|
||||
|
||||
- message: '#Template type T of method CuyZ\\Valinor\\Mapper\\TreeMapper::map\(\) is not referenced in a parameter#'
|
||||
path: src/Mapper/TreeMapper.php
|
||||
|
||||
stubFiles:
|
||||
- stubs/Psr/SimpleCache/CacheInterface.stub
|
||||
- qa/PHPStan/Stubs/Psr/SimpleCache/CacheInterface.stub
|
||||
tmpDir: var/cache/phpstan
|
||||
|
62
qa/PHPStan/Extension/TreeMapperPHPStanExtension.php
Normal file
62
qa/PHPStan/Extension/TreeMapperPHPStanExtension.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\QA\PHPStan\Extension;
|
||||
|
||||
use CuyZ\Valinor\Mapper\TreeMapper;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\PhpDoc\TypeStringResolver;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\Constant\ConstantStringType;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\Generic\GenericClassStringType;
|
||||
use PHPStan\Type\MixedType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\UnionType;
|
||||
|
||||
final class TreeMapperPHPStanExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
private TypeStringResolver $resolver;
|
||||
|
||||
public function __construct(TypeStringResolver $resolver)
|
||||
{
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
public function getClass(): string
|
||||
{
|
||||
return TreeMapper::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return $methodReflection->getName() === 'map';
|
||||
}
|
||||
|
||||
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
|
||||
{
|
||||
$argument = $methodCall->getArgs()[0]->value;
|
||||
$type = $scope->getType($argument);
|
||||
|
||||
if ($type instanceof UnionType) {
|
||||
return $type->traverse(fn (Type $type) => $this->type($type));
|
||||
}
|
||||
|
||||
return $this->type($type);
|
||||
}
|
||||
|
||||
private function type(Type $type): Type
|
||||
{
|
||||
if ($type instanceof GenericClassStringType) {
|
||||
return $type->getGenericType();
|
||||
}
|
||||
|
||||
if ($type instanceof ConstantStringType) {
|
||||
return $this->resolver->resolve($type->getValue());
|
||||
}
|
||||
|
||||
return new MixedType();
|
||||
}
|
||||
}
|
14
qa/PHPStan/valinor-phpstan-configuration.php
Normal file
14
qa/PHPStan/valinor-phpstan-configuration.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
use CuyZ\Valinor\QA\PHPStan\Extension\TreeMapperPHPStanExtension;
|
||||
|
||||
require_once 'Extension/TreeMapperPHPStanExtension.php';
|
||||
|
||||
return [
|
||||
'services' => [
|
||||
[
|
||||
'class' => TreeMapperPHPStanExtension::class,
|
||||
'tags' => ['phpstan.broker.dynamicMethodReturnTypeExtension']
|
||||
]
|
||||
],
|
||||
];
|
68
qa/Psalm/Plugin/TreeMapperPsalmPlugin.php
Normal file
68
qa/Psalm/Plugin/TreeMapperPsalmPlugin.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\QA\Psalm\Plugin;
|
||||
|
||||
use CuyZ\Valinor\Mapper\TreeMapper;
|
||||
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
|
||||
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic;
|
||||
use Psalm\Type\Atomic\TClassString;
|
||||
use Psalm\Type\Atomic\TDependentGetClass;
|
||||
use Psalm\Type\Atomic\TLiteralString;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
final class TreeMapperPsalmPlugin implements MethodReturnTypeProviderInterface
|
||||
{
|
||||
public static function getClassLikeNames(): array
|
||||
{
|
||||
return [TreeMapper::class];
|
||||
}
|
||||
|
||||
public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Union
|
||||
{
|
||||
if ($event->getMethodNameLowercase() !== 'map') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = $event->getSource()->getNodeTypeProvider()->getType($event->getCallArgs()[0]->value);
|
||||
|
||||
if (! $type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$types = [];
|
||||
|
||||
foreach ($type->getChildNodes() as $node) {
|
||||
$inferred = self::type($node);
|
||||
|
||||
if ($inferred === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$types[] = $inferred;
|
||||
}
|
||||
|
||||
if (count($types) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Type::combineUnionTypeArray($types, $event->getSource()->getCodebase());
|
||||
}
|
||||
|
||||
private static function type(Atomic $node): ?Union
|
||||
{
|
||||
switch (true) {
|
||||
case $node instanceof TLiteralString:
|
||||
return Type::parseString($node->value);
|
||||
case $node instanceof TDependentGetClass:
|
||||
return $node->as_type;
|
||||
case $node instanceof TClassString && $node->as_type:
|
||||
return new Union([$node->as_type]);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper\Exception;
|
||||
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use RuntimeException;
|
||||
|
||||
final class InvalidMappingType extends RuntimeException
|
||||
{
|
||||
public function __construct(Type $type)
|
||||
{
|
||||
parent::__construct(
|
||||
"Can not map type `$type`, it must be an object type.",
|
||||
1630959731
|
||||
);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ final class MappingError extends RuntimeException
|
||||
$this->node = $node;
|
||||
|
||||
parent::__construct(
|
||||
"Could not map an object of type `{$node->type()}` with the given source.",
|
||||
"Could not map type `{$node->type()}` with the given source.",
|
||||
1617193185
|
||||
);
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ interface TreeMapper
|
||||
/**
|
||||
* @template T of object
|
||||
*
|
||||
* @param class-string<T> $signature
|
||||
* @param string|class-string<T> $signature
|
||||
* @param mixed $source
|
||||
* @return T
|
||||
* @return T|mixed
|
||||
*
|
||||
* @throws MappingError
|
||||
*/
|
||||
public function map(string $signature, $source): object;
|
||||
public function map(string $signature, $source);
|
||||
}
|
||||
|
@ -4,12 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper;
|
||||
|
||||
use CuyZ\Valinor\Mapper\Exception\InvalidMappingType;
|
||||
use CuyZ\Valinor\Mapper\Exception\InvalidMappingTypeSignature;
|
||||
use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder;
|
||||
use CuyZ\Valinor\Mapper\Tree\Node;
|
||||
use CuyZ\Valinor\Mapper\Tree\Shell;
|
||||
use CuyZ\Valinor\Type\ObjectType;
|
||||
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
|
||||
use CuyZ\Valinor\Type\Parser\TypeParser;
|
||||
|
||||
@ -25,7 +23,7 @@ final class TreeMapperContainer implements TreeMapper
|
||||
$this->nodeBuilder = $nodeBuilder;
|
||||
}
|
||||
|
||||
public function map(string $signature, $source): object
|
||||
public function map(string $signature, $source)
|
||||
{
|
||||
$node = $this->node($signature, $source);
|
||||
|
||||
@ -33,7 +31,7 @@ final class TreeMapperContainer implements TreeMapper
|
||||
throw new MappingError($node);
|
||||
}
|
||||
|
||||
return $node->value(); // @phpstan-ignore-line
|
||||
return $node->value();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,10 +45,6 @@ final class TreeMapperContainer implements TreeMapper
|
||||
throw new InvalidMappingTypeSignature($signature, $exception);
|
||||
}
|
||||
|
||||
if (! $type instanceof ObjectType) {
|
||||
throw new InvalidMappingType($type);
|
||||
}
|
||||
|
||||
$shell = Shell::root($type, $source);
|
||||
|
||||
return $this->nodeBuilder->build($shell);
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Fake\Mapper;
|
||||
|
||||
use CuyZ\Valinor\Mapper\TreeMapper;
|
||||
use stdClass;
|
||||
|
||||
final class FakeTreeMapper implements TreeMapper
|
||||
{
|
||||
/** @var array<string, object> */
|
||||
private array $objects = [];
|
||||
|
||||
/**
|
||||
* @param mixed $source
|
||||
* @phpstan-return object
|
||||
*/
|
||||
public function map(string $signature, $source): object
|
||||
{
|
||||
return $this->objects[$signature] ?? new stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $signature
|
||||
*/
|
||||
public function willReturn(string $signature, object $object): void
|
||||
{
|
||||
$this->objects[$signature] = $object;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Mapper\Tree\Exception\CannotCastToScalarValue;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Mapper\Object\DateTimeObjectBuilder;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Fixture\Enum\BackedIntegerEnum;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Mapper\Tree\Exception\CannotCastToScalarValue;
|
||||
@ -10,7 +10,6 @@ use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
||||
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
|
||||
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;
|
||||
|
||||
use Throwable;
|
||||
|
||||
use function array_values;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Mapper\Tree\Exception\CannotCastToScalarValue;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Mapper\Tree\Exception\CannotCastToScalarValue;
|
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Type;
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
28
tests/Integration/Mapping/Other/ArrayOfScalarMappingTest.php
Normal file
28
tests/Integration/Mapping/Other/ArrayOfScalarMappingTest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Other;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
||||
|
||||
final class ArrayOfScalarMappingTest extends IntegrationTest
|
||||
{
|
||||
public function test_values_are_mapped_properly(): void
|
||||
{
|
||||
$source = [
|
||||
'foo',
|
||||
42,
|
||||
1337.404,
|
||||
];
|
||||
|
||||
try {
|
||||
$result = $this->mapperBuilder->mapper()->map('string[]', $source);
|
||||
} catch (MappingError $error) {
|
||||
$this->mappingFail($error);
|
||||
}
|
||||
|
||||
self::assertSame(['foo', '42', '1337.404'], $result);
|
||||
}
|
||||
}
|
49
tests/Integration/Mapping/Other/ShapedArrayMappingTest.php
Normal file
49
tests/Integration/Mapping/Other/ShapedArrayMappingTest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Integration\Mapping\Other;
|
||||
|
||||
use CuyZ\Valinor\Mapper\MappingError;
|
||||
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
||||
|
||||
final class ShapedArrayMappingTest extends IntegrationTest
|
||||
{
|
||||
public function test_values_are_mapped_properly(): void
|
||||
{
|
||||
$source = [
|
||||
'foo' => 'foo',
|
||||
'bar' => '42',
|
||||
'fiz' => '1337.404',
|
||||
];
|
||||
|
||||
try {
|
||||
$result = $this->mapperBuilder->mapper()->map('array{foo: string, bar: int, fiz: float}', $source);
|
||||
} catch (MappingError $error) {
|
||||
$this->mappingFail($error);
|
||||
}
|
||||
|
||||
self::assertSame('foo', $result['foo']);
|
||||
self::assertSame(42, $result['bar']);
|
||||
self::assertSame(1337.404, $result['fiz']);
|
||||
}
|
||||
|
||||
public function test_shared_values_are_mapped_properly(): void
|
||||
{
|
||||
$source = [
|
||||
'foo' => 'foo',
|
||||
'bar' => '42',
|
||||
'fiz' => '1337.404',
|
||||
];
|
||||
|
||||
foreach (['array{foo: string, bar: int}', 'array{bar: int, fiz:float}'] as $signature) {
|
||||
try {
|
||||
$result = $this->mapperBuilder->mapper()->map($signature, $source);
|
||||
} catch (MappingError $error) {
|
||||
$this->mappingFail($error);
|
||||
}
|
||||
|
||||
self::assertSame(42, $result['bar']);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Mapper;
|
||||
|
||||
use CuyZ\Valinor\Mapper\Exception\InvalidMappingType;
|
||||
use CuyZ\Valinor\Mapper\Exception\InvalidMappingTypeSignature;
|
||||
use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder;
|
||||
use CuyZ\Valinor\Mapper\TreeMapperContainer;
|
||||
@ -32,15 +31,6 @@ final class TreeMapperContainerTest extends TestCase
|
||||
$this->expectExceptionCode(1630959692);
|
||||
$this->expectExceptionMessageMatches('/^Could not parse the type `foo` that should be mapped: .*/');
|
||||
|
||||
$this->mapper->map('foo', []); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function test_invalid_mapping_type_throws_exception(): void
|
||||
{
|
||||
$this->expectException(InvalidMappingType::class);
|
||||
$this->expectExceptionCode(1630959731);
|
||||
$this->expectExceptionMessage("Can not map type `string`, it must be an object type.");
|
||||
|
||||
$this->mapper->map('string', []); // @phpstan-ignore-line
|
||||
$this->mapper->map('foo', []);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user