From d9c9d3ed95023dc3f0ff379d76fafe4951a06971 Mon Sep 17 00:00:00 2001 From: soyuka Date: Wed, 3 Jun 2026 10:46:33 +0200 Subject: [PATCH] fix(openapi): disambiguate definition names when input and output share a shortname When an operation declares both input and output DTO classes whose FQCNs differ but whose basenames are identical (e.g. App\...\Input\ThingCreate and App\...\Output\ThingCreate), DefinitionNameFactory produced the same definition name for both. The input schema was overwritten by the output schema and Swagger UI displayed the output DTO as the request body. Reuse the existing createPrefixFromClass disambiguation (already used for the resource class itself) for the input/output suffix. This keeps existing definition names stable for the common case where the DTO short name does not collide with anything else, and falls back to progressively longer namespace-qualified suffixes only when a real collision is detected. Fixes #8169 --- src/JsonSchema/DefinitionNameFactory.php | 7 ++-- .../Tests/DefinitionNameFactoryTest.php | 32 +++++++++++++++++++ .../Input/ThingCreate.php | 18 +++++++++++ .../Output/ThingCreate.php | 18 +++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Input/ThingCreate.php create mode 100644 src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Output/ThingCreate.php diff --git a/src/JsonSchema/DefinitionNameFactory.php b/src/JsonSchema/DefinitionNameFactory.php index f70f3a37a6f..2396f9424d5 100644 --- a/src/JsonSchema/DefinitionNameFactory.php +++ b/src/JsonSchema/DefinitionNameFactory.php @@ -44,9 +44,10 @@ public function create(string $className, string $format = 'json', ?string $inpu } if (null !== $inputOrOutputClass && $className !== $inputOrOutputClass) { - $parts = explode('\\', $inputOrOutputClass); - $shortName = end($parts); - $prefix .= self::GLUE.$shortName; + // Use createPrefixFromClass so DTOs with identical short names but different + // FQCNs (e.g. App\...\Input\ThingCreate and App\...\Output\ThingCreate) get + // disambiguated suffixes instead of overwriting each other in the schema map. + $prefix .= self::GLUE.$this->createPrefixFromClass($inputOrOutputClass); } // TODO: remove in 5.0 diff --git a/src/JsonSchema/Tests/DefinitionNameFactoryTest.php b/src/JsonSchema/Tests/DefinitionNameFactoryTest.php index 6257c4da70b..b297b8fae9f 100644 --- a/src/JsonSchema/Tests/DefinitionNameFactoryTest.php +++ b/src/JsonSchema/Tests/DefinitionNameFactoryTest.php @@ -15,8 +15,11 @@ use ApiPlatform\JsonSchema\DefinitionNameFactory; use ApiPlatform\JsonSchema\SchemaFactory; +use ApiPlatform\JsonSchema\Tests\Fixtures\DefinitionNameFactory\InputOutputCollision\Input\ThingCreate as InputThingCreate; +use ApiPlatform\JsonSchema\Tests\Fixtures\DefinitionNameFactory\InputOutputCollision\Output\ThingCreate as OutputThingCreate; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Post; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\DtoOutput; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use PHPUnit\Framework\Attributes\DataProvider; @@ -146,6 +149,35 @@ public function testCreateDifferentPrefixesForClassesWithTheSameShortName(): voi ); } + public function testCreateDistinctDefinitionNamesWhenInputAndOutputShareShortName(): void + { + $definitionNameFactory = new DefinitionNameFactory(); + + $operation = new Post(class: Dummy::class, shortName: 'Thing'); + + $inputName = $definitionNameFactory->create( + Dummy::class, + 'jsonld', + InputThingCreate::class, + $operation, + ['schema_type' => \ApiPlatform\JsonSchema\Schema::TYPE_INPUT] + ); + + $outputName = $definitionNameFactory->create( + Dummy::class, + 'jsonld', + OutputThingCreate::class, + $operation, + ['schema_type' => \ApiPlatform\JsonSchema\Schema::TYPE_OUTPUT] + ); + + self::assertNotSame( + $inputName, + $outputName, + 'Input and Output DTO classes sharing the same short name must produce distinct definition names.' + ); + } + public function testCreateDifferentPrefixesForClassesWithTheSameOperationShortName(): void { $definitionNameFactory = new DefinitionNameFactory(); diff --git a/src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Input/ThingCreate.php b/src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Input/ThingCreate.php new file mode 100644 index 00000000000..c0b771c904d --- /dev/null +++ b/src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Input/ThingCreate.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\JsonSchema\Tests\Fixtures\DefinitionNameFactory\InputOutputCollision\Input; + +class ThingCreate +{ +} diff --git a/src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Output/ThingCreate.php b/src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Output/ThingCreate.php new file mode 100644 index 00000000000..acebe21f36d --- /dev/null +++ b/src/JsonSchema/Tests/Fixtures/DefinitionNameFactory/InputOutputCollision/Output/ThingCreate.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\JsonSchema\Tests\Fixtures\DefinitionNameFactory\InputOutputCollision\Output; + +class ThingCreate +{ +}