Skip to content

Commit cd5067c

Browse files
committed
Improve Input/Output with normalizers
1 parent 1a9335e commit cd5067c

17 files changed

+494
-94
lines changed

features/bootstrap/DoctrineContext.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar;
6060
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCarColor;
6161
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyDate;
62+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyDtoCustom;
6263
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyDtoNoInput;
6364
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyDtoNoOutput;
6465
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyFriend;
@@ -1182,6 +1183,36 @@ public function thereIsAMaxDepthDummyWithLevelOfDescendants(int $level)
11821183
$this->manager->flush();
11831184
}
11841185

1186+
/**
1187+
* @Given there is a DummyCustomDto
1188+
*/
1189+
public function thereIsADummyCustomDto()
1190+
{
1191+
$dto = new DummyDtoCustom();
1192+
$dto->lorem = 'test';
1193+
$dto->ipsum = '0';
1194+
$this->manager->persist($dto);
1195+
1196+
$this->manager->flush();
1197+
$this->manager->clear();
1198+
}
1199+
1200+
/**
1201+
* @Given there are :nb DummyCustomDto
1202+
*/
1203+
public function thereAreNbDummyCustomDto($nb)
1204+
{
1205+
for ($i = 1; $i <= $nb; ++$i) {
1206+
$dto = new DummyDtoCustom();
1207+
$dto->lorem = 'test';
1208+
$dto->ipsum = (string) $i;
1209+
$this->manager->persist($dto);
1210+
}
1211+
1212+
$this->manager->flush();
1213+
$this->manager->clear();
1214+
}
1215+
11851216
private function isOrm(): bool
11861217
{
11871218
return null !== $this->schemaTool;

features/main/dto.feature

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# ajoute test relation
2+
Feature: DTO input and output
3+
In order to use an hypermedia API
4+
As a client software developer
5+
I need to be able to use DTOs on my resources as Input or Output objects.
6+
7+
@createSchema
8+
Scenario: Create a resource with a custom Input.
9+
When I add "Content-Type" header equal to "application/ld+json"
10+
And I send a "POST" request to "/dummy_dto_customs" with body:
11+
"""
12+
{
13+
"foo": "test",
14+
"bar": 1
15+
}
16+
"""
17+
Then the response status code should be 201
18+
And the response should be in JSON
19+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
20+
And the JSON should be equal to:
21+
"""
22+
{
23+
"@context": "/contexts/DummyDtoCustom",
24+
"@id": "/dummy_dto_customs/1",
25+
"@type": "DummyDtoCustom",
26+
"lorem": "test",
27+
"ipsum": "1",
28+
"id": 1
29+
}
30+
"""
31+
32+
@createSchema
33+
Scenario: Get an item with a custom output
34+
Given there is a DummyCustomDto
35+
When I send a "GET" request to "/dummy_dto_custom_output/1"
36+
Then the response status code should be 200
37+
And the response should be in JSON
38+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
39+
And the JSON should be equal to:
40+
"""
41+
{
42+
"foo": "test",
43+
"bar": 0
44+
}
45+
"""
46+
47+
@createSchema
48+
Scenario: Get a collection with a custom output
49+
Given there are 2 DummyCustomDto
50+
When I send a "GET" request to "/dummy_dto_custom_output"
51+
Then the response status code should be 200
52+
And the response should be in JSON
53+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
54+
And the JSON should be equal to:
55+
"""
56+
{
57+
"@context": "/contexts/DummyDtoCustom",
58+
"@id": "/dummy_dto_customs",
59+
"@type": "hydra:Collection",
60+
"hydra:member": [
61+
{
62+
"foo": "test",
63+
"bar": 1
64+
},
65+
{
66+
"foo": "test",
67+
"bar": 2
68+
}
69+
],
70+
"hydra:totalItems": 2
71+
}
72+
"""
73+
74+
@createSchema
75+
Scenario: Create a DummyCustomDto object without output
76+
When I add "Content-Type" header equal to "application/ld+json"
77+
And I send a "POST" request to "/dummy_dto_custom_post_without_output" with body:
78+
"""
79+
{
80+
"lorem": "test",
81+
"ipsum": "1"
82+
}
83+
"""
84+
Then the response status code should be 201
85+
And the response should be empty
86+
87+
@createSchema
88+
Scenario: Create and update a DummyInputOutput
89+
When I add "Content-Type" header equal to "application/ld+json"
90+
And I send a "POST" request to "/dummy_dto_input_outputs" with body:
91+
"""
92+
{
93+
"foo": "test",
94+
"bar": 1
95+
}
96+
"""
97+
Then the response status code should be 201
98+
And the JSON should be equal to:
99+
"""
100+
{
101+
"baz": 1,
102+
"bat": "test"
103+
}
104+
"""
105+
Then I add "Content-Type" header equal to "application/ld+json"
106+
And I send a "PUT" request to "/dummy_dto_input_outputs/1str" with body:
107+
"""
108+
{
109+
"foo": "test",
110+
"bar": 2
111+
}
112+
"""
113+
Then the response status code should be 200
114+
And the JSON should be equal to:
115+
"""
116+
{
117+
"baz": 2,
118+
"bat": "test"
119+
}
120+
"""

src/EventListener/DeserializeListener.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function onKernelRequest(GetResponseEvent $event)
7878
}
7979

