From c8e204a4a3cdfc681e99f80e0c1632e663a32161 Mon Sep 17 00:00:00 2001 From: Romain Canon Date: Sat, 30 Mar 2024 18:22:37 +0100 Subject: [PATCH] fix: load needed information only during interface inferring This commit changes the way interface inferring is done. Previously, the whole set of information for all interfaces was loaded everytime the library was used, which could lead to performance issue for no reason. Now, when an interface must be inferred, only information about this interface will be loaded. --- .../Tree/Builder/InterfaceNodeBuilder.php | 2 +- .../Tree/Builder/ObjectImplementations.php | 26 ++++------ .../Integration/Cache/CacheInjectionTest.php | 2 +- tests/Integration/Cache/CacheWarmupTest.php | 4 +- .../Mapping/InterfaceInferringMappingTest.php | 49 +------------------ 5 files changed, 16 insertions(+), 67 deletions(-) diff --git a/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php b/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php index 09597789..36afb2a5 100644 --- a/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php +++ b/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php @@ -74,7 +74,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode try { $classType = $this->implementations->implementation($className, $values); } catch (ObjectImplementationCallbackError $exception) { - throw UserlandError::from($exception->original()); + throw UserlandError::from($exception); } $class = $this->classDefinitionRepository->for($classType); diff --git a/src/Mapper/Tree/Builder/ObjectImplementations.php b/src/Mapper/Tree/Builder/ObjectImplementations.php index a1e9982c..bf92f657 100644 --- a/src/Mapper/Tree/Builder/ObjectImplementations.php +++ b/src/Mapper/Tree/Builder/ObjectImplementations.php @@ -6,14 +6,12 @@ use CuyZ\Valinor\Definition\FunctionDefinition; use CuyZ\Valinor\Definition\FunctionsContainer; -use CuyZ\Valinor\Mapper\Tree\Exception\InvalidAbstractObjectName; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue; use CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration; use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError; use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationNotRegistered; use CuyZ\Valinor\Mapper\Tree\Exception\ResolvedImplementationIsNotAccepted; use CuyZ\Valinor\Type\ClassType; -use CuyZ\Valinor\Type\Parser\Exception\InvalidType; use CuyZ\Valinor\Type\Parser\TypeParser; use CuyZ\Valinor\Type\Type; use CuyZ\Valinor\Type\Types\ClassStringType; @@ -21,6 +19,8 @@ use CuyZ\Valinor\Type\Types\UnionType; use Exception; +use function assert; + /** @internal */ final class ObjectImplementations { @@ -30,12 +30,7 @@ final class ObjectImplementations public function __construct( private FunctionsContainer $functions, private TypeParser $typeParser - ) { - foreach ($functions as $name => $function) { - /** @var string $name */ - $this->implementations[$name] = $this->implementations($name); - } - } + ) {} public function has(string $name): bool { @@ -52,6 +47,9 @@ public function function(string $name): FunctionDefinition */ public function implementation(string $name, array $arguments): ClassType { + /** @infection-ignore-all / We cannot test the assignment */ + $this->implementations[$name] ??= $this->implementations($name); + $class = $this->call($name, $arguments); return $this->implementations[$name][$class] @@ -83,18 +81,14 @@ private function implementations(string $name): array { $function = $this->functions->get($name)->definition; - try { - $type = $this->typeParser->parse($name); - } catch (InvalidType) { - } + $type = $this->typeParser->parse($name); - if (! isset($type) || (! $type instanceof InterfaceType && ! $type instanceof ClassType)) { - throw new InvalidAbstractObjectName($name); - } + /** @infection-ignore-all */ + assert($type instanceof InterfaceType || $type instanceof ClassType); $classes = $this->implementationsByReturnSignature($name, $function); - if (empty($classes)) { + if ($classes === []) { throw new MissingObjectImplementationRegistration($name, $function); } diff --git a/tests/Integration/Cache/CacheInjectionTest.php b/tests/Integration/Cache/CacheInjectionTest.php index d55ed777..7f54e292 100644 --- a/tests/Integration/Cache/CacheInjectionTest.php +++ b/tests/Integration/Cache/CacheInjectionTest.php @@ -32,7 +32,7 @@ public function test_cache_entries_are_written_once_during_mapping(): void $files = $this->recursivelyFindPhpFiles($cacheDirectory); - self::assertCount(6, $files); + self::assertCount(4, $files); foreach ($files as $file) { $file->setContent($file->getContent() . "\n// generated value 1661895014"); diff --git a/tests/Integration/Cache/CacheWarmupTest.php b/tests/Integration/Cache/CacheWarmupTest.php index 17dc5a5c..3c4a8924 100644 --- a/tests/Integration/Cache/CacheWarmupTest.php +++ b/tests/Integration/Cache/CacheWarmupTest.php @@ -64,8 +64,8 @@ public function test_will_warmup_type_parser_cache_for_object_with_constructor() $mapper->warmup(ObjectToWarmupWithConstructors::class); $mapper->warmup(ObjectToWarmupWithConstructors::class, SomeObjectC::class); - self::assertSame(7, $this->cache->countEntries()); - self::assertSame(7, $this->cache->timeSetWasCalled()); + self::assertSame(6, $this->cache->countEntries()); + self::assertSame(6, $this->cache->timeSetWasCalled()); } public function test_will_warmup_type_parser_cache_for_interface(): void diff --git a/tests/Integration/Mapping/InterfaceInferringMappingTest.php b/tests/Integration/Mapping/InterfaceInferringMappingTest.php index 41358cfc..a4a8457d 100644 --- a/tests/Integration/Mapping/InterfaceInferringMappingTest.php +++ b/tests/Integration/Mapping/InterfaceInferringMappingTest.php @@ -6,13 +6,11 @@ use CuyZ\Valinor\Mapper\MappingError; use CuyZ\Valinor\Mapper\Tree\Exception\CannotResolveObjectType; -use CuyZ\Valinor\Mapper\Tree\Exception\InvalidAbstractObjectName; use CuyZ\Valinor\Mapper\Tree\Exception\InvalidResolvedImplementationValue; use CuyZ\Valinor\Mapper\Tree\Exception\MissingObjectImplementationRegistration; use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationCallbackError; use CuyZ\Valinor\Mapper\Tree\Exception\ObjectImplementationNotRegistered; use CuyZ\Valinor\Mapper\Tree\Exception\ResolvedImplementationIsNotAccepted; -use CuyZ\Valinor\Tests\Fake\Mapper\Tree\Message\FakeErrorMessage; use CuyZ\Valinor\Tests\Fixture\Object\InterfaceWithDifferentNamespaces\A\ClassThatInheritsInterfaceA; use CuyZ\Valinor\Tests\Fixture\Object\InterfaceWithDifferentNamespaces\B\ClassThatInheritsInterfaceB; use CuyZ\Valinor\Tests\Fixture\Object\InterfaceWithDifferentNamespaces\ClassWithBothInterfaces; @@ -279,30 +277,6 @@ public function test_object_implementation_callback_error_throws_exception(): vo ->map(DateTimeInterface::class, []); } - public function test_invalid_abstract_object_name_throws_exception(): void - { - $this->expectException(InvalidAbstractObjectName::class); - $this->expectExceptionCode(1653990369); - $this->expectExceptionMessage('Invalid interface or class name `invalid type`.'); - - $this->mapperBuilder() - ->infer('invalid type', fn () => stdClass::class) // @phpstan-ignore-line - ->mapper() - ->map(stdClass::class, []); - } - - public function test_invalid_abstract_object_type_throws_exception(): void - { - $this->expectException(InvalidAbstractObjectName::class); - $this->expectExceptionCode(1653990369); - $this->expectExceptionMessage('Invalid interface or class name `string`.'); - - $this->mapperBuilder() - ->infer('string', fn () => stdClass::class) // @phpstan-ignore-line - ->mapper() - ->map(stdClass::class, []); - } - public function test_missing_object_implementation_registration_throws_exception(): void { $this->expectException(MissingObjectImplementationRegistration::class); @@ -315,7 +289,7 @@ public function test_missing_object_implementation_registration_throws_exception fn (string $type) => SomeClassThatInheritsInterfaceA::class ) ->mapper() - ->map(SomeInterface::class, []); + ->map(SomeInterface::class, 'foo'); } public function test_invalid_union_object_implementation_registration_throws_exception(): void @@ -330,7 +304,7 @@ public function test_invalid_union_object_implementation_registration_throws_exc fn (string $value): string|int => $value === 'foo' ? 'foo' : 42 ) ->mapper() - ->map(SomeInterface::class, []); + ->map(SomeInterface::class, 'foo'); } public function test_invalid_class_string_object_implementation_registration_throws_exception(): void @@ -402,25 +376,6 @@ public function test_invalid_source_value_throws_exception(): void } } - public function test_exception_thrown_is_caught_and_throws_message_exception(): void - { - try { - $this->mapperBuilder() - ->infer( - DateTimeInterface::class, - /** @return class-string */ - fn (string $value) => throw new FakeErrorMessage('some error message', 1645303304) - ) - ->mapper() - ->map(DateTimeInterface::class, 'foo'); - } catch (MappingError $exception) { - $error = $exception->node()->messages()[0]; - - self::assertSame('1645303304', $error->code()); - self::assertSame('some error message', (string)$error); - } - } - public function test_superfluous_values_throws_exception(): void { try {