feat: allow injecting a cache implementation that is used by the mapper

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, [/* … */]);
```
This commit is contained in:
Romain Canon 2022-05-22 20:43:01 +02:00
parent 1b242d4ce7
commit 69ad3f4777
45 changed files with 546 additions and 169 deletions

View File

@ -888,6 +888,28 @@ final class SomeClass
}
```
## Performance & caching
This library needs to parse a lot of information in order to handle all provided
features. Therefore, it is strongly advised to activate the cache to reduce
heavy workload between runtimes, especially when the application runs in a
production environment.
The library provides a cache implementation out of the box, which saves
cache entries into the file system.
> **Note** 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-directory');
(new \CuyZ\Valinor\MapperBuilder())
->withCache($cache)
->mapper()
->map(SomeClass::class, [/* … */]);
```
## Static analysis
To help static analysis of a codebase using this library, an extension for

View File

@ -25,6 +25,9 @@ parameters:
- message: '#Template type T of method CuyZ\\Valinor\\Mapper\\TreeMapper::map\(\) is not referenced in a parameter#'
path: src/Mapper/TreeMapper.php
- message: '#Method CuyZ\\Valinor\\MapperBuilder::withCache\(\) has parameter \$cache with generic interface Psr\\SimpleCache\\\CacheInterface but does not specify its types: EntryType#'
path: src/MapperBuilder.php
stubFiles:
- qa/PHPStan/Stubs/Psr/SimpleCache/CacheInterface.stub
tmpDir: var/cache/phpstan

View File

@ -11,8 +11,8 @@ interface CacheInterface
{
/**
* @param string $key
* @param EntryType $default
* @return EntryType
* @param EntryType|null $default
* @return EntryType|null
*/
public function get(string $key, $default = null);
@ -26,7 +26,7 @@ interface CacheInterface
/**
* @param iterable<string> $keys
* @param EntryType $default
* @return iterable<string, EntryType>
* @return iterable<string, EntryType|null>
*/
public function getMultiple(iterable $keys, $default = null): iterable;

View File

@ -83,7 +83,7 @@ final class ChainCache implements CacheInterface
}
/**
* @return Traversable<string, EntryType>
* @return Traversable<string, EntryType|null>
*/
public function getMultiple($keys, $default = null): Traversable
{

View File

@ -128,7 +128,7 @@ final class CompiledPhpFileCache implements CacheInterface
}
/**
* @return Traversable<string, EntryType>
* @return Traversable<string, EntryType|null>
*/
public function getMultiple($keys, $default = null): Traversable
{

View File

@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Cache;
use CuyZ\Valinor\Cache\Compiled\CompiledPhpFileCache;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
use Psr\SimpleCache\CacheInterface;
use Traversable;
use function current;
use function get_class;
use function next;
use function sys_get_temp_dir;
/**
* @api
*
* @implements CacheInterface<ClassDefinition|FunctionDefinition>
*/
final class FileSystemCache implements CacheInterface
{
/** @var array<string, CacheInterface<ClassDefinition|FunctionDefinition>> */
private array $delegates;
public function __construct(string $cacheDir = null)
{
$cacheDir ??= sys_get_temp_dir();
// @infection-ignore-all
$this->delegates = [
ClassDefinition::class => new CompiledPhpFileCache($cacheDir . DIRECTORY_SEPARATOR . 'classes', new ClassDefinitionCompiler()),
FunctionDefinition::class => new CompiledPhpFileCache($cacheDir . DIRECTORY_SEPARATOR . 'functions', new FunctionDefinitionCompiler()),
];
}
public function has($key): bool
{
foreach ($this->delegates as $delegate) {
if ($delegate->has($key)) {
return true;
}
}
return false;
}
public function get($key, $default = null)
{
while ($delegate = current($this->delegates)) {
if ($delegate->has($key)) {
return $delegate->get($key, $default);
}
// @infection-ignore-all
next($this->delegates);
}
return $default;
}
public function set($key, $value, $ttl = null): bool
{
return $this->delegates[get_class($value)]->set($key, $value, $ttl);
}
public function delete($key): bool
{
$deleted = true;
foreach ($this->delegates as $delegate) {
$deleted = $delegate->delete($key) && $deleted;
}
return $deleted;
}
public function clear(): bool
{
$cleared = true;
foreach ($this->delegates as $delegate) {
$cleared = $delegate->clear() && $cleared;
}
return $cleared;
}
/**
* @return Traversable<string, ClassDefinition|FunctionDefinition|null>
*/
public function getMultiple($keys, $default = null): Traversable
{
foreach ($keys as $key) {
yield $key => $this->get($key, $default);
}
}
public function setMultiple($values, $ttl = null): bool
{
$set = true;
foreach ($values as $key => $value) {
$set = $this->set($key, $value, $ttl) && $set;
}
return $set;
}
public function deleteMultiple($keys): bool
{
$deleted = true;
foreach ($keys as $key) {
$deleted = $this->delete($key) && $deleted;
}
return $deleted;
}
}

View File

@ -57,7 +57,7 @@ final class VersionedCache implements CacheInterface
}
/**
* @return Traversable<string, EntryType>
* @return Traversable<string, EntryType|null>
*/
public function getMultiple($keys, $default = null): Traversable
{

View File

@ -31,7 +31,11 @@ final class CacheClassDefinitionRepository implements ClassDefinitionRepository
$key = "class-definition-$type";
if ($this->cache->has($key)) {
return $this->cache->get($key);
$entry = $this->cache->get($key);
if ($entry) {
return $entry;
}
}
$class = $this->delegate->for($type);

View File

@ -32,7 +32,11 @@ final class CacheFunctionDefinitionRepository implements FunctionDefinitionRepos
$key = "function-definition-{$reflection->getFileName()}-{$reflection->getStartLine()}-{$reflection->getEndLine()}";
if ($this->cache->has($key)) {
return $this->cache->get($key);
$entry = $this->cache->get($key);
if ($entry) {
return $entry;
}
}
$definition = $this->delegate->for($function);

View File

@ -5,17 +5,12 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Library;
use CuyZ\Valinor\Cache\ChainCache;
use CuyZ\Valinor\Cache\Compiled\CompiledPhpFileCache;
use CuyZ\Valinor\Cache\RuntimeCache;
use CuyZ\Valinor\Cache\VersionedCache;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\FunctionsContainer;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Cache\CacheFunctionDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\CombinedAttributesRepository;
@ -162,31 +157,21 @@ final class Container
ObjectBuilderFilterer::class => fn () => new ObjectBuilderFilterer(),
ClassDefinitionRepository::class => function () use ($settings) {
$repository = new ReflectionClassDefinitionRepository(
ClassDefinitionRepository::class => fn () => new CacheClassDefinitionRepository(
new ReflectionClassDefinitionRepository(
$this->get(TypeParserFactory::class),
$this->get(AttributesRepository::class),
);
),
$this->get(CacheInterface::class)
),
/** @var CacheInterface<ClassDefinition> $cache */
$cache = new CompiledPhpFileCache($settings->cacheDir, new ClassDefinitionCompiler());
$cache = $this->wrapCache($cache);
return new CacheClassDefinitionRepository($repository, $cache);
},
FunctionDefinitionRepository::class => function () use ($settings) {
$repository = new ReflectionFunctionDefinitionRepository(
FunctionDefinitionRepository::class => fn () => new CacheFunctionDefinitionRepository(
new ReflectionFunctionDefinitionRepository(
$this->get(TypeParserFactory::class),
$this->get(AttributesRepository::class),
);
/** @var CacheInterface<FunctionDefinition> $cache */
$cache = new CompiledPhpFileCache($settings->cacheDir, new FunctionDefinitionCompiler());
$cache = $this->wrapCache($cache);
return new CacheFunctionDefinitionRepository($repository, $cache);
},
),
$this->get(CacheInterface::class)
),
AttributesRepository::class => function () use ($settings) {
if (! $settings->enableLegacyDoctrineAnnotations) {
@ -215,6 +200,16 @@ final class Container
},
TemplateParser::class => fn () => new BasicTemplateParser(),
CacheInterface::class => function () use ($settings) {
$cache = new RuntimeCache();
if (isset($settings->cache)) {
$cache = new ChainCache($cache, $settings->cache);
}
return new VersionedCache($cache);
},
];
}
@ -232,20 +227,4 @@ final class Container
{
return $this->services[$name] ??= call_user_func($this->factories[$name]); // @phpstan-ignore-line
}
/**
* @template EntryType
*
* @param CacheInterface<EntryType> $cache
* @return CacheInterface<EntryType>
*/
private function wrapCache(CacheInterface $cache): CacheInterface
{
/** @var RuntimeCache<EntryType> $runtimeCache */
$runtimeCache = new RuntimeCache();
return new VersionedCache(
new ChainCache($runtimeCache, $cache)
);
}
}

View File

@ -7,8 +7,7 @@ namespace CuyZ\Valinor\Library;
use CuyZ\Valinor\Mapper\Tree\Node;
use DateTimeImmutable;
use DateTimeInterface;
use function sys_get_temp_dir;
use Psr\SimpleCache\CacheInterface;
/** @internal */
final class Settings
@ -28,13 +27,13 @@ final class Settings
/** @var array<callable(Node): void> */
public array $nodeVisitors = [];
public string $cacheDir;
/** @var CacheInterface<mixed> */
public CacheInterface $cache;
public bool $enableLegacyDoctrineAnnotations = PHP_VERSION_ID < 8_00_00;
public function __construct()
{
$this->cacheDir = sys_get_temp_dir();
$this->interfaceMapping[DateTimeInterface::class] = static fn () => DateTimeImmutable::class;
}
}

View File

@ -4,10 +4,12 @@ declare(strict_types=1);
namespace CuyZ\Valinor;
use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\Library\Container;
use CuyZ\Valinor\Library\Settings;
use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\Mapper\TreeMapper;
use Psr\SimpleCache\CacheInterface;
use function is_callable;
@ -169,6 +171,36 @@ final class MapperBuilder
return $clone;
}
/**
* Inject a cache implementation that will be in charge of caching heavy
* data used by the mapper.
*
* An implementation is provided by the library, which writes cache entries
* in the file system; it is strongly recommended to use it when the
* application runs in production environment.
*
* 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, [
* // …
* ]);
* ```
*/
public function withCache(CacheInterface $cache): self
{
$clone = clone $this;
$clone->settings->cache = $cache;
return $clone;
}
/**
* @template T
* @param callable(T): T $callback
@ -191,12 +223,21 @@ final class MapperBuilder
return $this;
}
/**
* @deprecated instead, use:
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->withCache(new FileSystemCache('cache-directory'))
* ->mapper()
* ->map(SomeClass::class, [
* // …
* ]);
* ```
*/
public function withCacheDir(string $cacheDir): self
{
$clone = clone $this;
$clone->settings->cacheDir = $cacheDir;
return $clone;
return $this->withCache(new FileSystemCache($cacheDir));
}
/**

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Cache;
use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use org\bovigo\vfs\vfsStream;
use function strtoupper;
final class CacheInjectionTest extends IntegrationTest
{
public function test_cache_entries_are_written_during_mapping(): void
{
$files = vfsStream::setup('cache-dir');
$cache = new FileSystemCache($files->url());
self::assertFalse($files->hasChildren());
$object = (new MapperBuilder())
->withCache($cache)
// The cache should be able to cache function definitions…
->alter(fn (string $value): string => strtoupper($value))
->mapper()
// …as well as class definitions.
->map(SimpleObject::class, 'foo');
self::assertSame('FOO', $object->value);
self::assertTrue($files->hasChildren());
}
}

View File

@ -6,8 +6,6 @@ namespace CuyZ\Valinor\Tests\Integration;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\MapperBuilder;
use org\bovigo\vfs\vfsStream;
use PHPUnit\Framework\TestCase;
use function implode;
@ -15,17 +13,6 @@ use function iterator_to_array;
abstract class IntegrationTest extends TestCase
{
protected MapperBuilder $mapperBuilder;
protected function setUp(): void
{
parent::setUp();
vfsStream::setup('cache-dir');
$this->mapperBuilder = (new MapperBuilder())->withCacheDir(vfsStream::url('cache-dir'));
}
/**
* @return never-return
*/

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Attribute;
use CuyZ\Valinor\Attribute\Identifier;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use function get_class;
@ -39,7 +40,7 @@ final class IdentifierAttributeMappingTest extends IntegrationTest
];
try {
$result = $this->mapperBuilder->mapper()->map(get_class($class), $source);
$result = (new MapperBuilder())->mapper()->map(get_class($class), $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -65,7 +66,7 @@ final class IdentifierAttributeMappingTest extends IntegrationTest
'baz' => [],
]];
$result = $this->mapperBuilder->enableLegacyDoctrineAnnotations()->mapper()->map(get_class($class), $source);
$result = (new MapperBuilder())->enableLegacyDoctrineAnnotations()->mapper()->map(get_class($class), $source);
self::assertSame('foo', $result->identifierDoctrineAnnotation['foo']->identifier);
self::assertSame('bar', $result->identifierDoctrineAnnotation['bar']->identifier);

