Skip to content

Commit ae5ab60

Browse files
committed
Example input/output
1 parent 4f9139d commit ae5ab60

File tree

7 files changed

+419
-10
lines changed

7 files changed

+419
-10
lines changed

features/main/input_output.feature

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
@!mongodb
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 a superset of:
40+
"""
41+
{
42+
"@context": {
43+
"@vocab": "http://example.com/docs.jsonld#",
44+
"hydra": "http://www.w3.org/ns/hydra/core#",
45+
"foo": {
46+
"@type": "@id"
47+
},
48+
"bar": {
49+
"@type": "@id"
50+
}
51+
},
52+
"@type": "CustomOutputDto",
53+
"foo": "test",
54+
"bar": 0
55+
}
56+
"""
57+
58+
@createSchema
59+
Scenario: Get a collection with a custom output
60+
Given there are 2 DummyCustomDto
61+
When I send a "GET" request to "/dummy_dto_custom_output"
62+
Then the response status code should be 200
63+
And the response should be in JSON
64+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
65+
And the JSON should be a superset of:
66+
"""
67+
{
68+
"@context": "/contexts/DummyDtoCustom",
69+
"@id": "/dummy_dto_customs",
70+
"@type": "hydra:Collection",
71+
"hydra:member": [
72+
{
73+
"foo": "test",
74+
"bar": 1
75+
},
76+
{
77+
"foo": "test",
78+
"bar": 2
79+
}
80+
],
81+
"hydra:totalItems": 2
82+
}
83+
"""
84+
85+
@createSchema
86+
Scenario: Create a DummyCustomDto object without output
87+
When I add "Content-Type" header equal to "application/ld+json"
88+
And I send a "POST" request to "/dummy_dto_custom_post_without_output" with body:
89+
"""
90+
{
91+
"lorem": "test",
92+
"ipsum": "1"
93+
}
94+
"""
95+
Then the response status code should be 201
96+
And the response should be empty
97+
98+
@createSchema
99+
Scenario: Create and update a DummyInputOutput
100+
When I add "Content-Type" header equal to "application/ld+json"
101+
And I send a "POST" request to "/dummy_dto_input_outputs" with body:
102+
"""
103+
{
104+
"foo": "test",
105+
"bar": 1
106+
}
107+
"""
108+
Then the response status code should be 201
109+
And the JSON should be a superset of:
110+
"""
111+
{
112+
"@context": {
113+
"@vocab": "http://example.com/docs.jsonld#",
114+
"hydra": "http://www.w3.org/ns/hydra/core#",
115+
"baz": {
116+
"@type": "@id"
117+
},
118+
"bat": {
119+
"@type": "@id"
120+
}
121+
},
122+
"@type": "OutputDto",
123+
"baz": 1,
124+
"bat": "test"
125+
}
126+
"""
127+
Then I add "Content-Type" header equal to "application/ld+json"
128+
And I send a "PUT" request to "/dummy_dto_input_outputs/1" with body:
129+
"""
130+
{
131+
"foo": "test",
132+
"bar": 2
133+
}
134+
"""
135+
Then the response status code should be 200
136+
And the JSON should be a superset of:
137+
"""
138+
{
139+
"@context": {
140+
"@vocab": "http:\/\/example.com\/docs.jsonld#",
141+
"hydra": "http:\/\/www.w3.org\/ns\/hydra\/core#",
142+
"baz": {
143+
"@type": "@id"
144+
},
145+
"bat": {
146+
"@type": "@id"
147+
}
148+
},
149+
"@type": "OutputDto",
150+
"baz": 2,
151+
"bat": "test"
152+
}
153+
"""
154+
155+
@createSchema
156+
Scenario: Use DTO with relations on User
157+
When I add "Content-Type" header equal to "application/ld+json"
158+
And I send a "POST" request to "/users" with body:
159+
"""
160+
{
161+
"username": "soyuka",
162+
"plainPassword": "a real password",
163+
"email": "[email protected]"
164+
}
165+
"""
166+
Then the response status code should be 201
167+
Then I add "Content-Type" header equal to "application/ld+json"
168+
And I send a "PUT" request to "/users/recover/1" with body:
169+
"""
170+
{
171+
"user": "/users/1"
172+
}
173+
"""
174+
Then the response status code should be 200
175+
And the JSON should be a superset of:
176+
"""
177+
{
178+
"@context": {
179+
"@vocab": "http://example.com/docs.jsonld#",
180+
"hydra": "http://www.w3.org/ns/hydra/core#",
181+
"user": {
182+
"@type": "@id"
183+
}
184+
},
185+
"@type": "RecoverPasswordOutput",
186+
"user": {
187+
"@id": "/users/1",
188+
"@type": "User",
189+
"email": "[email protected]",
190+
"fullname": null,
191+
"username": "soyuka"
192+
}
193+
}
194+
"""
195+
196+
# @createSchema
197+
# Scenario: Execute a GraphQL query on DTO
198+
# Given there are 2 DummyCustomDto
199+
# When I send the following GraphQL request:
200+
# """
201+
# {
202+
# dummyDtoCustom(id: "/dummy_dto_customs/1") {
203+
# lorem
204+
# ipsum
205+
# }
206+
# }
207+
# """
208+
# Then the response status code should be 200
209+
# And the response should be in JSON
210+
# And the header "Content-Type" should be equal to "application/json"
211+
# Then print last JSON response
212+
# And the JSON node "data.dummy.id" should be equal to "/dummies/1"
213+
# And the JSON node "data.dummy.name" should be equal to "Dummy #1"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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+
use Symfony\Component\Serializer\Annotation\Groups;
17+
18+
class RecoverPasswordInput
19+
{
20+
/**
21+
* @var \ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\User
22+
* @Groups({"user"})
23+
*/
24+
public $user;
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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+
use Symfony\Component\Serializer\Annotation\Groups;
17+
18+
class RecoverPasswordOutput
19+
{
20+
/**
21+
* @var \ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\User
22+
* @Groups({"user"})
23+
*/
24+
public $user;
25+
}

tests/Fixtures/TestBundle/Entity/User.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RecoverPasswordInput;
18+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RecoverPasswordOutput;
1719
use Doctrine\ORM\Mapping as ORM;
1820
use FOS\UserBundle\Model\User as BaseUser;
1921
use FOS\UserBundle\Model\UserInterface;
@@ -24,10 +26,17 @@
2426
*
2527
* @ORM\Entity
2628
* @ORM\Table(name="user_test")
27-
* @ApiResource(attributes={
28-
* "normalization_context"={"groups"={"user", "user-read"}},
29-
* "denormalization_context"={"groups"={"user", "user-write"}}
30-
* })
29+
* @ApiResource(
30+
* attributes={
31+
* "normalization_context"={"groups"={"user", "user-read"}},
32+
* "denormalization_context"={"groups"={"user", "user-write"}}
33+
* },
34+
* itemOperations={"get", "put", "delete",
35+
* "recover_password"={
36+
* "input"=RecoverPasswordInput::class, "output"=RecoverPasswordOutput::class, "method"="PUT", "path"="users/recover/{id}"
37+
* }
38+
* }
39+
* )
3140
*
3241
* @author Théo FIDRY <[email protected]>
3342
* @author Kévin Dunglas <[email protected]>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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\Serializer\Denormalizer;
15+
16+
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
17+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RecoverPasswordInput;
18+
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
19+
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
20+
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
21+
22+
class RecoverPasswordDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface
23+
{
24+
use DenormalizerAwareTrait;
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function denormalize($data, $class, $format = null, array $context = [])
30+
{
31+
$context['input_denormalized'] = true;
32+
// The RecoverPasswordInput has a user relation that gets denormalized
33+
/**
34+
* @var \ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RecoverPasswordInput
35+
*/
36+
$inputDto = $this->denormalizer->denormalize($data, $context['input']['class'], $format, $context);
37+
$inputDto->user->getEmail();
38+
39+
// Because we're in a PUT operation, we will use the retrieved object...
40+
$resourceObject = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE] ?? new $class();
41+
// ...where we remove the credentials
42+
$resourceObject->eraseCredentials();
43+
44+
return $resourceObject;
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
51+
{
52+
if ($context['input_denormalized'] ?? false) {
53+
return false;
54+
}
55+
56+
return RecoverPasswordInput::class === ($context['input']['class'] ?? null);
57+
}
58+
}

0 commit comments

Comments
 (0)