feat: improve value altering API

This commit is contained in:
Romain Canon 2022-02-15 21:45:38 +01:00
parent 380961247e
commit 422e6a8b27
6 changed files with 72 additions and 59 deletions

View File

@ -9,14 +9,19 @@ use CuyZ\Valinor\Cache\Compiled\CompiledPhpFileCache;
use CuyZ\Valinor\Cache\RuntimeCache; use CuyZ\Valinor\Cache\RuntimeCache;
use CuyZ\Valinor\Cache\VersionedCache; use CuyZ\Valinor\Cache\VersionedCache;
use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository; use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository; 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\ClassDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\CombinedAttributesRepository; use CuyZ\Valinor\Definition\Repository\Reflection\CombinedAttributesRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\DoctrineAnnotationsRepository; use CuyZ\Valinor\Definition\Repository\Reflection\DoctrineAnnotationsRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\NativeAttributesRepository; use CuyZ\Valinor\Definition\Repository\Reflection\NativeAttributesRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionFunctionDefinitionRepository;
use CuyZ\Valinor\Mapper\Object\Factory\AttributeObjectBuilderFactory; use CuyZ\Valinor\Mapper\Object\Factory\AttributeObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory; use CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\DateTimeObjectBuilderFactory; use CuyZ\Valinor\Mapper\Object\Factory\DateTimeObjectBuilderFactory;
@ -127,7 +132,11 @@ final class Container
$builder = new CasterProxyNodeBuilder($builder); $builder = new CasterProxyNodeBuilder($builder);
$builder = new VisitorNodeBuilder($builder, $settings->nodeVisitors); $builder = new VisitorNodeBuilder($builder, $settings->nodeVisitors);
$builder = new ValueAlteringNodeBuilder($builder, $settings->valueModifier); $builder = new ValueAlteringNodeBuilder(
$builder,
$this->get(FunctionDefinitionRepository::class),
$settings->valueModifier
);
$builder = new ShellVisitorNodeBuilder($builder, $this->get(ShellVisitor::class)); $builder = new ShellVisitorNodeBuilder($builder, $this->get(ShellVisitor::class));
return new ErrorCatcherNodeBuilder($builder); return new ErrorCatcherNodeBuilder($builder);
@ -158,6 +167,19 @@ final class Container
return new CacheClassDefinitionRepository($repository, $cache); return new CacheClassDefinitionRepository($repository, $cache);
}, },
FunctionDefinitionRepository::class => function () use ($settings): FunctionDefinitionRepository {
$repository = 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);
},
AttributesRepository::class => function () use ($settings): AttributesRepository { AttributesRepository::class => function () use ($settings): AttributesRepository {
if (! $settings->enableLegacyDoctrineAnnotations) { if (! $settings->enableLegacyDoctrineAnnotations) {
return new NativeAttributesRepository(); return new NativeAttributesRepository();

View File

@ -18,10 +18,7 @@ final class Settings
/** @var array<string, callable(mixed): object> */ /** @var array<string, callable(mixed): object> */
public array $objectBinding = []; public array $objectBinding = [];
/** /** @var list<callable> */
* @template T
* @var array<string, array<callable(T): T>>
*/
public array $valueModifier = []; public array $valueModifier = [];
/** @var array<callable(Node): void> */ /** @var array<callable(Node): void> */

View File

@ -4,28 +4,35 @@ declare(strict_types=1);
namespace CuyZ\Valinor\Mapper\Tree\Builder; namespace CuyZ\Valinor\Mapper\Tree\Builder;
use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Mapper\Tree\Node; use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\Mapper\Tree\Shell; use CuyZ\Valinor\Mapper\Tree\Shell;
/** /** @internal */
* @internal
*
* @template T
*/
final class ValueAlteringNodeBuilder implements NodeBuilder final class ValueAlteringNodeBuilder implements NodeBuilder
{ {
private NodeBuilder $delegate; private NodeBuilder $delegate;
/** @var array<string, array<callable(T): T>> */ private FunctionDefinitionRepository $functionDefinitionRepository;
private array $valueModifiers;
/** @var list<callable> */
private array $callbacks;
/** @var list<FunctionDefinition> */
private array $functions;
/** /**
* @param array<string, array<callable(T): T>> $valueModifiers * @param list<callable> $callbacks
*/ */
public function __construct(NodeBuilder $delegate, array $valueModifiers) public function __construct(
{ NodeBuilder $delegate,
FunctionDefinitionRepository $functionDefinitionRepository,
array $callbacks
) {
$this->delegate = $delegate; $this->delegate = $delegate;
$this->valueModifiers = $valueModifiers; $this->functionDefinitionRepository = $functionDefinitionRepository;
$this->callbacks = $callbacks;
} }
public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
@ -36,15 +43,38 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
return $node; return $node;
} }
/** @var T $value */
$value = $node->value(); $value = $node->value();
$type = (string)$node->type(); $type = $node->type();
foreach ($this->valueModifiers[$type] ?? [] as $valueModifier) { foreach ($this->functions() as $key => $function) {
$value = $valueModifier($value); $parameters = $function->parameters();
$node = $node->withValue($value);
if (count($parameters) === 0) {
continue;
}
if ($parameters->at(0)->type()->matches($type)) {
$value = ($this->callbacks[$key])($value);
$node = $node->withValue($value);
}
} }
return $node; return $node;
} }
/**
* @return FunctionDefinition[]
*/
private function functions(): array
{
if (! isset($this->functions)) {
$this->functions = [];
foreach ($this->callbacks as $key => $callback) {
$this->functions[$key] = $this->functionDefinitionRepository->for($callback);
}
}
return $this->functions;
}
} }

