Skip to content

Commit b80ed59

Browse files
authored
Merge 0e86a6a into 999bb79
2 parents 999bb79 + 0e86a6a commit b80ed59

File tree

7 files changed

+368
-2
lines changed

7 files changed

+368
-2
lines changed

src/State/Processor/ObjectMapperProcessor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
use ApiPlatform\Metadata\Operation;
1717
use ApiPlatform\State\ProcessorInterface;
18-
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
1918
use Symfony\Component\HttpFoundation\Response;
19+
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
2020

2121
/**
2222
* @implements ProcessorInterface<mixed,mixed>

src/State/Provider/ObjectMapperProvider.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
4040
{
4141
$data = $this->decorated->provide($operation, $uriVariables, $context);
4242

43-
if (!$this->objectMapper || !\is_object($data) || !$operation->canMap()) {
43+
if (!$this->objectMapper || !$operation->canMap()) {
44+
return $data;
45+
}
46+
47+
if (!\is_object($data) && !\is_array($data)) {
4448
return $data;
4549
}
4650

@@ -49,6 +53,12 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
4953

5054
if ($data instanceof PaginatorInterface) {
5155
$data = new ArrayPaginator(array_map(fn ($v) => $this->objectMapper->map($v, $operation->getClass()), iterator_to_array($data)), 0, \count($data));
56+
} elseif (\is_array($data)) {
57+
foreach ($data as &$v) {
58+
if (\is_object($v)) {
59+
$v = $this->objectMapper->map($v, $operation->getClass());
60+
}
61+
}
5262
} else {
5363
$data = $this->objectMapper->map($data, $operation->getClass());
5464
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\Issue7563;
15+
16+
use ApiPlatform\Doctrine\Orm\State\Options;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\GetCollection;
19+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Book;
20+
use ApiPlatform\Tests\Fixtures\TestBundle\ObjectMapper\IsbnToCustomIsbnTransformer;
21+
use Symfony\Component\ObjectMapper\Attribute\Map;
22+
23+
#[Get(
24+
stateOptions: new Options(entityClass: Book::class)
25+
)]
26+
#[GetCollection(
27+
stateOptions: new Options(entityClass: Book::class)
28+
)]
29+
#[Map(source: Book::class)]
30+
class BookDto
31+
{
32+
public function __construct(
33+
#[Map(source: 'id')]
34+
public ?int $id = null,
35+
#[Map(source: 'name')]
36+
public ?string $name = null,
37+
#[Map(source: 'isbn', transform: IsbnToCustomIsbnTransformer::class)]
38+
public ?string $customIsbn = null,
39+
) {
40+
}
41+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\ObjectMapper;
15+
16+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7563\BookDto;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Book;
18+
use Symfony\Component\ObjectMapper\TransformCallableInterface;
19+
20+
/**
21+
* @implements TransformCallableInterface<Book, BookDto>
22+
*/
23+
final readonly class IsbnToCustomIsbnTransformer implements TransformCallableInterface
24+
{
25+
public function __invoke(mixed $value, object $source, ?object $target): mixed
26+
{
27+
return 'custom'.$value;
28+
}
29+
}

tests/Fixtures/app/config/config_common.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ services:
271271
tags:
272272
- name: 'api_platform.state_processor'
273273

274+
ApiPlatform\Tests\Fixtures\TestBundle\ObjectMapper\IsbnToCustomIsbnTransformer:
275+
tags:
276+
- name: 'object_mapper.transform_callable'
277+
274278
app.messenger_handler.messenger_with_response:
275279
class: 'ApiPlatform\Tests\Fixtures\TestBundle\MessengerHandler\MessengerWithResponseHandler'
276280
tags:

tests/Functional/MappingTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
1717
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\FirstResource;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7563\BookDto;
1819
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResource;
1920
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceNoMap;
2021
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceOdm;
@@ -24,6 +25,7 @@
2425
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MappedResourceWithRelationRelated;
2526
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\SecondResource;
2627
use ApiPlatform\Tests\Fixtures\TestBundle\Document\MappedDocument;
28+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Book;
2729
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntity;
2830
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntityNoMap;
2931
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\MappedEntitySourceOnly;
@@ -55,6 +57,7 @@ public static function getResources(): array
5557
MappedResourceWithInput::class,
5658
MappedResourceSourceOnly::class,
5759
MappedResourceNoMap::class,
60+
BookDto::class,
5861
];
5962
}
6063

@@ -277,4 +280,75 @@ private function loadFixtures(): void
277280

278281
$manager->flush();
279282
}
283+
284+
private function loadBookFixtures(): void
285+
{
286+
$manager = $this->getManager();
287+
288+
for ($i = 1; $i <= 5; ++$i) {
289+
$book = new Book();
290+
$book->name = 'Book '.$i;
291+
$book->isbn = 'ISBN-'.$i;
292+
$manager->persist($book);
293+
}
294+
295+
$manager->flush();
296+
}
297+
298+
public function testGetSingleBookDto(): void
299+
{
300+
if ($this->isMongoDB()) {
301+
$this->markTestSkipped('MongoDB not tested.');
302+
}
303+
304+
$this->recreateSchema([Book::class]);
305+
$this->loadBookFixtures();
306+
307+
self::createClient()->request('GET', '/book_dtos/1');
308+
self::assertResponseIsSuccessful();
309+
self::assertJsonContains([
310+
'@type' => 'BookDto',
311+
'id' => 1,
312+
'name' => 'Book 1',
313+
'customIsbn' => 'customISBN-1',
314+
]);
315+
}
316+
317+
public function testGetCollectionBookDtoPaginated(): void
318+
{
319+
if ($this->isMongoDB()) {
320+
$this->markTestSkipped('MongoDB not tested.');
321+
}
322+
323+
$this->recreateSchema([Book::class]);
324+
$this->loadBookFixtures();
325+
326+
$response = self::createClient()->request('GET', '/book_dtos');
327+
self::assertResponseIsSuccessful();
328+
329+
$json = $response->toArray();
330+
self::assertCount(3, $json['hydra:member']);
331+
foreach ($response->toArray()['hydra:member'] as $member) {
332+
self::assertStringStartsWith('customISBN-', $member['customIsbn']);
333+
}
334+
}
335+
336+
public function testGetCollectionBookDtoUnpaginated(): void
337+
{
338+
if ($this->isMongoDB()) {
339+
$this->markTestSkipped('MongoDB not tested.');
340+
}
341+
342+
$this->recreateSchema([Book::class]);
343+
$this->loadBookFixtures();
344+
345+
$response = self::createClient()->request('GET', '/book_dtos?pagination=false');
346+
self::assertResponseIsSuccessful();
347+
348+
$json = $response->toArray();
349+
self::assertCount(5, $json['hydra:member']);
350+
foreach ($json['hydra:member'] as $member) {
351+
self::assertStringStartsWith('customISBN-', $member['customIsbn']);
352+
}
353+
}
280354
}

0 commit comments

Comments
 (0)