From 6bb31ab031ea2b2272a63314848a6e1060e4ce34 Mon Sep 17 00:00:00 2001 From: wuchen90 Date: Thu, 19 Feb 2026 11:49:22 +0100 Subject: [PATCH] fix(jsonschema): name collision when an operation name is already used by another class --- src/JsonSchema/DefinitionNameFactory.php | 20 ++++++++++++++++++- .../Tests/DefinitionNameFactoryTest.php | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/JsonSchema/DefinitionNameFactory.php b/src/JsonSchema/DefinitionNameFactory.php index 0d1948742a0..702167d9d2f 100644 --- a/src/JsonSchema/DefinitionNameFactory.php +++ b/src/JsonSchema/DefinitionNameFactory.php @@ -36,7 +36,7 @@ public function __construct(private ?array $distinctFormats = null) public function create(string $className, string $format = 'json', ?string $inputOrOutputClass = null, ?Operation $operation = null, array $serializerContext = []): string { if ($operation) { - $prefix = $operation->getShortName(); + $prefix = $this->createPrefixFromOperation($operation); } if (!isset($prefix)) { @@ -82,6 +82,24 @@ private function encodeDefinitionName(string $name): string return preg_replace('/[^a-zA-Z0-9.\-_]/', '.', $name); } + private function createPrefixFromOperation(Operation $operation): ?string + { + $name = $operation->getShortName(); + + if (!isset($this->prefixCache[$name])) { + $this->prefixCache[$name] = $operation->getClass(); + + return $name; + } + + if ($this->prefixCache[$name] === $operation->getClass()) { + return $name; + } + + // This will fallback to using `createPrefixFromClass` + return null; + } + private function createPrefixFromClass(string $fullyQualifiedClassName, int $namespaceParts = 1): string { $parts = explode('\\', $fullyQualifiedClassName); diff --git a/src/JsonSchema/Tests/DefinitionNameFactoryTest.php b/src/JsonSchema/Tests/DefinitionNameFactoryTest.php index 1c129a1f3b7..1096e2427ff 100644 --- a/src/JsonSchema/Tests/DefinitionNameFactoryTest.php +++ b/src/JsonSchema/Tests/DefinitionNameFactoryTest.php @@ -105,4 +105,24 @@ public function testCreateDifferentPrefixesForClassesWithTheSameShortName(): voi $definitionNameFactory->create(Fixtures\DefinitionNameFactory\NamespaceA\Module\DummyClass::class, 'jsonhal') ); } + + public function testCreateDifferentPrefixesForClassesWithTheSameOperationShortName(): void + { + $definitionNameFactory = new DefinitionNameFactory(); + + self::assertEquals( + 'DummyClass.jsonapi', + $definitionNameFactory->create(Fixtures\DefinitionNameFactory\NamespaceA\Module\DummyClass::class, 'jsonapi', null, new Get(class: Fixtures\DefinitionNameFactory\NamespaceA\Module\DummyClass::class, shortName: 'DummyClass')) + ); + + self::assertEquals( + 'Module.DummyClass.jsonapi', + $definitionNameFactory->create(Fixtures\DefinitionNameFactory\NamespaceB\Module\DummyClass::class, 'jsonapi', null, new Get(class: Fixtures\DefinitionNameFactory\NamespaceB\Module\DummyClass::class, shortName: 'DummyClass')) + ); + + self::assertEquals( + 'NamespaceC.Module.DummyClass.jsonapi', + $definitionNameFactory->create(Fixtures\DefinitionNameFactory\NamespaceC\Module\DummyClass::class, 'jsonapi', null, new Get(class: Fixtures\DefinitionNameFactory\NamespaceC\Module\DummyClass::class, shortName: 'DummyClass')) + ); + } }