mirror of
https://github.com/danog/Valinor.git
synced 2025-01-10 22:59:04 +01:00
69ad3f4777
The cache implementation that was previously injected in the mapper builder must now be manually injected. This gives better control on when the cache should be enabled, especially depending on which environment the application is running. The library provides a cache implementation out of the box, which saves cache entries into the file system. It is also possible to use any PSR-16 compliant implementation, as long as it is capable of caching the entries handled by the library. ```php $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-dir'); (new \CuyZ\Valinor\MapperBuilder()) ->withCache($cache) ->mapper() ->map(SomeClass::class, [/* … */]); ```
211 lines
7.9 KiB
PHP
211 lines
7.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace CuyZ\Valinor\Tests\Integration\Mapping;
|
|
|
|
use CuyZ\Valinor\Mapper\MappingError;
|
|
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidInterfaceResolverReturnType;
|
|
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidTypeResolvedForInterface;
|
|
use CuyZ\Valinor\Mapper\Tree\Exception\ResolvedTypeForInterfaceIsNotAccepted;
|
|
use CuyZ\Valinor\MapperBuilder;
|
|
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
|
|
use CuyZ\Valinor\Type\Resolver\Exception\CannotResolveTypeFromInterface;
|
|
use DateTime;
|
|
use DateTimeInterface;
|
|
use DomainException;
|
|
use stdClass;
|
|
|
|
final class InterfaceInferringMappingTest extends IntegrationTest
|
|
{
|
|
public function test_override_date_time_interface_inferring_overrides_correctly(): void
|
|
{
|
|
try {
|
|
$result = (new MapperBuilder())
|
|
->infer(DateTimeInterface::class, fn () => DateTime::class)
|
|
->mapper()
|
|
->map(DateTimeInterface::class, 1645279176);
|
|
} catch (MappingError $error) {
|
|
$this->mappingFail($error);
|
|
}
|
|
|
|
self::assertInstanceOf(DateTime::class, $result);
|
|
self::assertSame('1645279176', $result->format('U'));
|
|
}
|
|
|
|
public function test_infer_interface_with_single_argument_works_properly(): void
|
|
{
|
|
try {
|
|
[$resultA, $resultB] = (new MapperBuilder())
|
|
->infer(SomeInterface::class, function (string $type): string {
|
|
// @PHP8.0 use `match` with short closure
|
|
switch ($type) {
|
|
case 'classA-foo':
|
|
return SomeClassThatInheritsInterfaceA::class;
|
|
case 'classB-bar':
|
|
return SomeClassThatInheritsInterfaceB::class;
|
|
default:
|
|
self::fail("Type `$type` not handled.");
|
|
}
|
|
})
|
|
->mapper()
|
|
->map('list<' . SomeInterface::class . '>', [
|
|
'classA-foo',
|
|
['type' => 'classB-bar', 'valueB' => 'classB-bar'],
|
|
]);
|
|
} catch (MappingError $error) {
|
|
$this->mappingFail($error);
|
|
}
|
|
|
|
self::assertInstanceOf(SomeClassThatInheritsInterfaceA::class, $resultA);
|
|
self::assertInstanceOf(SomeClassThatInheritsInterfaceB::class, $resultB);
|
|
self::assertSame('classA-foo', $resultA->valueA);
|
|
self::assertSame('classB-bar', $resultB->valueB);
|
|
}
|
|
|
|
public function test_infer_interface_with_several_arguments_works_properly(): void
|
|
{
|
|
try {
|
|
[$resultA, $resultB] = (new MapperBuilder())
|
|
->infer(SomeInterface::class, function (string $type, int $key): string {
|
|
if ($type === 'classA' && $key === 42) {
|
|
return SomeClassThatInheritsInterfaceA::class;
|
|
} elseif ($type === 'classB' && $key === 1337) {
|
|
return SomeClassThatInheritsInterfaceB::class;
|
|
}
|
|
|
|
self::fail("Combinaison `$type` / `$key` not handled.");
|
|
})
|
|
->mapper()
|
|
->map('list<' . SomeInterface::class . '>', [
|
|
[
|
|
'type' => 'classA',
|
|
'key' => 42,
|
|
'valueA' => 'foo',
|
|
],
|
|
[
|
|
'type' => 'classB',
|
|
'key' => 1337,
|
|
'valueB' => 'bar',
|
|
],
|
|
]);
|
|
} catch (MappingError $error) {
|
|
$this->mappingFail($error);
|
|
}
|
|
|
|
self::assertInstanceOf(SomeClassThatInheritsInterfaceA::class, $resultA);
|
|
self::assertInstanceOf(SomeClassThatInheritsInterfaceB::class, $resultB);
|
|
self::assertSame('foo', $resultA->valueA);
|
|
self::assertSame('bar', $resultB->valueB);
|
|
}
|
|
|
|
public function test_unresolvable_interface_implementation_throws_exception(): void
|
|
{
|
|
$this->expectException(CannotResolveTypeFromInterface::class);
|
|
$this->expectExceptionCode(1618049116);
|
|
$this->expectExceptionMessage('Impossible to resolve an implementation for the interface `' . SomeInterface::class . '`.');
|
|
|
|
(new MapperBuilder())
|
|
->mapper()
|
|
->map(SomeInterface::class, []);
|
|
}
|
|
|
|
public function test_invalid_interface_resolver_return_type_throws_exception(): void
|
|
{
|
|
$this->expectException(InvalidInterfaceResolverReturnType::class);
|
|
$this->expectExceptionCode(1630091260);
|
|
$this->expectExceptionMessage('Invalid value 42; it must be the name of a class that implements `DateTimeInterface`.');
|
|
|
|
(new MapperBuilder())
|
|
->infer(DateTimeInterface::class, fn () => 42)
|
|
->mapper()
|
|
->map(DateTimeInterface::class, []);
|
|
}
|
|
|
|
public function test_invalid_interface_resolved_return_type_throws_exception(): void
|
|
{
|
|
$this->expectException(InvalidTypeResolvedForInterface::class);
|
|
$this->expectExceptionCode(1618049224);
|
|
$this->expectExceptionMessage('Invalid type `int`; it must be the name of a class that implements `DateTimeInterface`.');
|
|
|
|
(new MapperBuilder())
|
|
->infer(DateTimeInterface::class, fn () => 'int')
|
|
->mapper()
|
|
->map(DateTimeInterface::class, []);
|
|
}
|
|
|
|
public function test_invalid_resolved_interface_implementation_throws_exception(): void
|
|
{
|
|
$this->expectException(ResolvedTypeForInterfaceIsNotAccepted::class);
|
|
$this->expectExceptionCode(1618049487);
|
|
$this->expectExceptionMessage('The implementation `stdClass` is not accepted by the interface `DateTimeInterface`.');
|
|
|
|
(new MapperBuilder())
|
|
->infer(DateTimeInterface::class, fn () => stdClass::class)
|
|
->mapper()
|
|
->map(DateTimeInterface::class, []);
|
|
}
|
|
|
|
public function test_invalid_source_throws_exception(): void
|
|
{
|
|
try {
|
|
(new MapperBuilder())
|
|
->infer(SomeInterface::class, fn (string $type, int $key): string => SomeClassThatInheritsInterfaceA::class)
|
|
->mapper()
|
|
->map(SomeInterface::class, 42);
|
|
} catch (MappingError $exception) {
|
|
$error = $exception->node()->messages()[0];
|
|
|
|
self::assertSame('1645283485', $error->code());
|
|
self::assertSame('Invalid value 42: it must be an array.', (string)$error);
|
|
}
|
|
}
|
|
|
|
public function test_invalid_source_value_throws_exception(): void
|
|
{
|
|
try {
|
|
(new MapperBuilder())
|
|
->infer(SomeInterface::class, fn (int $key): string => SomeClassThatInheritsInterfaceA::class)
|
|
->mapper()
|
|
->map(SomeInterface::class, 'foo');
|
|
} catch (MappingError $exception) {
|
|
$error = $exception->node()->children()['key']->messages()[0];
|
|
|
|
self::assertSame('1618736242', $error->code());
|
|
self::assertSame("Cannot cast 'foo' to `int`.", (string)$error);
|
|
}
|
|
}
|
|
|
|
public function test_exception_thrown_is_caught_and_throws_message_exception(): void
|
|
{
|
|
try {
|
|
(new MapperBuilder())
|
|
->infer(DateTimeInterface::class, function () {
|
|
// @PHP8.0 use short closure
|
|
throw new DomainException('some error message', 1645303304);
|
|
})
|
|
->mapper()
|
|
->map(DateTimeInterface::class, 'foo');
|
|
} catch (MappingError $exception) {
|
|
$error = $exception->node()->messages()[0];
|
|
|
|
self::assertSame('1645303304', $error->code());
|
|
self::assertSame('some error message', (string)$error);
|
|
}
|
|
}
|
|
}
|
|
|
|
interface SomeInterface
|
|
{
|
|
}
|
|
|
|
final class SomeClassThatInheritsInterfaceA implements SomeInterface
|
|
{
|
|
public string $valueA;
|
|
}
|
|
|
|
final class SomeClassThatInheritsInterfaceB implements SomeInterface
|
|
{
|
|
public string $valueB;
|
|
}
|