Skip to content

Commit 881cfe8

Browse files
committed
fix(object-mapper): ObjectMapperProvider can handle non paginated collections
1 parent 325a04c commit 881cfe8

File tree

6 files changed

+432
-1
lines changed

6 files changed

+432
-1
lines changed

src/State/Provider/ObjectMapperProvider.php

Lines changed: 7 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,8 @@ 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+
$data = array_map(fn ($v) => $this->objectMapper->map($v, $operation->getClass()), $data);
5258
} else {
5359
$data = $this->objectMapper->map($data, $operation->getClass());
5460
}
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
@@ -270,6 +270,10 @@ services:
270270
tags:
271271
- name: 'api_platform.state_processor'
272272

273+
ApiPlatform\Tests\Fixtures\TestBundle\ObjectMapper\IsbnToCustomIsbnTransformer:
274+
tags:
275+
- name: 'object_mapper.transform_callable'
276+
273277
app.messenger_handler.messenger_with_response:
274278
class: 'ApiPlatform\Tests\Fixtures\TestBundle\MessengerHandler\MessengerWithResponseHandler'
275279
tags:
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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\Functional\State\Provider;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7563\BookDto;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Book;
19+
use ApiPlatform\Tests\RecreateSchemaTrait;
20+
use ApiPlatform\Tests\SetupClassResourcesTrait;
21+
22+
/**
23+
* Tests for ObjectMapperProvider to verify object mapping works correctly
24+
* for single items, paginated collections, and unpaginated collections.
25+
*/
26+
final class Issue7563Test extends ApiTestCase
27+
{
28+
use RecreateSchemaTrait;
29+
use SetupClassResourcesTrait;
30+
31+
protected static ?bool $alwaysBootKernel = false;
32+
33+
/**
34+
* @return class-string[]
35+
*/
36+
public static function getResources(): array
37+
{
38+
return [BookDto::class];
39+
}
40+
41+
protected function setUp(): void
42+
{
43+
parent::setUp();
44+
45+
$this->recreateSchema([Book::class]);
46+
$this->loadFixtures();
47+
}
48+
49+
private function loadFixtures(): void
50+
{
51+
$manager = $this->getManager();
52+
53+
for ($i = 1; $i <= 5; ++$i) {
54+
$book = new Book();
55+
$book->name = 'Book '.$i;
56+
$book->isbn = 'ISBN-'.$i;
57+
$manager->persist($book);
58+
}
59+
60+
$manager->flush();
61+
}
62+
63+
public function testGetSingleBookDto(): void
64+
{
65+
// When
66+
self::createClient()->request('GET', '/book_dtos/1');
67+
68+
// Then
69+
self::assertResponseIsSuccessful();
70+
self::assertJsonContains([
71+
'@type' => 'BookDto',
72+
'id' => 1,
73+
'name' => 'Book 1',
74+
'customIsbn' => 'customISBN-1',
75+
]);
76+
}
77+
78+
public function testGetCollectionBookDtoPaginated(): void
79+
{
80+
// When
81+
$response = self::createClient()->request('GET', '/book_dtos');
82+
83+
// Then
84+
self::assertResponseIsSuccessful();
85+
86+
$json = $response->toArray();
87+
self::assertCount(3, $json['hydra:member']);
88+
foreach ($response->toArray()['hydra:member'] as $member) {
89+
self::assertStringStartsWith('customISBN-', $member['customIsbn']);
90+
}
91+
}
92+
93+
public function testGetCollectionBookDtoUnpaginated(): void
94+
{
95+
// When
96+
$response = self::createClient()->request('GET', '/book_dtos?pagination=false');
97+
98+
// Then
99+
self::assertResponseIsSuccessful();
100+
101+
$json = $response->toArray();
102+
self::assertCount(5, $json['hydra:member']);
103+
foreach ($json['hydra:member'] as $member) {
104+
self::assertStringStartsWith('customISBN-', $member['customIsbn']);
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)