diff --git a/src/Library/MapperContainer.php b/src/Library/Container.php similarity index 68% rename from src/Library/MapperContainer.php rename to src/Library/Container.php index 4aab7631..deb3a0ce 100644 --- a/src/Library/MapperContainer.php +++ b/src/Library/Container.php @@ -4,11 +4,19 @@ namespace CuyZ\Valinor\Library; +use CuyZ\Valinor\Cache\ChainCache; +use CuyZ\Valinor\Cache\KeySanitizerCache; use CuyZ\Valinor\Cache\RuntimeCache; use CuyZ\Valinor\Cache\Warmup\RecursiveCacheWarmupService; 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\ClassDefinitionRepository; use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository; +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\ArgumentsMapper; use CuyZ\Valinor\Mapper\Object\Factory\CacheObjectBuilderFactory; use CuyZ\Valinor\Mapper\Object\Factory\CollisionObjectBuilderFactory; @@ -22,14 +30,14 @@ use CuyZ\Valinor\Mapper\Tree\Builder\ArrayNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\CasterNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\CasterProxyNodeBuilder; +use CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\ListNodeBuilder; -use CuyZ\Valinor\Mapper\Tree\Builder\NativeClassNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\NodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\ObjectImplementations; -use CuyZ\Valinor\Mapper\Tree\Builder\ObjectNodeBuilder; +use CuyZ\Valinor\Mapper\Tree\Builder\NativeClassNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\ScalarNodeBuilder; use CuyZ\Valinor\Mapper\Tree\Builder\ShapedArrayNodeBuilder; @@ -39,7 +47,12 @@ use CuyZ\Valinor\Mapper\TreeMapper; use CuyZ\Valinor\Mapper\TypeArgumentsMapper; use CuyZ\Valinor\Mapper\TypeTreeMapper; +use CuyZ\Valinor\Normalizer\FunctionsCheckerNormalizer; +use CuyZ\Valinor\Normalizer\Normalizer; +use CuyZ\Valinor\Normalizer\RecursiveNormalizer; use CuyZ\Valinor\Type\ClassType; +use CuyZ\Valinor\Type\Parser\Factory\LexingTypeParserFactory; +use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory; use CuyZ\Valinor\Type\Parser\TypeParser; use CuyZ\Valinor\Type\ScalarType; use CuyZ\Valinor\Type\Types\ArrayType; @@ -54,27 +67,24 @@ use function count; /** @internal */ -final class MapperContainer +final class Container { - private SharedContainer $shared; - /** @var array */ private array $services = []; /** @var array */ private array $factories; - public function __construct(MapperSettings $settings) + public function __construct(Settings $settings) { - $this->shared = SharedContainer::new($settings->cache ?? null); $this->factories = [ TreeMapper::class => fn () => new TypeTreeMapper( - $this->shared->get(TypeParser::class), + $this->get(TypeParser::class), $this->get(RootNodeBuilder::class) ), ArgumentsMapper::class => fn () => new TypeArgumentsMapper( - $this->shared->get(FunctionDefinitionRepository::class), + $this->get(FunctionDefinitionRepository::class), $this->get(RootNodeBuilder::class) ), @@ -95,7 +105,7 @@ public function __construct(MapperSettings $settings) ShapedArrayType::class => new ShapedArrayNodeBuilder($settings->allowSuperfluousKeys), ScalarType::class => new ScalarNodeBuilder($settings->enableFlexibleCasting), ClassType::class => new NativeClassNodeBuilder( - $this->shared->get(ClassDefinitionRepository::class), + $this->get(ClassDefinitionRepository::class), $this->get(ObjectBuilderFactory::class), $this->get(ObjectNodeBuilder::class), $settings->enableFlexibleCasting, @@ -104,7 +114,7 @@ public function __construct(MapperSettings $settings) $builder = new UnionNodeBuilder( $builder, - $this->shared->get(ClassDefinitionRepository::class), + $this->get(ClassDefinitionRepository::class), $this->get(ObjectBuilderFactory::class), $this->get(ObjectNodeBuilder::class), $settings->enableFlexibleCasting @@ -113,7 +123,7 @@ public function __construct(MapperSettings $settings) $builder = new InterfaceNodeBuilder( $builder, $this->get(ObjectImplementations::class), - $this->shared->get(ClassDefinitionRepository::class), + $this->get(ClassDefinitionRepository::class), $this->get(ObjectBuilderFactory::class), $this->get(ObjectNodeBuilder::class), $settings->enableFlexibleCasting @@ -126,7 +136,7 @@ public function __construct(MapperSettings $settings) $builder = new ValueAlteringNodeBuilder( $builder, new FunctionsContainer( - $this->shared->get(FunctionDefinitionRepository::class), + $this->get(FunctionDefinitionRepository::class), $settings->valueModifier ) ); @@ -141,22 +151,22 @@ public function __construct(MapperSettings $settings) ObjectImplementations::class => fn () => new ObjectImplementations( new FunctionsContainer( - $this->shared->get(FunctionDefinitionRepository::class), + $this->get(FunctionDefinitionRepository::class), $settings->inferredMapping ), - $this->shared->get(TypeParser::class), + $this->get(TypeParser::class), ), ObjectBuilderFactory::class => function () use ($settings) { $constructors = new FunctionsContainer( - $this->shared->get(FunctionDefinitionRepository::class), + $this->get(FunctionDefinitionRepository::class), $settings->customConstructors ); $factory = new ReflectionObjectBuilderFactory(); $factory = new ConstructorObjectBuilderFactory($factory, $settings->nativeConstructors, $constructors); - $factory = new DateTimeZoneObjectBuilderFactory($factory, $this->shared->get(FunctionDefinitionRepository::class)); - $factory = new DateTimeObjectBuilderFactory($factory, $settings->supportedDateFormats, $this->shared->get(FunctionDefinitionRepository::class)); + $factory = new DateTimeZoneObjectBuilderFactory($factory, $this->get(FunctionDefinitionRepository::class)); + $factory = new DateTimeObjectBuilderFactory($factory, $settings->supportedDateFormats, $this->get(FunctionDefinitionRepository::class)); $factory = new CollisionObjectBuilderFactory($factory); if (! $settings->allowPermissiveTypes) { @@ -169,13 +179,56 @@ public function __construct(MapperSettings $settings) return new CacheObjectBuilderFactory($factory, $cache); }, + Normalizer::class => function () use ($settings) { + $functions = new FunctionsContainer( + $this->get(FunctionDefinitionRepository::class), + $settings->sortedHandlers() + ); + + $normalizer = new RecursiveNormalizer($functions); + + return new FunctionsCheckerNormalizer($normalizer, $functions); + }, + + ClassDefinitionRepository::class => fn () => new CacheClassDefinitionRepository( + new ReflectionClassDefinitionRepository( + $this->get(TypeParserFactory::class), + $this->get(AttributesRepository::class), + ), + $this->get(CacheInterface::class) + ), + + FunctionDefinitionRepository::class => fn () => new CacheFunctionDefinitionRepository( + new ReflectionFunctionDefinitionRepository( + $this->get(TypeParserFactory::class), + $this->get(AttributesRepository::class), + ), + $this->get(CacheInterface::class) + ), + + AttributesRepository::class => fn () => new NativeAttributesRepository(), + + TypeParserFactory::class => fn () => new LexingTypeParserFactory(), + + TypeParser::class => fn () => $this->get(TypeParserFactory::class)->get(), + RecursiveCacheWarmupService::class => fn () => new RecursiveCacheWarmupService( - $this->shared->get(TypeParser::class), - $this->shared->get(CacheInterface::class), + $this->get(TypeParser::class), + $this->get(CacheInterface::class), $this->get(ObjectImplementations::class), - $this->shared->get(ClassDefinitionRepository::class), + $this->get(ClassDefinitionRepository::class), $this->get(ObjectBuilderFactory::class) ), + + CacheInterface::class => function () use ($settings) { + $cache = new RuntimeCache(); + + if (isset($settings->cache)) { + $cache = new ChainCache($cache, new KeySanitizerCache($settings->cache)); + } + + return $cache; + }, ]; } @@ -189,6 +242,11 @@ public function argumentsMapper(): ArgumentsMapper return $this->get(ArgumentsMapper::class); } + public function normalizer(): Normalizer + { + return $this->get(Normalizer::class); + } + public function cacheWarmupService(): RecursiveCacheWarmupService { return $this->get(RecursiveCacheWarmupService::class); diff --git a/src/Library/NormalizerContainer.php b/src/Library/NormalizerContainer.php deleted file mode 100644 index 6e517026..00000000 --- a/src/Library/NormalizerContainer.php +++ /dev/null @@ -1,55 +0,0 @@ - */ - private array $services = []; - - /** @var array */ - private array $factories; - - public function __construct(NormalizerSettings $settings) - { - $this->shared = SharedContainer::new($settings->cache); - $this->factories = [ - Normalizer::class => function () use ($settings) { - $functions = new FunctionsContainer( - $this->shared->get(FunctionDefinitionRepository::class), - $settings->sortedHandlers() - ); - - $normalizer = new RecursiveNormalizer($functions); - - return new FunctionsCheckerNormalizer($normalizer, $functions); - }, - ]; - } - - public function normalizer(): Normalizer - { - return $this->get(Normalizer::class); - } - - /** - * @template T of object - * @param class-string $name - * @return T - */ - private function get(string $name): object - { - return $this->services[$name] ??= call_user_func($this->factories[$name]); // @phpstan-ignore-line - } -} diff --git a/src/Library/NormalizerSettings.php b/src/Library/NormalizerSettings.php deleted file mode 100644 index 78b32c44..00000000 --- a/src/Library/NormalizerSettings.php +++ /dev/null @@ -1,35 +0,0 @@ -|null */ - public ?CacheInterface $cache = null; - - /** @var array> */ - public array $handlers = []; - - /** - * @return array - */ - public function sortedHandlers(): array - { - krsort($this->handlers); - - $callables = []; - - foreach ($this->handlers as $list) { - $callables = [...$callables, ...$list]; - } - - return $callables; - } -} diff --git a/src/Library/MapperSettings.php b/src/Library/Settings.php similarity index 78% rename from src/Library/MapperSettings.php rename to src/Library/Settings.php index 0be67053..e7036e7b 100644 --- a/src/Library/MapperSettings.php +++ b/src/Library/Settings.php @@ -11,7 +11,7 @@ use Throwable; /** @internal */ -final class MapperSettings +final class Settings { /** @var non-empty-array */ public const DEFAULT_SUPPORTED_DATETIME_FORMATS = [ @@ -47,9 +47,28 @@ final class MapperSettings /** @var callable(Throwable): ErrorMessage */ public $exceptionFilter; + /** @var array> */ + public array $handlers = []; + public function __construct() { $this->inferredMapping[DateTimeInterface::class] = static fn () => DateTimeImmutable::class; $this->exceptionFilter = fn (Throwable $exception) => throw $exception; } + + /** + * @return array + */ + public function sortedHandlers(): array + { + krsort($this->handlers); + + $callables = []; + + foreach ($this->handlers as $list) { + $callables = [...$callables, ...$list]; + } + + return $callables; + } } diff --git a/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php b/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php index 23b5378f..f1c80bc2 100644 --- a/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php +++ b/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php @@ -7,7 +7,7 @@ use CuyZ\Valinor\Definition\ClassDefinition; use CuyZ\Valinor\Definition\FunctionObject; use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository; -use CuyZ\Valinor\Library\MapperSettings; +use CuyZ\Valinor\Library\Settings; use CuyZ\Valinor\Mapper\Object\DateTimeFormatConstructor; use CuyZ\Valinor\Mapper\Object\FunctionObjectBuilder; use CuyZ\Valinor\Mapper\Object\NativeConstructorObjectBuilder; @@ -44,7 +44,7 @@ public function for(ClassDefinition $class): array $buildersWithOneArgument = array_filter($builders, fn (ObjectBuilder $builder) => count($builder->describeArguments()) === 1); - if (count($buildersWithOneArgument) === 0 || $this->supportedDateFormats !== MapperSettings::DEFAULT_SUPPORTED_DATETIME_FORMATS) { + if (count($buildersWithOneArgument) === 0 || $this->supportedDateFormats !== Settings::DEFAULT_SUPPORTED_DATETIME_FORMATS) { $builders[] = $this->internalDateTimeBuilder($class->type()); } diff --git a/src/MapperBuilder.php b/src/MapperBuilder.php index 7f8cade8..ea0ee07c 100644 --- a/src/MapperBuilder.php +++ b/src/MapperBuilder.php @@ -4,11 +4,12 @@ namespace CuyZ\Valinor; -use CuyZ\Valinor\Library\MapperContainer; -use CuyZ\Valinor\Library\MapperSettings; +use CuyZ\Valinor\Library\Container; +use CuyZ\Valinor\Library\Settings; use CuyZ\Valinor\Mapper\ArgumentsMapper; use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage; use CuyZ\Valinor\Mapper\TreeMapper; +use CuyZ\Valinor\Normalizer\Normalizer; use Psr\SimpleCache\CacheInterface; use Throwable; @@ -18,13 +19,13 @@ /** @api */ final class MapperBuilder { - private MapperSettings $settings; + private Settings $settings; - private MapperContainer $container; + private Container $container; public function __construct() { - $this->settings = new MapperSettings(); + $this->settings = new Settings(); } /** @@ -455,6 +456,24 @@ public function filterExceptions(callable $filter): self return $clone; } + /** + * @param callable(object, callable(): mixed): mixed $callback + * @todo doc + * + */ + public function addHandler(callable $callback, int $priority = 0): self + { + $clone = clone $this; + $clone->settings->handlers[$priority][] = $callback; + + return $clone; + } + + public function normalizer(): Normalizer + { + return $this->container()->normalizer(); + } + /** * Warms up the injected cache implementation with the provided class names. * @@ -482,8 +501,8 @@ public function __clone() unset($this->container); } - private function container(): MapperContainer + private function container(): Container { - return ($this->container ??= new MapperContainer($this->settings)); + return ($this->container ??= new Container($this->settings)); } } diff --git a/src/Normalizer/RecursiveNormalizer.php b/src/Normalizer/RecursiveNormalizer.php index 8313ca33..1205bd41 100644 --- a/src/Normalizer/RecursiveNormalizer.php +++ b/src/Normalizer/RecursiveNormalizer.php @@ -16,7 +16,6 @@ use function array_filter; use function array_shift; -use function count; use function is_array; use function is_iterable; use function is_object; @@ -34,7 +33,10 @@ public function normalize(mixed $value): mixed return $this->doNormalize($value, []); } - private function doNormalize(mixed $value, $references) + /** + * @param array $references + */ + private function doNormalize(mixed $value, array $references): mixed { if ($value === null) { return null; diff --git a/src/NormalizerBuilder.php b/src/NormalizerBuilder.php deleted file mode 100644 index ae948bf9..00000000 --- a/src/NormalizerBuilder.php +++ /dev/null @@ -1,51 +0,0 @@ -settings = new NormalizerSettings(); - } - - /** - * @todo doc - * - * @param callable(object, callable(): mixed): mixed $callback - */ - public function addHandler(callable $callback, int $priority = 0): self - { - $clone = clone $this; - $clone->settings->handlers[$priority][] = $callback; - - return $clone; - } - - public function normalizer(): Normalizer - { - return $this->container()->normalizer(); - } - - public function __clone() - { - $this->settings = clone $this->settings; - unset($this->container); - } - - private function container(): NormalizerContainer - { - return ($this->container ??= new NormalizerContainer($this->settings)); - } -} diff --git a/tests/Integration/Normalizer/NormalizerTest.php b/tests/Integration/Normalizer/NormalizerTest.php index 034cdef8..f070d8b5 100644 --- a/tests/Integration/Normalizer/NormalizerTest.php +++ b/tests/Integration/Normalizer/NormalizerTest.php @@ -4,7 +4,7 @@ namespace CuyZ\Valinor\Tests\Integration\Normalizer; -use CuyZ\Valinor\NormalizerBuilder; +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; @@ -20,11 +20,11 @@ final class NormalizerTest extends TestCase /** * @dataProvider normalize_basic_values_yields_expected_output_data_provider * - * @param array $handlers + * @param array> $handlers */ public function test_normalize_basic_values_yields_expected_output(mixed $input, mixed $expected, array $handlers = []): void { - $builder = new NormalizerBuilder(); + $builder = new MapperBuilder(); foreach ($handlers as $priority => $handlersList) { foreach ($handlersList as $handler) { @@ -300,7 +300,7 @@ public function getIterator(): Traversable public function test_no_priority_given_is_set_to_0(): void { - $result = (new NormalizerBuilder()) + $result = (new MapperBuilder()) ->addHandler(fn (object $object) => 'foo', -2) ->addHandler(fn (object $object, callable $next) => $next() . '!', -1) ->addHandler(fn (object $object, callable $next) => $next() . '?') @@ -316,7 +316,7 @@ public function test_no_param_in_callable_throws_exception(): void $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('@todo'); - (new NormalizerBuilder()) + (new MapperBuilder()) ->addHandler(fn () => 42) ->normalizer() ->normalize(new stdClass()); @@ -327,7 +327,7 @@ public function test_too_many_params_in_callable_throws_exception(): void $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('@todo'); - (new NormalizerBuilder()) + (new MapperBuilder()) // @phpstan-ignore-next-line ->addHandler(fn (stdClass $object, callable $next, int $unexpectedParameter) => 42) ->normalizer() @@ -339,7 +339,7 @@ public function test_second_param_is_not_callable_throws_exception(): void $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('@todo'); - (new NormalizerBuilder()) + (new MapperBuilder()) // @phpstan-ignore-next-line ->addHandler(fn (stdClass $object, int $unexpectedParameterType) => 42) ->normalizer() @@ -356,7 +356,7 @@ public function test_object_circular_reference_is_detected_and_throws_exception( $a->b = $b; $b->a = $a; - (new NormalizerBuilder())->normalizer()->normalize($a); + (new MapperBuilder())->normalizer()->normalize($a); } }