From 31c63fd75583156d2342f068121589d522a35993 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 2 Oct 2025 15:39:56 +0200 Subject: [PATCH] fix(state): object mapper with input different --- src/State/Processor/ObjectMapperProcessor.php | 11 +++-- .../ApiResource/MappedResourceWithInput.php | 46 +++++++++++++++++++ .../TestBundle/Dto/MappedResouceInput.php | 20 ++++++++ tests/Functional/MappingTest.php | 33 ++++++++++++- 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 tests/Fixtures/TestBundle/ApiResource/MappedResourceWithInput.php create mode 100644 tests/Fixtures/TestBundle/Dto/MappedResouceInput.php diff --git a/src/State/Processor/ObjectMapperProcessor.php b/src/State/Processor/ObjectMapperProcessor.php index 891f194b5ab..cfb13766f42 100644 --- a/src/State/Processor/ObjectMapperProcessor.php +++ b/src/State/Processor/ObjectMapperProcessor.php @@ -35,11 +35,12 @@ public function __construct( public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): object|array|null { - if (!$this->objectMapper || !$operation->canWrite()) { - return $this->decorated->process($data, $operation, $uriVariables, $context); - } - - if (!(new \ReflectionClass($operation->getClass()))->getAttributes(Map::class)) { + if ( + !$this->objectMapper + || !$operation->canWrite() + || !is_a($data, $operation->getClass(), true) + || !(new \ReflectionClass($operation->getClass()))->getAttributes(Map::class) + ) { return $this->decorated->process($data, $operation, $uriVariables, $context); } diff --git a/tests/Fixtures/TestBundle/ApiResource/MappedResourceWithInput.php b/tests/Fixtures/TestBundle/ApiResource/MappedResourceWithInput.php new file mode 100644 index 00000000000..f85e218fc39 --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/MappedResourceWithInput.php @@ -0,0 +1,46 @@ + + * + * 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\Tests\Fixtures\TestBundle\ApiResource; + +use ApiPlatform\Doctrine\Orm\State\Options; +use ApiPlatform\JsonLd\ContextBuilder; +use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Tests\Fixtures\TestBundle\Dto\MappedResouceInput; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntity; +use Symfony\Component\ObjectMapper\Attribute\Map; + +#[Post( + uriTemplate: '/mapped_resource_with_input', + input: MappedResouceInput::class, + stateOptions: new Options(entityClass: MappedEntity::class), + normalizationContext: [ContextBuilder::HYDRA_CONTEXT_HAS_PREFIX => false], + processor: [self::class, 'process'] +)] +#[Map(target: MappedEntity::class)] +final class MappedResourceWithInput +{ + #[Map(if: false)] + public ?string $id = null; + public string $username; + + public static function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) + { + $s = new self(); + $s->id = $data->id; + $s->username = $data->name; + + return $s; + } +} diff --git a/tests/Fixtures/TestBundle/Dto/MappedResouceInput.php b/tests/Fixtures/TestBundle/Dto/MappedResouceInput.php new file mode 100644 index 00000000000..fd8e88adaf9 --- /dev/null +++ b/tests/Fixtures/TestBundle/Dto/MappedResouceInput.php @@ -0,0 +1,20 @@ + + * + * 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\Tests\Fixtures\TestBundle\Dto; + +class MappedResouceInput +{ + public string $id; + public string $name; +} diff --git a/tests/Functional/MappingTest.php b/tests/Functional/MappingTest.php index 8b5c94d625d..0665b5acdc2 100644 --- a/tests/Functional/MappingTest.php +++ b/tests/Functional/MappingTest.php @@ -17,6 +17,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FirstResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceOdm; +use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithInput; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithRelation; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithRelationRelated; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\SecondResource; @@ -40,7 +41,15 @@ final class MappingTest extends ApiTestCase */ public static function getResources(): array { - return [MappedResource::class, MappedResourceOdm::class, FirstResource::class, SecondResource::class, MappedResourceWithRelation::class, MappedResourceWithRelationRelated::class]; + return [ + MappedResource::class, + MappedResourceOdm::class, + FirstResource::class, + SecondResource::class, + MappedResourceWithRelation::class, + MappedResourceWithRelationRelated::class, + MappedResourceWithInput::class, + ]; } public function testShouldMapBetweenResourceAndEntity(): void @@ -136,6 +145,28 @@ public function testMapPutAllowCreate(): void ]); } + public function testShouldNotMapWhenInput(): void + { + if (!$this->getContainer()->has('api_platform.object_mapper')) { + $this->markTestSkipped('ObjectMapper not installed'); + } + + if ($this->isMongoDB()) { + $this->markTestSkipped('MongoDB not tested'); + } + + $this->recreateSchema([MappedEntity::class]); + $this->loadFixtures(); + $r = self::createClient()->request('POST', 'mapped_resource_with_input', [ + 'headers' => [ + 'content-type' => 'application/ld+json', + ], + 'json' => ['name' => 'test', 'id' => '1'], + ]); + + $this->assertJsonContains(['username' => 'test']); + } + private function loadFixtures(): void { $manager = $this->getManager();