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\VersionedCache;
use CuyZ\Valinor\Definition\ClassDefinition;
use CuyZ\Valinor\Definition\FunctionDefinition;
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;
use CuyZ\Valinor\Definition\Repository\Reflection\DoctrineAnnotationsRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\NativeAttributesRepository;
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\ConstructorObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\DateTimeObjectBuilderFactory;
@ -127,7 +132,11 @@ final class Container
$builder = new CasterProxyNodeBuilder($builder);
$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));
return new ErrorCatcherNodeBuilder($builder);
@ -158,6 +167,19 @@ final class Container
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 {
if (! $settings->enableLegacyDoctrineAnnotations) {
return new NativeAttributesRepository();

View File

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

View File

@ -4,28 +4,35 @@ declare(strict_types=1);
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\Shell;
/**
* @internal
*
* @template T
*/
/** @internal */
final class ValueAlteringNodeBuilder implements NodeBuilder
{
private NodeBuilder $delegate;
/** @var array<string, array<callable(T): T>> */
private array $valueModifiers;
private FunctionDefinitionRepository $functionDefinitionRepository;
/** @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->valueModifiers = $valueModifiers;
$this->functionDefinitionRepository = $functionDefinitionRepository;
$this->callbacks = $callbacks;
}
public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
@ -36,15 +43,38 @@ final class ValueAlteringNodeBuilder implements NodeBuilder
return $node;
}
/** @var T $value */
$value = $node->value();
$type = (string)$node->type();
$type = $node->type();
foreach ($this->valueModifiers[$type] ?? [] as $valueModifier) {
$value = $valueModifier($value);
$node = $node->withValue($value);
foreach ($this->functions() as $key => $function) {
$parameters = $function->parameters();
if (count($parameters) === 0) {
continue;
}
if ($parameters->at(0)->type()->matches($type)) {
$value = ($this->callbacks[$key])($value);
$node = $node->withValue($value);
}
}
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
{
$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->settings->valueModifier[$type] ??= [];
$clone->settings->valueModifier[$type][] = $callback;
$clone->settings->valueModifier[] = $callback;
return $clone;
}

View File

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

View File

@ -54,22 +54,4 @@ final class MapperBuilderTest extends TestCase
$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 {
});
}
}