diff --git a/src/Type/Parser/Factory/Specifications/AliasSpecification.php b/src/Type/Parser/Factory/Specifications/AliasSpecification.php index be71ec10..3d927787 100644 --- a/src/Type/Parser/Factory/Specifications/AliasSpecification.php +++ b/src/Type/Parser/Factory/Specifications/AliasSpecification.php @@ -12,6 +12,10 @@ use ReflectionFunction; use Reflector; +use function array_shift; +use function explode; +use function strtolower; + /** @internal */ final class AliasSpecification implements TypeParserSpecification { @@ -52,10 +56,10 @@ private function resolveAlias(string $symbol): string $alias = $symbol; $namespaceParts = explode('\\', $symbol); - $lastPart = array_shift($namespaceParts); + $firstPart = array_shift($namespaceParts); - if ($lastPart) { - $alias = strtolower($lastPart); + if ($firstPart) { + $alias = strtolower($firstPart); } $aliases = PhpParser::parseUseStatements($this->reflection); @@ -64,17 +68,17 @@ private function resolveAlias(string $symbol): string return $symbol; } - if ($aliases[$alias] === $symbol) { - return $symbol; - } + $fqcn = $aliases[$alias]['fqcn']; - $full = $aliases[$alias]; + if ($namespaceParts === []) { + return $fqcn; + } - if (! empty($namespaceParts)) { - $full .= '\\' . implode('\\', $namespaceParts); + if (! $aliases[$alias]['isExplicitAlias']) { + return $symbol; } - return $full; + return $fqcn . '\\' . implode('\\', $namespaceParts); } private function resolveNamespaced(string $symbol): string diff --git a/src/Utility/Reflection/PhpParser.php b/src/Utility/Reflection/PhpParser.php index 9f08755d..3024c331 100644 --- a/src/Utility/Reflection/PhpParser.php +++ b/src/Utility/Reflection/PhpParser.php @@ -17,12 +17,12 @@ */ final class PhpParser { - /** @var array> */ + /** @var array> */ private static array $statements = []; /** * @param ReflectionClass|ReflectionFunction|ReflectionMethod $reflection - * @return array + * @return array */ public static function parseUseStatements(\ReflectionClass|\ReflectionFunction|\ReflectionMethod $reflection): array { @@ -34,7 +34,7 @@ public static function parseUseStatements(\ReflectionClass|\ReflectionFunction|\ /** * @param ReflectionClass|ReflectionFunction|ReflectionMethod $reflection - * @return array + * @return array */ private static function fetchUseStatements(\ReflectionClass|\ReflectionFunction|\ReflectionMethod $reflection): array { diff --git a/src/Utility/Reflection/TokenParser.php b/src/Utility/Reflection/TokenParser.php index e97bd34c..68dc04ee 100644 --- a/src/Utility/Reflection/TokenParser.php +++ b/src/Utility/Reflection/TokenParser.php @@ -29,7 +29,7 @@ public function __construct(string $content) } /** - * @return array + * @return array */ public function parseUseStatements(string $namespaceName): array { @@ -58,7 +58,7 @@ public function parseUseStatements(string $namespaceName): array } /** - * @return array + * @return array */ private function parseUseStatement(): array { @@ -86,12 +86,18 @@ private function parseUseStatement(): array $explicitAlias = true; $alias = ''; } elseif ($name === ',') { - $statements[strtolower($alias)] = $groupRoot . $class; + $statements[strtolower($alias)] = [ + 'fqcn' => $groupRoot . $class, + 'isExplicitAlias' => $explicitAlias, + ]; $class = $alias = ''; $explicitAlias = false; } elseif ($name === ';') { if ($alias !== '') { - $statements[strtolower($alias)] = $groupRoot . $class; + $statements[strtolower($alias)] = [ + 'fqcn' => $groupRoot . $class, + 'isExplicitAlias' => $explicitAlias, + ]; } break; } elseif ($name === '{') { @@ -100,6 +106,7 @@ private function parseUseStatement(): array } } + /** @var array */ return $statements; } diff --git a/tests/Integration/Mapping/Namespace/SameClassNameAsNamespaceGroupMappingTest.php b/tests/Integration/Mapping/Namespace/SameClassNameAsNamespaceGroupMappingTest.php new file mode 100644 index 00000000..c8ebe69f --- /dev/null +++ b/tests/Integration/Mapping/Namespace/SameClassNameAsNamespaceGroupMappingTest.php @@ -0,0 +1,37 @@ +mapperBuilder()->mapper()->map(ClassContainingNamespacedClasses::class, [ + 'objectA' => ['stringValue' => 'foo'], + 'objectB' => ['integerValue' => 42], + ]); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame('foo', $result->objectA->stringValue); + self::assertSame(42, $result->objectB->integerValue); + } +} + +final class ClassContainingNamespacedClasses +{ + public SomeNamespace $objectA; + public SomeClass $objectB; +} diff --git a/tests/Integration/Mapping/Namespace/class-name-with-same-name-as-namespace-group.php b/tests/Integration/Mapping/Namespace/class-name-with-same-name-as-namespace-group.php new file mode 100644 index 00000000..948931a3 --- /dev/null +++ b/tests/Integration/Mapping/Namespace/class-name-with-same-name-as-namespace-group.php @@ -0,0 +1,13 @@ +|ReflectionFunction|ReflectionMethod $reflection - * @param array $expectedMap + * @param array $expectedMap */ #[DataProvider('use_statements_data_provider')] public function test_parse_use_statements(\ReflectionClass|\ReflectionFunction|\ReflectionMethod $reflection, array $expectedMap): void @@ -46,120 +46,282 @@ public static function use_statements_data_provider(): Generator yield 'one namespace' => [ new ReflectionClass(ClassInSingleNamespace::class), [ - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'multiple namespaces, class A' => [ new ReflectionClass(ClassA::class), [ - 'classb' => ClassB::class, - 'classbalias' => ClassB::class, - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'classb' => [ + 'fqcn' => ClassB::class, + 'isExplicitAlias' => false, + ], + 'classbalias' => [ + 'fqcn' => ClassB::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'multiple namespaces, class B' => [ new ReflectionClass(ClassB::class), [ - 'classa' => ClassA::class, - 'classaalias' => ClassA::class, - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'classa' => [ + 'fqcn' => ClassA::class, + 'isExplicitAlias' => false, + ], + 'classaalias' => [ + 'fqcn' => ClassA::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'multiple namespaces, function A' => [ new ReflectionFunction('\CuyZ\Valinor\Tests\Fixtures\WithAliasA\functionA'), [ - 'classb' => ClassB::class, - 'classbalias' => ClassB::class, - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'classb' => [ + 'fqcn' => ClassB::class, + 'isExplicitAlias' => false, + ], + 'classbalias' => [ + 'fqcn' => ClassB::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'multiple namespaces, function B' => [ new ReflectionFunction('\CuyZ\Valinor\Tests\Fixtures\WithAliasB\functionB'), [ - 'classa' => ClassA::class, - 'classaalias' => ClassA::class, - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'classa' => [ + 'fqcn' => ClassA::class, + 'isExplicitAlias' => false, + ], + 'classaalias' => [ + 'fqcn' => ClassA::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'one namespace, method' => [ new ReflectionMethod(ClassInSingleNamespace::class, '__construct'), [ - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'multiple namespaces, class A method' => [ new ReflectionMethod(ClassA::class, '__construct'), [ - 'classb' => ClassB::class, - 'classbalias' => ClassB::class, - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'classb' => [ + 'fqcn' => ClassB::class, + 'isExplicitAlias' => false, + ], + 'classbalias' => [ + 'fqcn' => ClassB::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'multiple namespaces, class B method' => [ new ReflectionMethod(ClassB::class, '__construct'), [ - 'classa' => ClassA::class, - 'classaalias' => ClassA::class, - 'baralias' => Bar::class, - 'foo' => Foo::class, - 'datetimeimmutable' => \DateTimeImmutable::class, - 'stdclassalias' => \stdClass::class, + 'classa' => [ + 'fqcn' => ClassA::class, + 'isExplicitAlias' => false, + ], + 'classaalias' => [ + 'fqcn' => ClassA::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'foo' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => false, + ], + 'datetimeimmutable' => [ + 'fqcn' => \DateTimeImmutable::class, + 'isExplicitAlias' => false, + ], + 'stdclassalias' => [ + 'fqcn' => \stdClass::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'function in root namespace' => [ new ReflectionFunction('function_in_root_namespace'), [ - 'fooalias' => Foo::class, - 'baralias' => Bar::class, + 'fooalias' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'one namespace, one use statement, two import statements' => [ new ReflectionFunction('CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\function_with_several_import_statements_in_same_use_statement'), [ - 'fooalias' => Foo::class, - 'baralias' => Bar::class, - 'anotherfooalias' => Foo::class, - 'anotherbaralias' => Bar::class, + 'fooalias' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'anotherfooalias' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => true, + ], + 'anotherbaralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], ] ]; yield 'one namespace, one use statement, two grouped import statements' => [ new ReflectionFunction('\CuyZ\Valinor\Tests\Unit\Utility\Reflection\Fixtures\function_with_grouped_import_statements'), [ - 'fooalias' => Foo::class, - 'baralias' => Bar::class, - 'anotherfooalias' => Foo::class, - 'anotherbaralias' => Bar::class, + 'fooalias' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => true, + ], + 'baralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], + 'anotherfooalias' => [ + 'fqcn' => Foo::class, + 'isExplicitAlias' => true, + ], + 'anotherbaralias' => [ + 'fqcn' => Bar::class, + 'isExplicitAlias' => true, + ], ], ]; }