Skip to content

Commit 90e9b15

Browse files
committed
[jsonapi] Correct normalization of output class and non-resource
1 parent 5b7738e commit 90e9b15

File tree

6 files changed

+149
-51
lines changed

6 files changed

+149
-51
lines changed

features/bootstrap/DoctrineContext.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,17 +1185,17 @@ public function thereIsAMaxDepthDummyWithLevelOfDescendants(int $level)
11851185
}
11861186

11871187
/**
1188-
* @Given there is a DummyCustomDto
1188+
* @Given there is a DummyDtoCustom
11891189
*/
1190-
public function thereIsADummyCustomDto()
1190+
public function thereIsADummyDtoCustom()
11911191
{
1192-
$this->thereAreNbDummyCustomDto(1);
1192+
$this->thereAreNbDummyDtoCustom(1);
11931193
}
11941194

11951195
/**
1196-
* @Given there are :nb DummyCustomDto
1196+
* @Given there are :nb DummyDtoCustom
11971197
*/
1198-
public function thereAreNbDummyCustomDto($nb)
1198+
public function thereAreNbDummyDtoCustom($nb)
11991199
{
12001200
for ($i = 0; $i < $nb; ++$i) {
12011201
$dto = $this->isOrm() ? new DummyDtoCustom() : new DummyDtoCustomDocument();

features/jsonapi/input_output.feature

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
Feature: JSON API DTO input and output
2+
In order to use a hypermedia API
3+
As a client software developer
4+
I need to be able to use DTOs on my resources as Input or Output objects.
5+
6+
Background:
7+
Given I add "Accept" header equal to "application/vnd.api+json"
8+
And I add "Content-Type" header equal to "application/vnd.api+json"
9+
10+
@createSchema
11+
Scenario: Get an item with a custom output
12+
Given there is a DummyDtoCustom
13+
When I send a "GET" request to "/dummy_dto_custom_output/1"
14+
Then the response status code should be 200
15+
And the response should be in JSON
16+
And the header "Content-Type" should be equal to "application/vnd.api+json; charset=utf-8"
17+
And the JSON should be valid according to the JSON API schema
18+
And the JSON should be a superset of:
19+
"""
20+
{
21+
"data": {
22+
"id": "/dummy_dto_customs/1",
23+
"type": "DummyDtoCustom",
24+
"attributes": {
25+
"foo": "test",
26+
"bar": 1
27+
}
28+
}
29+
}
30+
"""
31+
32+
@createSchema
33+
Scenario: Get a collection with a custom output
34+
Given there are 2 DummyDtoCustom
35+
When I send a "GET" request to "/dummy_dto_custom_output"
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/vnd.api+json; charset=utf-8"
39+
And the JSON should be valid according to the JSON API schema
40+
And the JSON should be a superset of:
41+
"""
42+
{
43+
"data": [
44+
{
45+
"id": "/dummy_dto_customs/1",
46+
"type": "DummyDtoCustom",
47+
"attributes": {
48+
"foo": "test",
49+
"bar": 1
50+
}
51+
},
52+
{
53+
"id": "/dummy_dto_customs/2",
54+
"type": "DummyDtoCustom",
55+
"attributes": {
56+
"foo": "test",
57+
"bar": 2
58+
}
59+
}
60+
]
61+
}
62+
"""

features/main/input_output.feature

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Feature: DTO input and output
3030

3131
@createSchema
3232
Scenario: Get an item with a custom output
33-
Given there is a DummyCustomDto
33+
Given there is a DummyDtoCustom
3434
When I send a "GET" request to "/dummy_dto_custom_output/1"
3535
Then the response status code should be 200
3636
And the response should be in JSON
@@ -59,7 +59,7 @@ Feature: DTO input and output
5959

6060
@createSchema
6161
Scenario: Get a collection with a custom output
62-
Given there are 2 DummyCustomDto
62+
Given there are 2 DummyDtoCustom
6363
When I send a "GET" request to "/dummy_dto_custom_output"
6464
Then the response status code should be 200
6565
And the response should be in JSON
@@ -87,7 +87,7 @@ Feature: DTO input and output
8787
"""
8888

8989
@createSchema
90-
Scenario: Create a DummyCustomDto object without output
90+
Scenario: Create a DummyDtoCustom object without output
9191
When I add "Content-Type" header equal to "application/ld+json"
9292
And I send a "POST" request to "/dummy_dto_custom_post_without_output" with body:
9393
"""
@@ -192,30 +192,6 @@ Feature: DTO input and output
192192
}
193193
"""
194194

195-
@!mongodb
196-
@createSchema
197-
Scenario: Create a resource that has a non-resource DTO relation.
198-
When I add "Content-Type" header equal to "application/ld+json"
199-
And I send a "POST" request to "/non_relation_resources" with body:
200-
"""
201-
{"relation": {"foo": "test"}}
202-
"""
203-
Then the response status code should be 201
204-
And the response should be in JSON
205-
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
206-
And the JSON should be equal to:
207-
"""
208-
{
209-
"@context": "/contexts/NonRelationResource",
210-
"@id": "/non_relation_resources/1",
211-
"@type": "NonRelationResource",
212-
"relation": {
213-
"foo": "test"
214-
},
215-
"id": 1
216-
}
217-
"""
218-
219195
@createSchema
220196
Scenario: Retrieve an Output with GraphQl
221197
When I add "Content-Type" header equal to "application/ld+json"

features/main/non_resource.feature

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,27 @@ Feature: Non-resources handling
2828
}
2929
}
3030
"""
31+
32+
@!mongodb
33+
@createSchema
34+
Scenario: Create a resource that has a non-resource relation.
35+
When I add "Content-Type" header equal to "application/ld+json"
36+
And I send a "POST" request to "/non_relation_resources" with body:
37+
"""
38+
{"relation": {"foo": "test"}}
39+
"""
40+
Then the response status code should be 201
41+
And the response should be in JSON
42+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
43+
And the JSON should be equal to:
44+
"""
45+
{
46+
"@context": "/contexts/NonRelationResource",
47+
"@id": "/non_relation_resources/1",
48+
"@type": "NonRelationResource",
49+
"relation": {
50+
"foo": "test"
51+
},
52+
"id": 1
53+
}
54+
"""

src/JsonApi/Serializer/ItemNormalizer.php

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,51 +64,87 @@ public function supportsNormalization($data, $format = null, array $context = []
6464
*/
6565
public function normalize($object, $format = null, array $context = [])
6666
{
67-
if ($this->handleNonResource || null !== $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) {
67+
if (!$this->handleNonResource && null !== $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) {
6868
return parent::normalize($object, $format, $context);
6969
}
7070

7171
if (!isset($context['cache_key'])) {
7272
$context['cache_key'] = $this->getJsonApiCacheKey($format, $context);
7373
}
7474

75-
// Get and populate attributes data
76-
$objectAttributesData = parent::normalize($object, $format, $context);
75+
if ($this->handleNonResource) {
76+
if (isset($context['api_resource'])) {
77+
$originalResource = $context['api_resource'];
78+
unset($context['api_resource']);
79+
}
80+
81+
$attributesData = parent::normalize($object, $format, $context);
82+
if (!\is_array($attributesData)) {
83+
return $attributesData;
84+
}
85+
86+
if (isset($context['api_attribute'])) {
87+
return $attributesData;
88+
}
89+
90+
if (isset($originalResource)) {
91+
$resourceClass = $this->resourceClassResolver->getResourceClass($originalResource, $context['resource_class'] ?? null, true);
92+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
7793

78-
if (!\is_array($objectAttributesData)) {
79-
return $objectAttributesData;
94+
$resourceData = [
95+
'id' => $this->iriConverter->getIriFromItem($originalResource),
96+
'type' => $resourceMetadata->getShortName(),
97+
];
98+
} else {
99+
$resourceData = [
100+
'id' => \function_exists('spl_object_id') ? spl_object_id($object) : spl_object_hash($object),
101+
'type' => (new \ReflectionClass($this->getObjectClass($object)))->getShortName(),
102+
];
103+
}
104+
105+
if ($attributesData) {
106+
$resourceData['attributes'] = $attributesData;
107+
}
108+
109+
$document = ['data' => $resourceData];
110+
111+
return $document;
112+
}
113+
114+
$attributesData = parent::normalize($object, $format, $context);
115+
if (!\is_array($attributesData)) {
116+
return $attributesData;
80117
}
81118

82-
// Get and populate item type
83119
$resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true);
84120
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
85121

86122
// Get and populate relations
87-
$components = $this->getComponents($object, $format, $context);
123+
$allRelationshipsData = $this->getComponents($object, $format, $context)['relationships'];
88124
$populatedRelationContext = $context;
89-
$objectRelationshipsData = $this->getPopulatedRelations($object, $format, $populatedRelationContext, $components['relationships']);
90-
$objectRelatedResources = $this->getRelatedResources($object, $format, $context, $components['relationships']);
125+
$relationshipsData = $this->getPopulatedRelations($object, $format, $populatedRelationContext, $allRelationshipsData);
126+
$includedResourcesData = $this->getRelatedResources($object, $format, $context, $allRelationshipsData);
91127

92-
$item = [
128+
$resourceData = [
93129
'id' => $this->iriConverter->getIriFromItem($object),
94130
'type' => $resourceMetadata->getShortName(),
95131
];
96132

97-
if ($objectAttributesData) {
98-
$item['attributes'] = $objectAttributesData;
133+
if ($attributesData) {
134+
$resourceData['attributes'] = $attributesData;
99135
}
100136

101-
if ($objectRelationshipsData) {
102-
$item['relationships'] = $objectRelationshipsData;
137+
if ($relationshipsData) {
138+
$resourceData['relationships'] = $relationshipsData;
103139
}
104140

105-
$data = ['data' => $item];
141+
$document = ['data' => $resourceData];
106142

107-
if ($objectRelatedResources) {
108-
$data['included'] = $objectRelatedResources;
143+
if ($includedResourcesData) {
144+
$document['included'] = $includedResourcesData;
109145
}
110146

111-
return $data;
147+
return $document;
112148
}
113149

114150
/**

src/Serializer/AbstractItemNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ public function normalize($object, $format = null, array $context = [])
126126
}
127127

128128
$context['api_normalize'] = true;
129-
$context['resource_class'] = $this->getObjectClass($transformed);
130129
$context['api_resource'] = $object;
131130

132131
return $this->serializer->normalize($transformed, $format, $context);
@@ -527,6 +526,7 @@ protected function createRelationSerializationContext(string $resourceClass, arr
527526
*/
528527
protected function getAttributeValue($object, $attribute, $format = null, array $context = [])
529528
{
529+
$context['api_attribute'] = $attribute;
530530
$propertyMetadata = $this->propertyMetadataFactory->create($context['resource_class'], $attribute, $this->getFactoryOptions($context));
531531

532532
try {

0 commit comments

Comments
 (0)