diff --git a/src/State/Provider/ObjectMapperProvider.php b/src/State/Provider/ObjectMapperProvider.php index b19d9758b3e..c9170d7a56e 100644 --- a/src/State/Provider/ObjectMapperProvider.php +++ b/src/State/Provider/ObjectMapperProvider.php @@ -59,7 +59,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $entityClass ??= $data::class; - if (!(new \ReflectionClass($entityClass))->getAttributes(Map::class)) { + if (!(new \ReflectionClass($operation->getClass()))->getAttributes(Map::class) && !(new \ReflectionClass($entityClass))->getAttributes(Map::class)) { return $data; } diff --git a/tests/Fixtures/TestBundle/ApiResource/MappedResourceSourceOnly.php b/tests/Fixtures/TestBundle/ApiResource/MappedResourceSourceOnly.php new file mode 100644 index 00000000000..239cd75a61f --- /dev/null +++ b/tests/Fixtures/TestBundle/ApiResource/MappedResourceSourceOnly.php @@ -0,0 +1,45 @@ + + * + * 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\ApiResource; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntitySourceOnly; +use Symfony\Component\ObjectMapper\Attribute\Map; + +#[ApiResource( + stateOptions: new Options(entityClass: MappedEntitySourceOnly::class), + normalizationContext: [ContextBuilder::HYDRA_CONTEXT_HAS_PREFIX => false], +)] +#[Map(target: MappedEntitySourceOnly::class)] +final class MappedResourceSourceOnly +{ + #[Map(if: false)] + public ?string $id = null; + + #[Map(target: 'firstName', transform: [self::class, 'toFirstName'])] + #[Map(target: 'lastName', transform: [self::class, 'toLastName'])] + public string $username; + + public static function toFirstName(string $v): string + { + return explode(' ', $v)[0]; + } + + public static function toLastName(string $v): string + { + return explode(' ', $v)[1]; + } +} diff --git a/tests/Fixtures/TestBundle/Entity/MappedEntitySourceOnly.php b/tests/Fixtures/TestBundle/Entity/MappedEntitySourceOnly.php new file mode 100644 index 00000000000..6e46cd9d66c --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/MappedEntitySourceOnly.php @@ -0,0 +1,67 @@ + + * + * 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\Entity; + +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\ObjectMapper\Attribute\Map; + +/** + * MappedEntity to MappedResource. + */ +#[ORM\Entity] +class MappedEntitySourceOnly +{ + #[ORM\Column(type: 'integer')] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'AUTO')] + private ?int $id = null; + + #[ORM\Column] + #[Map(if: false)] + private string $firstName; + + #[Map(target: 'username', transform: [self::class, 'toUsername'])] + #[ORM\Column] + private string $lastName; + + public static function toUsername($value, $object): string + { + return $object->getFirstName().' '.$object->getLastName(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function setLastName(string $name): void + { + $this->lastName = $name; + } + + public function getLastName(): string + { + return $this->lastName; + } + + public function setFirstName(string $name): void + { + $this->firstName = $name; + } + + public function getFirstName(): string + { + return $this->firstName; + } +} diff --git a/tests/Functional/MappingTest.php b/tests/Functional/MappingTest.php index 0665b5acdc2..79522492379 100644 --- a/tests/Functional/MappingTest.php +++ b/tests/Functional/MappingTest.php @@ -17,12 +17,14 @@ 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\MappedResourceSourceOnly; 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; use ApiPlatform\Tests\Fixtures\TestBundle\Document\MappedDocument; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntity; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntitySourceOnly; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedResourceWithRelationEntity; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedResourceWithRelationRelatedEntity; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SameEntity; @@ -49,6 +51,7 @@ public static function getResources(): array MappedResourceWithRelation::class, MappedResourceWithRelationRelated::class, MappedResourceWithInput::class, + MappedResourceSourceOnly::class, ]; } @@ -167,6 +170,52 @@ public function testShouldNotMapWhenInput(): void $this->assertJsonContains(['username' => 'test']); } + public function testShouldMapWithSourceOnly(): void + { + if ($this->isMongoDB()) { + $this->markTestSkipped('MongoDB not tested'); + } + + if (!$this->getContainer()->has('api_platform.object_mapper')) { + $this->markTestSkipped('ObjectMapper not installed'); + } + + $this->recreateSchema([MappedEntitySourceOnly::class]); + $manager = $this->getManager(); + + for ($i = 0; $i < 10; ++$i) { + $e = new MappedEntitySourceOnly(); + $e->setLastName('A'.$i); + $e->setFirstName('B'.$i); + $manager->persist($e); + } + + $manager->flush(); + + $r = self::createClient()->request('GET', '/mapped_resource_source_onlies'); + $this->assertJsonContains(['member' => [ + ['username' => 'B0 A0'], + ['username' => 'B1 A1'], + ['username' => 'B2 A2'], + ]]); + + $r = self::createClient()->request('POST', 'mapped_resource_source_onlies', ['json' => ['username' => 'so yuka']]); + $this->assertJsonContains(['username' => 'so yuka']); + + $manager = $this->getManager(); + $repo = $manager->getRepository(MappedEntitySourceOnly::class); + $persisted = $repo->findOneBy(['id' => $r->toArray()['id']]); + $this->assertSame('so', $persisted->getFirstName()); + $this->assertSame('yuka', $persisted->getLastName()); + + $uri = $r->toArray()['@id']; + self::createClient()->request('GET', $uri); + $this->assertJsonContains(['username' => 'so yuka']); + + $r = self::createClient()->request('PATCH', $uri, ['json' => ['username' => 'ba zar'], 'headers' => ['content-type' => 'application/merge-patch+json']]); + $this->assertJsonContains(['username' => 'ba zar']); + } + private function loadFixtures(): void { $manager = $this->getManager();