View File

@ -11,6 +11,7 @@ use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Object\Exception\TooManyObjectBuilderFactoryAttributes;
use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Fake\Mapper\Object\FakeObjectBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use RuntimeException;
@ -20,7 +21,7 @@ final class ObjectBuilderStrategyMappingTest extends IntegrationTest
public function test_object_builder_attribute_is_used(): void
{
try {
$result = $this->mapperBuilder->mapper()->map(ObjectWithBuilderStrategyAttribute::class, [
$result = (new MapperBuilder())->mapper()->map(ObjectWithBuilderStrategyAttribute::class, [
'foo' => 'foo',
'bar' => 'bar',
]);
@ -36,7 +37,7 @@ final class ObjectBuilderStrategyMappingTest extends IntegrationTest
public function test_named_constructor_throwing_exception_is_caught_by_mapper(): void
{
try {
$this->mapperBuilder->mapper()->map(ObjectWithFailingBuilderStrategyAttribute::class, []);
(new MapperBuilder())->mapper()->map(ObjectWithFailingBuilderStrategyAttribute::class, []);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];
@ -53,7 +54,7 @@ final class ObjectBuilderStrategyMappingTest extends IntegrationTest
$this->expectExceptionCode(1634044714);
$this->expectExceptionMessage("Only one attribute of type `$factoryClass` is allowed, class `$objectClass` contains 2.");
$this->mapperBuilder->mapper()->map($objectClass, 'foo');
(new MapperBuilder())->mapper()->map($objectClass, 'foo');
}
}

View File

@ -8,6 +8,7 @@ use Attribute;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Tree\Shell;
use CuyZ\Valinor\Mapper\Tree\Visitor\ShellVisitor;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
@ -16,7 +17,7 @@ final class ShellVisitorMappingTest extends IntegrationTest
public function test_shell_visitor_attributes_are_called_during_mapping(): void
{
try {
$result = $this->mapperBuilder->enableLegacyDoctrineAnnotations()->mapper()->map(
$result = (new MapperBuilder())->enableLegacyDoctrineAnnotations()->mapper()->map(
ObjectWithShellVisitorAttributes::class,
[
'valueA' => 'foo',

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Object\Exception\CannotInstantiateObject;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use DateTime;
use DateTimeImmutable;
@ -22,7 +23,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
$object = new stdClass();
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor(fn (): stdClass => $object)
->mapper()
->map(stdClass::class, []);
@ -41,7 +42,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
};
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor(/** @return stdClass */ fn () => $object)
->mapper()
->map(get_class($class), []);
@ -55,7 +56,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_named_constructor_is_used(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor([SomeClassWithNamedConstructors::class, 'namedConstructor'])
->mapper()
@ -80,7 +81,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
};
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor($constructor)
->mapper()
->map(stdClass::class, []);
@ -104,7 +105,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
};
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor([$constructor, 'build'])
->mapper()
@ -119,7 +120,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_native_constructor_is_not_called_if_not_registered_but_other_constructors_are_registered(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor([SomeClassWithSimilarNativeConstructorAndNamedConstructor::class, 'namedConstructor'])
->mapper()
@ -134,7 +135,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_native_constructor_is_called_if_registered_and_other_constructors_are_registered(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor(SomeClassWithDifferentNativeConstructorAndNamedConstructor::class)
->registerConstructor([SomeClassWithDifferentNativeConstructorAndNamedConstructor::class, 'namedConstructor'])
@ -156,7 +157,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
$object = new stdClass();
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor(fn (): DateTime => new DateTime())
// This constructor is surrounded by other ones to ensure it is
// still used correctly.
@ -174,7 +175,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_constructor_with_one_argument_is_used(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor(function (int $int): stdClass {
$class = new stdClass();
$class->int = $int;
@ -193,7 +194,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_constructor_with_several_arguments_is_used(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor(function (string $string, int $int, float $float = 1337.404): stdClass {
$class = new stdClass();
$class->string = $string;
@ -218,7 +219,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_constructors_for_same_class_are_filtered_correctly(): void
{
$mapper = $this->mapperBuilder
$mapper = (new MapperBuilder())
// Basic constructor
->registerConstructor(function (string $foo): stdClass {
$class = new stdClass();
@ -279,7 +280,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_identical_registered_constructors_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->registerConstructor(
fn (): stdClass => new stdClass(),
fn (): stdClass => new stdClass(),
@ -297,7 +298,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_source_not_matching_registered_constructors_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->registerConstructor(
fn (int $bar, float $baz = 1337.404): stdClass => new stdClass(),
fn (string $foo): stdClass => new stdClass(),
@ -318,7 +319,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
$this->expectExceptionCode(1646916477);
$this->expectExceptionMessage('No available constructor found for class `' . SomeClassWithPrivateNativeConstructor::class . '`');
$this->mapperBuilder
(new MapperBuilder())
->mapper()
->map(SomeClassWithPrivateNativeConstructor::class, []);
}
@ -328,7 +329,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
$default = new DateTime('@1356097062');
$defaultImmutable = new DateTimeImmutable('@1356097062');
$mapper = $this->mapperBuilder
$mapper = (new MapperBuilder())
->registerConstructor(fn (int $timestamp): DateTime => $default)
->registerConstructor(fn (int $timestamp): DateTimeImmutable => $defaultImmutable)
->mapper();
@ -347,7 +348,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_datetime_constructor_not_matching_source_uses_default_constructor(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->registerConstructor(fn (string $foo, int $bar): DateTimeInterface => new DateTimeImmutable())
->mapper()
->map(DateTimeInterface::class, 1647781015);
@ -361,7 +362,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
public function test_registered_constructor_throwing_exception_fails_mapping_with_message(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->registerConstructor(function (): stdClass {
// @PHP8.0 use short closure
throw new DomainException('some domain exception');
@ -380,7 +381,7 @@ final class ConstructorRegistrationMappingTest extends IntegrationTest
$object = new stdClass();
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->bind(fn (): stdClass => $object)
->mapper()
->map(stdClass::class, []);

View File

@ -8,6 +8,7 @@ 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;
@ -20,7 +21,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
public function test_override_date_time_interface_inferring_overrides_correctly(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->infer(DateTimeInterface::class, fn () => DateTime::class)
->mapper()
->map(DateTimeInterface::class, 1645279176);
@ -35,7 +36,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
public function test_infer_interface_with_single_argument_works_properly(): void
{
try {
[$resultA, $resultB] = $this->mapperBuilder
[$resultA, $resultB] = (new MapperBuilder())
->infer(SomeInterface::class, function (string $type): string {
// @PHP8.0 use `match` with short closure
switch ($type) {
@ -65,7 +66,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
public function test_infer_interface_with_several_arguments_works_properly(): void
{
try {
[$resultA, $resultB] = $this->mapperBuilder
[$resultA, $resultB] = (new MapperBuilder())
->infer(SomeInterface::class, function (string $type, int $key): string {
if ($type === 'classA' && $key === 42) {
return SomeClassThatInheritsInterfaceA::class;
@ -104,7 +105,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
$this->expectExceptionCode(1618049116);
$this->expectExceptionMessage('Impossible to resolve an implementation for the interface `' . SomeInterface::class . '`.');
$this->mapperBuilder
(new MapperBuilder())
->mapper()
->map(SomeInterface::class, []);
}
@ -115,7 +116,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
$this->expectExceptionCode(1630091260);
$this->expectExceptionMessage('Invalid value 42; it must be the name of a class that implements `DateTimeInterface`.');
$this->mapperBuilder
(new MapperBuilder())
->infer(DateTimeInterface::class, fn () => 42)
->mapper()
->map(DateTimeInterface::class, []);
@ -127,7 +128,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
$this->expectExceptionCode(1618049224);
$this->expectExceptionMessage('Invalid type `int`; it must be the name of a class that implements `DateTimeInterface`.');
$this->mapperBuilder
(new MapperBuilder())
->infer(DateTimeInterface::class, fn () => 'int')
->mapper()
->map(DateTimeInterface::class, []);
@ -139,7 +140,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
$this->expectExceptionCode(1618049487);
$this->expectExceptionMessage('The implementation `stdClass` is not accepted by the interface `DateTimeInterface`.');
$this->mapperBuilder
(new MapperBuilder())
->infer(DateTimeInterface::class, fn () => stdClass::class)
->mapper()
->map(DateTimeInterface::class, []);
@ -148,7 +149,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
public function test_invalid_source_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->infer(SomeInterface::class, fn (string $type, int $key): string => SomeClassThatInheritsInterfaceA::class)
->mapper()
->map(SomeInterface::class, 42);
@ -163,7 +164,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
public function test_invalid_source_value_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->infer(SomeInterface::class, fn (int $key): string => SomeClassThatInheritsInterfaceA::class)
->mapper()
->map(SomeInterface::class, 'foo');
@ -178,7 +179,7 @@ final class InterfaceInferringMappingTest extends IntegrationTest
public function test_exception_thrown_is_caught_and_throws_message_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->infer(DateTimeInterface::class, function () {
// @PHP8.0 use short closure
throw new DomainException('some error message', 1645303304);

View File

@ -9,6 +9,7 @@ use CuyZ\Valinor\Mapper\Tree\Message\Formatter\AggregateMessageFormatter;
use CuyZ\Valinor\Mapper\Tree\Message\Formatter\LocaleMessageFormatter;
use CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageMapFormatter;
use CuyZ\Valinor\Mapper\Tree\Message\Formatter\TranslationMessageFormatter;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class MessageFormatterTest extends IntegrationTest
@ -16,7 +17,7 @@ final class MessageFormatterTest extends IntegrationTest
public function test_message_is_formatted_correctly(): void
{
try {
$this->mapperBuilder->mapper()->map('int', 'foo');
(new MapperBuilder())->mapper()->map('int', 'foo');
} catch (MappingError $error) {
$formatter = new AggregateMessageFormatter(
new LocaleMessageFormatter('fr'),

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;
@ -40,7 +41,7 @@ final class ArrayValuesMappingTest extends IntegrationTest
foreach ([ArrayValues::class, ArrayValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -69,7 +70,7 @@ final class ArrayValuesMappingTest extends IntegrationTest
public function test_empty_array_in_non_empty_array_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(ArrayValues::class, [
(new MapperBuilder())->mapper()->map(ArrayValues::class, [
'nonEmptyArraysOfStrings' => [],
]);
} catch (MappingError $exception) {
@ -83,7 +84,7 @@ final class ArrayValuesMappingTest extends IntegrationTest
public function test_value_that_cannot_be_casted_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(ArrayValues::class, [
(new MapperBuilder())->mapper()->map(ArrayValues::class, [
'integers' => ['foo'],
]);
} catch (MappingError $exception) {

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Object\DateTimeObjectBuilder;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use DateTime;
use DateTimeImmutable;
@ -36,7 +37,7 @@ final class DateTimeMappingTest extends IntegrationTest
$pgsqlDate = (new DateTime('@' . $this->buildRandomTimestamp()))->format('Y-m-d H:i:s.u');
try {
$result = $this->mapperBuilder->mapper()->map(AllDateTimeValues::class, [
$result = (new MapperBuilder())->mapper()->map(AllDateTimeValues::class, [
'dateTimeInterface' => $dateTimeInterface,
'dateTimeImmutable' => $dateTimeImmutable,
'dateTimeFromTimestamp' => $dateTimeFromTimestamp,
@ -67,7 +68,7 @@ final class DateTimeMappingTest extends IntegrationTest
public function test_invalid_datetime_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->mapper()
->map(SimpleDateTimeValues::class, [
'dateTime' => 'invalid datetime',
@ -83,7 +84,7 @@ final class DateTimeMappingTest extends IntegrationTest
public function test_invalid_datetime_from_array_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->mapper()
->map(SimpleDateTimeValues::class, [
'dateTime' => [
@ -102,7 +103,7 @@ final class DateTimeMappingTest extends IntegrationTest
public function test_invalid_array_source_throws_exception(): void
{
try {
$this->mapperBuilder
(new MapperBuilder())
->mapper()
->map(SimpleDateTimeValues::class, [
'dateTime' => [

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Fixture\Enum\BackedIntegerEnum;
use CuyZ\Valinor\Tests\Fixture\Enum\BackedStringEnum;
use CuyZ\Valinor\Tests\Fixture\Enum\PureEnum;
@ -29,7 +30,7 @@ final class EnumValuesMappingTest extends IntegrationTest
foreach ([EnumValues::class, EnumValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;
@ -42,7 +43,7 @@ final class GenericValuesMappingTest extends IntegrationTest
foreach ([GenericValues::class, GenericValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;
@ -35,7 +36,7 @@ final class IterableValuesMappingTest extends IntegrationTest
foreach ([IterableValues::class, IterableValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;
@ -45,7 +46,7 @@ final class ListValuesMappingTest extends IntegrationTest
foreach ([ListValues::class, ListValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -69,7 +70,7 @@ final class ListValuesMappingTest extends IntegrationTest
public function test_empty_list_in_non_empty_list_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(ListValues::class, [
(new MapperBuilder())->mapper()->map(ListValues::class, [
'nonEmptyListOfStrings' => [],
]);
} catch (MappingError $exception) {
@ -83,7 +84,7 @@ final class ListValuesMappingTest extends IntegrationTest
public function test_value_that_cannot_be_casted_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(ListValues::class, [
(new MapperBuilder())->mapper()->map(ListValues::class, [
'integers' => ['foo'],
]);
} catch (MappingError $exception) {

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class LocalTypeAliasMappingTest extends IntegrationTest
@ -23,7 +24,7 @@ final class LocalTypeAliasMappingTest extends IntegrationTest
foreach ([PhpStanLocalAliases::class, PsalmLocalAliases::class] as $class) {
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->mapper()
->map($class, $source);
@ -41,7 +42,7 @@ final class LocalTypeAliasMappingTest extends IntegrationTest
{
foreach ([PhpStanAliasImport::class, PsalmAliasImport::class] as $class) {
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->mapper()
->map($class, [
'importedType' => 42,

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
@ -21,7 +22,7 @@ final class ObjectValuesMappingTest extends IntegrationTest
foreach ([ObjectValues::class, ObjectValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -36,7 +37,7 @@ final class ObjectValuesMappingTest extends IntegrationTest
foreach ([ObjectValues::class, ObjectValuesWithConstructor::class] as $class) {
try {
$this->mapperBuilder->mapper()->map($class, $source);
(new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use DateTime;
@ -41,7 +42,7 @@ final class ScalarValuesMappingTest extends IntegrationTest
foreach ([ScalarValues::class, ScalarValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -71,7 +72,7 @@ final class ScalarValuesMappingTest extends IntegrationTest
public function test_value_that_cannot_be_casted_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(SimpleObject::class, [
(new MapperBuilder())->mapper()->map(SimpleObject::class, [
'value' => new stdClass(),
]);
} catch (MappingError $exception) {
@ -85,7 +86,7 @@ final class ScalarValuesMappingTest extends IntegrationTest
public function test_empty_mandatory_value_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(SimpleObject::class, [
(new MapperBuilder())->mapper()->map(SimpleObject::class, [
'value' => null,
]);
} catch (MappingError $exception) {

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use stdClass;
@ -45,7 +46,7 @@ final class ShapedArrayValuesMappingTest extends IntegrationTest
foreach ([ShapedArrayValues::class, ShapedArrayValuesWithConstructor::class] as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -65,7 +66,7 @@ final class ShapedArrayValuesMappingTest extends IntegrationTest
public function test_value_that_cannot_be_casted_throws_exception(): void
{
try {
$this->mapperBuilder->mapper()->map(ShapedArrayValues::class, [
(new MapperBuilder())->mapper()->map(ShapedArrayValues::class, [
'basicShapedArrayWithStringKeys' => [
'foo' => new stdClass(),
'bar' => 42,

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\NativeUnionOfObjects;
@ -16,10 +17,10 @@ final class UnionOfObjectsMappingTest extends IntegrationTest
public function test_object_type_is_narrowed_correctly_for_simple_case(): void
{
try {
$resultFoo = $this->mapperBuilder->mapper()->map(NativeUnionOfObjects::class, [
$resultFoo = (new MapperBuilder())->mapper()->map(NativeUnionOfObjects::class, [
'foo' => 'foo',
]);
$resultBar = $this->mapperBuilder->mapper()->map(NativeUnionOfObjects::class, [
$resultBar = (new MapperBuilder())->mapper()->map(NativeUnionOfObjects::class, [
'bar' => 'bar',
]);
} catch (MappingError $error) {
@ -33,7 +34,7 @@ final class UnionOfObjectsMappingTest extends IntegrationTest
public function test_object_type_is_narrowed_correctly_for_simple_array_case(): void
{
try {
$result = $this->mapperBuilder->mapper()->map(UnionOfFooAndBar::class, [
$result = (new MapperBuilder())->mapper()->map(UnionOfFooAndBar::class, [
'foo' => ['foo' => 'foo'],
'bar' => ['bar' => 'bar'],
]);
@ -48,7 +49,7 @@ final class UnionOfObjectsMappingTest extends IntegrationTest
public function test_source_matching_two_unions_maps_the_one_with_most_arguments(): void
{
try {
$result = $this->mapperBuilder->mapper()->map(UnionOfBarAndFizAndFoo::class, [
$result = (new MapperBuilder())->mapper()->map(UnionOfBarAndFizAndFoo::class, [
['foo' => 'foo', 'bar' => 'bar', 'fiz' => 'fiz'],
]);
} catch (MappingError $error) {
@ -65,7 +66,7 @@ final class UnionOfObjectsMappingTest extends IntegrationTest
public function test_objects_sharing_one_property_are_resolved_correctly(): void
{
try {
$result = $this->mapperBuilder->mapper()->map(UnionOfFooAndBarAndFoo::class, [
$result = (new MapperBuilder())->mapper()->map(UnionOfFooAndBarAndFoo::class, [
['foo' => 'foo'],
['foo' => 'foo', 'bar' => 'bar'],
]);
@ -80,7 +81,7 @@ final class UnionOfObjectsMappingTest extends IntegrationTest
public function test_one_failing_union_type_does_not_stop_union_inferring(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor(
[SomeClassWithTwoIdenticalNamedConstructors::class, 'constructorA'],
@ -107,7 +108,7 @@ final class UnionOfObjectsMappingTest extends IntegrationTest
public function test_mapping_error_when_cannot_resolve_union(string $className, array $source): void
{
try {
$this->mapperBuilder->mapper()->map($className, $source);
(new MapperBuilder())->mapper()->map($className, $source);
self::fail('No mapping error when one was expected');
} catch (MappingError $exception) {

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\NativeUnionValues;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\NativeUnionValuesWithConstructor;
@ -39,7 +40,7 @@ final class UnionValuesMappingTest extends IntegrationTest
foreach ($classes as $class) {
try {
$result = $this->mapperBuilder->mapper()->map($class, $source);
$result = (new MapperBuilder())->mapper()->map($class, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Other;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class ArrayOfScalarMappingTest extends IntegrationTest
@ -18,7 +19,7 @@ final class ArrayOfScalarMappingTest extends IntegrationTest
];
try {
$result = $this->mapperBuilder->mapper()->map('string[]', $source);
$result = (new MapperBuilder())->mapper()->map('string[]', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping\Other;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class ShapedArrayMappingTest extends IntegrationTest
@ -18,7 +19,7 @@ final class ShapedArrayMappingTest extends IntegrationTest
];
try {
$result = $this->mapperBuilder->mapper()->map('array{foo: string, bar: int, fiz: float}', $source);
$result = (new MapperBuilder())->mapper()->map('array{foo: string, bar: int, fiz: float}', $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}
@ -38,7 +39,7 @@ final class ShapedArrayMappingTest extends IntegrationTest
foreach (['array{foo: string, bar: int}', 'array{bar: int, fiz:float}'] as $signature) {
try {
$result = $this->mapperBuilder->mapper()->map($signature, $source);
$result = (new MapperBuilder())->mapper()->map($signature, $source);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\ReadonlyValues;
@ -16,7 +17,7 @@ final class ReadonlyMappingTest extends IntegrationTest
public function test_single_property_and_constructor_parameter_are_mapped_properly(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(ReadonlyValues::class, [
$object = (new MapperBuilder())->mapper()->map(ReadonlyValues::class, [
'value' => 'foo',
]);
} catch (MappingError $error) {

View File

@ -5,13 +5,14 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class SingleNodeMappingTest extends IntegrationTest
{
public function test_single_property_and_constructor_parameter_are_mapped_properly(): void
{
$mapper = $this->mapperBuilder->mapper();
$mapper = (new MapperBuilder())->mapper();
// Note that the key `value` is missing from the source
$scalarSource = 'foo';

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Source;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Source\JsonSource;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
@ -14,7 +15,7 @@ final class JsonSourceMappingTest extends IntegrationTest
public function test_json_source_is_mapped_correctly(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SimpleObject::class,
new JsonSource('{"value": "foo"}')
);

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Source\Modifier;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Source\Modifier\CamelCaseKeys;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class CamelCaseKeysMappingTest extends IntegrationTest
@ -13,7 +14,7 @@ final class CamelCaseKeysMappingTest extends IntegrationTest
public function test_underscore_key_is_modified_to_camel_case(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithCamelCaseProperty::class,
new CamelCaseKeys(['some_value' => 'foo'])
);
@ -27,7 +28,7 @@ final class CamelCaseKeysMappingTest extends IntegrationTest
public function test_dash_key_is_modified_to_camel_case(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithCamelCaseProperty::class,
new CamelCaseKeys(['some-value' => 'foo'])
);
@ -41,7 +42,7 @@ final class CamelCaseKeysMappingTest extends IntegrationTest
public function test_spaced_key_is_modified_to_camel_case(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithCamelCaseProperty::class,
new CamelCaseKeys(['some value' => 'foo'])
);
@ -55,7 +56,7 @@ final class CamelCaseKeysMappingTest extends IntegrationTest
public function test_nested_camel_case_keys_are_modified(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithNestedProperty::class,
new CamelCaseKeys([
'some_nested_value' => ['some_value' => 'foo'],
@ -71,7 +72,7 @@ final class CamelCaseKeysMappingTest extends IntegrationTest
public function test_existing_camel_case_key_is_not_overridden(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithCamelCaseProperty::class,
new CamelCaseKeys([
'someValue' => 'bar',
@ -88,7 +89,7 @@ final class CamelCaseKeysMappingTest extends IntegrationTest
public function test_multiple_camel_case_keys_are_modified(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithCamelCaseProperty::class,
new CamelCaseKeys([
'some_value' => 'foo',

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Source\Modifier;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Source\Modifier\PathMapping;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class PathMappingTest extends IntegrationTest
@ -35,7 +36,7 @@ final class PathMappingTest extends IntegrationTest
}
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeRootClass::class,
new PathMapping(
[

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Source;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Source\Source;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use IteratorAggregate;
use Traversable;
@ -15,7 +16,7 @@ final class SourceTest extends IntegrationTest
public function test_iterable_source(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::iterable(new SomeIterableClass())
);
@ -29,7 +30,7 @@ final class SourceTest extends IntegrationTest
public function test_array_source(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::array(['someNestedValue' => ['someValue' => 'foo']])
);
@ -43,7 +44,7 @@ final class SourceTest extends IntegrationTest
public function test_json_source(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::json('{"someNestedValue": {"someValue": "foo"}}')
);
@ -60,7 +61,7 @@ final class SourceTest extends IntegrationTest
public function test_yaml_source(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::yaml("someNestedValue:\n someValue: foo")
);
@ -74,7 +75,7 @@ final class SourceTest extends IntegrationTest
public function test_camel_case_keys(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::json('{"some nested value": {"some value": "foo"}}')
->camelCaseKeys()
@ -89,7 +90,7 @@ final class SourceTest extends IntegrationTest
public function test_path_mapping(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::json('{"A": {"B": "foo"}}')
->map([
@ -107,7 +108,7 @@ final class SourceTest extends IntegrationTest
public function test_camel_case_keys_then_path_mapping(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::json('{"level-one": {"level-two": "foo"}}')
->camelCaseKeys()
@ -126,7 +127,7 @@ final class SourceTest extends IntegrationTest
public function test_path_mapping_then_camel_case_keys(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SomeClassWithSubProperty::class,
Source::json('{"level-one": {"level-two": "foo"}}')
->map([

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping\Source;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Source\YamlSource;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
@ -17,7 +18,7 @@ final class YamlSourceMappingTest extends IntegrationTest
public function test_yaml_source_is_mapped_correctly(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(
$object = (new MapperBuilder())->mapper()->map(
SimpleObject::class,
new YamlSource('value: foo')
);

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
@ -16,7 +17,7 @@ final class ValueAlteringMappingTest extends IntegrationTest
public function test_alter_string_alters_value(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->alter(fn () => 'bar')
->alter(fn (string $value) => strtolower($value))
->alter(fn (string $value) => strtoupper($value))
@ -34,7 +35,7 @@ final class ValueAlteringMappingTest extends IntegrationTest
public function test_value_not_accepted_by_value_altering_callback_is_not_used(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->alter(fn (string $value) => $value)
->mapper()
->map('string|null', null);
@ -48,7 +49,7 @@ final class ValueAlteringMappingTest extends IntegrationTest
public function test_alter_function_is_called_when_not_the_first_nor_the_last_one(): void
{
try {
$result = $this->mapperBuilder
$result = (new MapperBuilder())
->alter(fn (int $value) => 404)
->alter(fn (string $value) => $value . '!')
->alter(fn (float $value) => 42.1337)

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Integration\Mapping;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
final class VariadicParameterMappingTest extends IntegrationTest
@ -12,7 +13,7 @@ final class VariadicParameterMappingTest extends IntegrationTest
public function test_only_variadic_parameters_are_mapped_properly(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(SomeClassWithOnlyVariadicParameters::class, [
$object = (new MapperBuilder())->mapper()->map(SomeClassWithOnlyVariadicParameters::class, [
'values' => ['foo', 'bar', 'baz'],
]);
} catch (MappingError $error) {
@ -25,7 +26,7 @@ final class VariadicParameterMappingTest extends IntegrationTest
public function test_variadic_parameters_are_mapped_properly_when_string_keys_are_given(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(SomeClassWithOnlyVariadicParameters::class, [
$object = (new MapperBuilder())->mapper()->map(SomeClassWithOnlyVariadicParameters::class, [
'values' => [
'foo' => 'foo',
'bar' => 'bar',
@ -42,7 +43,7 @@ final class VariadicParameterMappingTest extends IntegrationTest
public function test_named_constructor_with_only_variadic_parameters_are_mapped_properly(): void
{
try {
$object = $this->mapperBuilder
$object = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor([SomeClassWithNamedConstructorWithOnlyVariadicParameters::class, 'new'])
->mapper()
@ -59,7 +60,7 @@ final class VariadicParameterMappingTest extends IntegrationTest
public function test_non_variadic_and_variadic_parameters_are_mapped_properly(): void
{
try {
$object = $this->mapperBuilder->mapper()->map(SomeClassWithNonVariadicAndVariadicParameters::class, [
$object = (new MapperBuilder())->mapper()->map(SomeClassWithNonVariadicAndVariadicParameters::class, [
'int' => 42,
'values' => ['foo', 'bar', 'baz'],
]);
@ -74,14 +75,14 @@ final class VariadicParameterMappingTest extends IntegrationTest
public function test_named_constructor_with_non_variadic_and_variadic_parameters_are_mapped_properly(): void
{
try {
$object = $this->mapperBuilder
$object = (new MapperBuilder())
// @PHP8.1 first-class callable syntax
->registerConstructor([SomeClassWithNamedConstructorWithNonVariadicAndVariadicParameters::class, 'new'])
->mapper()
->map(SomeClassWithNamedConstructorWithNonVariadicAndVariadicParameters::class, [
'int' => 42,
'values' => ['foo', 'bar', 'baz'],
]);
'int' => 42,
'values' => ['foo', 'bar', 'baz'],
]);
} catch (MappingError $error) {
$this->mappingFail($error);
}

View File

@ -6,6 +6,7 @@ namespace CuyZ\Valinor\Tests\Integration\Mapping;
use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\MapperBuilder;
use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage;
use CuyZ\Valinor\Tests\Integration\IntegrationTest;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
@ -18,7 +19,7 @@ final class VisitorMappingTest extends IntegrationTest
$error = new FakeErrorMessage();
try {
$this->mapperBuilder
(new MapperBuilder())
->visit(function (Node $node) use (&$visits): void {
if ($node->isRoot()) {
$visits[] = '#1';

View File

@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Tests\Unit\Cache;
use CuyZ\Valinor\Cache\FileSystemCache;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Tests\Fake\Cache\FakeCache;
use CuyZ\Valinor\Tests\Fake\Cache\FakeFailingCache;
use CuyZ\Valinor\Tests\Fake\Definition\FakeClassDefinition;
use CuyZ\Valinor\Tests\Fake\Definition\FakeFunctionDefinition;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PHPUnit\Framework\TestCase;
use function iterator_to_array;
final class FileSystemCacheTest extends TestCase
{
private vfsStreamDirectory $files;
private FileSystemCache $cache;
protected function setUp(): void
{
parent::setUp();
$this->files = vfsStream::setup('cache-dir');
$this->cache = new FileSystemCache($this->files->url());
$this->injectFakeCache();
}
public function test_cache_entries_are_handled_properly(): void
{
$classDefinition = FakeClassDefinition::new();
$functionDefinition = FakeFunctionDefinition::new();
self::assertFalse($this->cache->has('class-definition'));
self::assertFalse($this->cache->has('function-definition'));
self::assertTrue($this->cache->set('class-definition', $classDefinition));
self::assertTrue($this->cache->set('function-definition', $functionDefinition));
self::assertTrue($this->cache->has('class-definition'));
self::assertTrue($this->cache->has('function-definition'));
/** @var ClassDefinition $cachedClassDefinition */
$cachedClassDefinition = $this->cache->get('class-definition');
/** @var FunctionDefinition $cachedFunctionDefinition */
$cachedFunctionDefinition = $this->cache->get('function-definition');
self::assertSame($classDefinition->name(), $cachedClassDefinition->name());
self::assertSame($functionDefinition->signature(), $cachedFunctionDefinition->signature());
self::assertTrue($this->cache->delete('class-definition'));
self::assertTrue($this->cache->delete('function-definition'));
self::assertFalse($this->cache->has('class-definition'));
self::assertFalse($this->cache->has('function-definition'));
}
public function test_clear_cache_clears_all_caches(): void
{
$classDefinition = FakeClassDefinition::new();
$functionDefinition = FakeFunctionDefinition::new();
$this->cache->set('class-definition', $classDefinition);
$this->cache->set('function-definition', $functionDefinition);
self::assertTrue($this->cache->has('class-definition'));
self::assertTrue($this->cache->has('function-definition'));
self::assertTrue($this->cache->clear());
self::assertFalse($this->cache->has('class-definition'));
self::assertFalse($this->cache->has('function-definition'));
}
public function test_multiple_cache_entries_are_handled_properly(): void
{
$classDefinition = FakeClassDefinition::new();
$functionDefinition = FakeFunctionDefinition::new();
self::assertTrue($this->cache->setMultiple([
'class-definition' => $classDefinition,
'function-definition' => $functionDefinition,
]));
$cached = iterator_to_array($this->cache->getMultiple(['class-definition', 'function-definition']));
/** @var ClassDefinition $cachedClassDefinition */
$cachedClassDefinition = $cached['class-definition'];
/** @var FunctionDefinition $cachedFunctionDefinition */
$cachedFunctionDefinition = $cached['function-definition'];
self::assertSame($classDefinition->name(), $cachedClassDefinition->name());
self::assertSame($functionDefinition->signature(), $cachedFunctionDefinition->signature());
self::assertTrue($this->cache->deleteMultiple(['class-definition', 'function-definition']));
self::assertFalse($this->cache->has('class-definition'));
self::assertFalse($this->cache->has('function-definition'));
}
public function test_methods_returns_false_if_delegates_fail(): void
{
$this->injectFakeCache(true);
$classDefinition = FakeClassDefinition::new();
$functionDefinition = FakeFunctionDefinition::new();
self::assertTrue($this->cache->set('class-definition', $classDefinition));
self::assertFalse($this->cache->set('function-definition', $functionDefinition));
self::assertFalse($this->cache->delete('class-definition'));
self::assertFalse($this->cache->delete('function-definition'));
self::assertFalse($this->cache->clear());
self::assertFalse($this->cache->setMultiple([
'class-definition' => $classDefinition,
'function-definition' => $functionDefinition,
]));
self::assertFalse($this->cache->deleteMultiple(['class-definition', 'function-definition']));
}
public function test_get_non_existing_entry_returns_default_value(): void
{
$defaultValue = FakeClassDefinition::new();
self::assertSame($defaultValue, $this->cache->get('non-existing-entry', $defaultValue));
}
private function injectFakeCache(bool $withFailingCache = false): void
{
(function () use ($withFailingCache) {
$this->delegates = [
ClassDefinition::class => new FakeCache(),
FunctionDefinition::class => $withFailingCache ? new FakeFailingCache() : new FakeCache(),
];
})->call($this->cache);
}
}