Skip to content

Commit 2f2fd11

Browse files
committed
fix(state): detect mapping on source
1 parent eb6396b commit 2f2fd11

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

src/State/Provider/ObjectMapperProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5959

6060
$entityClass ??= $data::class;
6161

62-
if (!(new \ReflectionClass($entityClass))->getAttributes(Map::class)) {
62+
if (!(new \ReflectionClass($operation->getClass()))->getAttributes(Map::class) && !(new \ReflectionClass($entityClass))->getAttributes(Map::class)) {
6363
return $data;
6464
}
6565

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\ApiResource;
15+
16+
use ApiPlatform\Doctrine\Orm\State\Options;
17+
use ApiPlatform\JsonLd\ContextBuilder;
18+
use ApiPlatform\Metadata\ApiResource;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntitySourceOnly;
20+
use Symfony\Component\ObjectMapper\Attribute\Map;
21+
22+
#[ApiResource(
23+
stateOptions: new Options(entityClass: MappedEntitySourceOnly::class),
24+
normalizationContext: [ContextBuilder::HYDRA_CONTEXT_HAS_PREFIX => false],
25+
)]
26+
#[Map(target: MappedEntitySourceOnly::class)]
27+
final class MappedResourceSourceOnly
28+
{
29+
#[Map(if: false)]
30+
public ?string $id = null;
31+
32+
#[Map(target: 'firstName', transform: [self::class, 'toFirstName'])]
33+
#[Map(target: 'lastName', transform: [self::class, 'toLastName'])]
34+
public string $username;
35+
36+
public static function toFirstName(string $v): string
37+
{
38+
return explode(' ', $v)[0];
39+
}
40+
41+
public static function toLastName(string $v): string
42+
{
43+
return explode(' ', $v)[1];
44+
}
45+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
15+
16+
use Doctrine\ORM\Mapping as ORM;
17+
use Symfony\Component\ObjectMapper\Attribute\Map;
18+
19+
/**
20+
* MappedEntity to MappedResource.
21+
*/
22+
#[ORM\Entity]
23+
class MappedEntitySourceOnly
24+
{
25+
#[ORM\Column(type: 'integer')]
26+
#[ORM\Id]
27+
#[ORM\GeneratedValue(strategy: 'AUTO')]
28+
// @phpstan-ignore-next-line
29+
private ?int $id = null;
30+
31+
#[ORM\Column]
32+
#[Map(if: false)]
33+
private string $firstName;
34+
35+
#[Map(target: 'username', transform: [self::class, 'toUsername'])]
36+
#[ORM\Column]
37+
private string $lastName;
38+
39+
public static function toUsername($value, $object): string
40+
{
41+
return $object->getFirstName().' '.$object->getLastName();
42+
}
43+
44+
public function getId(): ?int
45+
{
46+
return $this->id;
47+
}
48+
49+
public function setLastName(string $name): void
50+
{
51+
$this->lastName = $name;
52+
}
53+
54+
public function getLastName(): string
55+
{
56+
return $this->lastName;
57+
}
58+
59+
public function setFirstName(string $name): void
60+
{
61+
$this->firstName = $name;
62+
}
63+
64+
public function getFirstName(): string
65+
{
66+
return $this->firstName;
67+
}
68+
}

tests/Functional/MappingTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FirstResource;
1818
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResource;
1919
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceOdm;
20+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceSourceOnly;
2021
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithInput;
2122
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithRelation;
2223
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithRelationRelated;
2324
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\SecondResource;
2425
use ApiPlatform\Tests\Fixtures\TestBundle\Document\MappedDocument;
2526
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntity;
27+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntitySourceOnly;
2628
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedResourceWithRelationEntity;
2729
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedResourceWithRelationRelatedEntity;
2830
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\SameEntity;
@@ -49,6 +51,7 @@ public static function getResources(): array
4951
MappedResourceWithRelation::class,
5052
MappedResourceWithRelationRelated::class,
5153
MappedResourceWithInput::class,
54+
MappedResourceSourceOnly::class,
5255
];
5356
}
5457

@@ -167,6 +170,52 @@ public function testShouldNotMapWhenInput(): void
167170
$this->assertJsonContains(['username' => 'test']);
168171
}
169172

173+
public function testShouldMapWithSourceOnly(): void
174+
{
175+
if ($this->isMongoDB()) {
176+
$this->markTestSkipped('MongoDB not tested');
177+
}
178+
179+
if (!$this->getContainer()->has('api_platform.object_mapper')) {
180+
$this->markTestSkipped('ObjectMapper not installed');
181+
}
182+
183+
$this->recreateSchema([MappedEntitySourceOnly::class]);
184+
$manager = $this->getManager();
185+
186+
for ($i = 0; $i < 10; ++$i) {
187+
$e = new MappedEntitySourceOnly();
188+
$e->setLastName('A'.$i);
189+
$e->setFirstName('B'.$i);
190+
$manager->persist($e);
191+
}
192+
193+
$manager->flush();
194+
195+
$r = self::createClient()->request('GET', '/mapped_resource_source_onlies');
196+
$this->assertJsonContains(['member' => [
197+
['username' => 'B0 A0'],
198+
['username' => 'B1 A1'],
199+
['username' => 'B2 A2'],
200+
]]);
201+
202+
$r = self::createClient()->request('POST', 'mapped_resource_source_onlies', ['json' => ['username' => 'so yuka']]);
203+
$this->assertJsonContains(['username' => 'so yuka']);
204+
205+
$manager = $this->getManager();
206+
$repo = $manager->getRepository(MappedEntitySourceOnly::class);
207+
$persisted = $repo->findOneBy(['id' => $r->toArray()['id']]);
208+
$this->assertSame('so', $persisted->getFirstName());
209+
$this->assertSame('yuka', $persisted->getLastName());
210+
211+
$uri = $r->toArray()['@id'];
212+
self::createClient()->request('GET', $uri);
213+
$this->assertJsonContains(['username' => 'so yuka']);
214+
215+
$r = self::createClient()->request('PATCH', $uri, ['json' => ['username' => 'ba zar'], 'headers' => ['content-type' => 'application/merge-patch+json']]);
216+
$this->assertJsonContains(['username' => 'ba zar']);
217+
}
218+
170219
private function loadFixtures(): void
171220
{
172221
$manager = $this->getManager();

0 commit comments

Comments
 (0)