diff --git a/src/Library/Container.php b/src/Library/Container.php index 569492e..ded16ad 100644 --- a/src/Library/Container.php +++ b/src/Library/Container.php @@ -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 $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(); diff --git a/src/Library/Settings.php b/src/Library/Settings.php index c0185be..6a585cd 100644 --- a/src/Library/Settings.php +++ b/src/Library/Settings.php @@ -18,10 +18,7 @@ final class Settings /** @var array */ public array $objectBinding = []; - /** - * @template T - * @var array> - */ + /** @var list */ public array $valueModifier = []; /** @var array */ diff --git a/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php b/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php index f97a6c8..7b47a95 100644 --- a/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php @@ -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> */ - private array $valueModifiers; + private FunctionDefinitionRepository $functionDefinitionRepository; + + /** @var list */ + private array $callbacks; + + /** @var list */ + private array $functions; /** - * @param array> $valueModifiers + * @param list $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; + } } diff --git a/src/MapperBuilder.php b/src/MapperBuilder.php index 1cfa95b..a3e4e26 100644 --- a/src/MapperBuilder.php +++ b/src/MapperBuilder.php @@ -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; } diff --git a/tests/Integration/Mapping/ValueAlteringMappingTest.php b/tests/Integration/Mapping/ValueAlteringMappingTest.php index 5a19acf..384d23a 100644 --- a/tests/Integration/Mapping/ValueAlteringMappingTest.php +++ b/tests/Integration/Mapping/ValueAlteringMappingTest.php @@ -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 . '!') diff --git a/tests/Unit/MapperBuilderTest.php b/tests/Unit/MapperBuilderTest.php index 6c84a9c..b22a5d1 100644 --- a/tests/Unit/MapperBuilderTest.php +++ b/tests/Unit/MapperBuilderTest.php @@ -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 { - }); - } }