8080
$context = $this->serializerContextBuilder->createFromRequest($request, false, $attributes);
81-
if (false === $context['input_class']) {
81+
if (isset($context['input_class']) && false === $context['input_class']) {
8282
return;
8383
}
8484

@@ -97,7 +97,7 @@ public function onKernelRequest(GetResponseEvent $event)
9797
$request->attributes->set(
9898
'data',
9999
$this->serializer->deserialize(
100-
$requestContent, $context['input_class'], $format, $context
100+
$requestContent, $context['resource_class'], $format, $context
101101
)
102102
);
103103
}

src/EventListener/SerializeListener.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,10 @@ public function onKernelView(GetResponseForControllerResultEvent $event)
6262
$request->attributes->set('_api_respond', true);
6363
$context = $this->serializerContextBuilder->createFromRequest($request, true, $attributes);
6464

65-
if (isset($context['output_class'])) {
66-
if (false === $context['output_class']) {
67-
// If the output class is explicitly set to false, the response must be empty
68-
$event->setControllerResult('');
65+
if (isset($context['output_class']) && false === $context['output_class']) {
66+
$event->setControllerResult('');
6967

70-
return;
71-
}
72-
73-
$context['resource_class'] = $context['output_class'];
68+
return;
7469
}
7570

7671
if ($included = $request->attributes->get('_api_included')) {

src/Hydra/Serializer/CollectionFiltersNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function normalize($object, $format = null, array $context = [])
7878
try {
7979
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true);
8080
} catch (InvalidArgumentException $e) {
81-
if (!isset($context['resource_class'])) {
81+
if (!isset($context['resource_class']) || isset($context['output_class'])) {
8282
return $data;
8383
}
8484

src/Hydra/Serializer/CollectionNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public function normalize($object, $format = null, array $context = [])
7272
try {
7373
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true);
7474
} catch (InvalidArgumentException $e) {
75-
if (!isset($context['resource_class'])) {
75+
if (!isset($context['resource_class']) || isset($context['output_class'])) {
7676
return $this->normalizeRawCollection($object, $format, $context);
7777
}
7878

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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\Core\Tests\Fixtures\TestBundle\Dto;
15+
16+
class CustomInputDto
17+
{
18+
/**
19+
* @var string
20+
*/
21+
public $foo;
22+
23+
/**
24+
* @var int
25+
*/
26+
public $bar;
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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\Core\Tests\Fixtures\TestBundle\Dto;
15+
16+
class CustomOutputDto
17+
{
18+
/**
19+
* @var string
20+
*/
21+
public $foo;
22+
23+
/**
24+
* @var int
25+
*/
26+
public $bar;
27+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\CustomInputDto;
18+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\CustomOutputDto;
19+
use Doctrine\ORM\Mapping as ORM;
20+
21+
/**
22+
* DummyDtoCustom.
23+
*
24+
* @ORM\Entity
25+
*
26+
* @ApiResource(
27+
* collectionOperations={"post"={"input_class"=CustomInputDto::class}, "get", "custom_output"={"output_class"=CustomOutputDto::class, "path"="dummy_dto_custom_output", "method"="GET"}, "post_without_output"={"output_class"=false, "method"="POST", "path"="dummy_dto_custom_post_without_output"}},
28+
* itemOperations={"get", "custom_output"={"output_class"=CustomOutputDto::class, "method"="GET", "path"="dummy_dto_custom_output/{id}"}, "put", "delete"}
29+
* )
30+
*/
31+
class DummyDtoCustom
32+
{
33+
/**
34+
* @var int The id
35+
*
36+
* @ORM\Column(type="integer")
37+
* @ORM\Id
38+
* @ORM\GeneratedValue(strategy="AUTO")
39+
*/
40+
private $id;
41+
42+
/**
43+
* @var string
44+
*
45+
* @ORM\Column
46+
*/
47+
public $lorem;
48+
49+
/**
50+
* @var string
51+
*
52+
* @ORM\Column
53+
*/
54+
public $ipsum;
55+
56+
public function getId()
57+
{
58+
return $this->id;
59+
}
60+
}

tests/Fixtures/TestBundle/Entity/DummyOutput.php renamed to tests/Fixtures/TestBundle/Entity/DummyDtoInputOutput.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,38 @@
1515

1616
use ApiPlatform\Core\Annotation\ApiProperty;
1717
use ApiPlatform\Core\Annotation\ApiResource;
18+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\InputDto;
19+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\OutputDto;
1820
use Doctrine\ORM\Mapping as ORM;
1921

2022
/**
21-
* Dummy Output.
23+
* Dummy InputOutput.
2224
*
2325
* @author Kévin Dunglas <dunglas@gmail.com>
2426
*
25-
* @ApiResource
27+
* @ApiResource(attributes={"input_class"=InputDto::class, "output_class"=OutputDto::class})
2628
* @ORM\Entity
2729
*/
28-
class DummyOutput
30+
class DummyDtoInputOutput
2931
{
3032
/**
3133
* @var int The id
32-
*
34+
* @ApiProperty(identifier=true)
3335
* @ORM\Column(type="integer")
3436
* @ORM\Id
3537
* @ORM\GeneratedValue(strategy="AUTO")
3638
*/
3739
public $id;
3840

3941
/**
40-
* @var string The dummy name
41-
*
42-
* @ORM\Column
43-
* @ApiProperty(iri="http://schema.org/name")
42+
* @var int The id
43+
* @ORM\Column(type="string")
44+
*/
45+
public $str;
46+
47+
/**
48+
* @var int The id
49+
* @ORM\Column(type="decimal")
4450
*/
45-
public $name;
51+
public $num;
4652
}

0 commit comments

Comments
 (0)