View File

@ -66,27 +66,8 @@ final class MapperBuilder
*/ */
public function alter(callable $callback): self public function alter(callable $callback): self
{ {
$reflection = Reflection::ofCallable($callback);
if ($reflection->getNumberOfParameters() === 0) {
throw new LogicException('One parameter is required for this callable.');
}
$parameter = $reflection->getParameters()[0];
$nativeType = $parameter->getType();
$typeFromDocBlock = Reflection::docBlockType($parameter);
if ($typeFromDocBlock) {
$type = $typeFromDocBlock;
} elseif ($nativeType) {
$type = Reflection::flattenType($nativeType);
} else {
throw new LogicException('No type was found for the parameter of this callable.');
}
$clone = clone $this; $clone = clone $this;
$clone->settings->valueModifier[$type] ??= []; $clone->settings->valueModifier[] = $callback;
$clone->settings->valueModifier[$type][] = $callback;
return $clone; return $clone;
} }

View File

@ -17,6 +17,7 @@ final class ValueAlteringMappingTest extends IntegrationTest
{ {
try { try {
$result = $this->mapperBuilder $result = $this->mapperBuilder
->alter(fn () => 'bar')
->alter(fn (string $value) => strtolower($value)) ->alter(fn (string $value) => strtolower($value))
->alter(fn (string $value) => strtoupper($value)) ->alter(fn (string $value) => strtoupper($value))
->alter(/** @param string $value */ fn ($value) => $value . '!') ->alter(/** @param string $value */ fn ($value) => $value . '!')

View File

@ -54,22 +54,4 @@ final class MapperBuilderTest extends TestCase
$this->mapperBuilder->bind(static function () { $this->mapperBuilder->bind(static function () {
}); });
} }
public function test_alter_with_callable_with_no_parameter_throws_exception(): void
{
$this->expectException(LogicException::class);
$this->expectExceptionMessage('One parameter is required for this callable.');
$this->mapperBuilder->alter(static function (): void {
});
}
public function test_alter_with_callable_with_parameter_with_no_type_throws_exception(): void
{
$this->expectException(LogicException::class);
$this->expectExceptionMessage('No type was found for the parameter of this callable.');
$this->mapperBuilder->alter(static function ($foo): void {
});
}
} }