Skip to content

Commit

Permalink
[jsonapi] Correct normalization of output class and non-resource
Browse files Browse the repository at this point in the history
  • Loading branch information
teohhanhui committed Mar 19, 2019
1 parent 5b7738e commit 3b69049
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 51 deletions.
10 changes: 5 additions & 5 deletions features/bootstrap/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -1185,17 +1185,17 @@ public function thereIsAMaxDepthDummyWithLevelOfDescendants(int $level)
}

/**
* @Given there is a DummyCustomDto
* @Given there is a DummyDtoCustom
*/
public function thereIsADummyCustomDto()
public function thereIsADummyDtoCustom()
{
$this->thereAreNbDummyCustomDto(1);
$this->thereAreNbDummyDtoCustom(1);
}

/**
* @Given there are :nb DummyCustomDto
* @Given there are :nb DummyDtoCustom
*/
public function thereAreNbDummyCustomDto($nb)
public function thereAreNbDummyDtoCustom($nb)
{
for ($i = 0; $i < $nb; ++$i) {
$dto = $this->isOrm() ? new DummyDtoCustom() : new DummyDtoCustomDocument();
Expand Down
64 changes: 64 additions & 0 deletions features/jsonapi/input_output.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
Feature: JSON API DTO input and output
In order to use a hypermedia API
As a client software developer
I need to be able to use DTOs on my resources as Input or Output objects.

Background:
Given I add "Accept" header equal to "application/vnd.api+json"
And I add "Content-Type" header equal to "application/vnd.api+json"

@createSchema
Scenario: Get an item with a custom output
Given there is a DummyDtoCustom
When I send a "GET" request to "/dummy_dto_custom_output/1"
Then print last JSON response
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/vnd.api+json; charset=utf-8"
And the JSON should be valid according to the JSON API schema
And the JSON should be a superset of:
"""
{
"data": {
"id": "/dummy_dto_customs/1",
"type": "DummyDtoCustom",
"attributes": {
"foo": "test",
"bar": 1
}
}
}
"""

@createSchema
Scenario: Get a collection with a custom output
Given there are 2 DummyDtoCustom
When I send a "GET" request to "/dummy_dto_custom_output"
Then print last JSON response
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/vnd.api+json; charset=utf-8"
And the JSON should be valid according to the JSON API schema
And the JSON should be a superset of:
"""
{
"data": [
{
"id": "/dummy_dto_customs/1",
"type": "DummyDtoCustom",
"attributes": {
"foo": "test",
"bar": 1
}
},
{
"id": "/dummy_dto_customs/2",
"type": "DummyDtoCustom",
"attributes": {
"foo": "test",
"bar": 2
}
}
]
}
"""
30 changes: 3 additions & 27 deletions features/main/input_output.feature
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Feature: DTO input and output

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

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

@createSchema
Scenario: Create a DummyCustomDto object without output
Scenario: Create a DummyDtoCustom object without output
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/dummy_dto_custom_post_without_output" with body:
"""
Expand Down Expand Up @@ -192,30 +192,6 @@ Feature: DTO input and output
}
"""

@!mongodb
@createSchema
Scenario: Create a resource that has a non-resource DTO relation.
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/non_relation_resources" with body:
"""
{"relation": {"foo": "test"}}
"""
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/NonRelationResource",
"@id": "/non_relation_resources/1",
"@type": "NonRelationResource",
"relation": {
"foo": "test"
},
"id": 1
}
"""

@createSchema
Scenario: Retrieve an Output with GraphQl
When I add "Content-Type" header equal to "application/ld+json"
Expand Down
24 changes: 24 additions & 0 deletions features/main/non_resource.feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,27 @@ Feature: Non-resources handling
}
}
"""

@!mongodb
@createSchema
Scenario: Create a resource that has a non-resource relation.
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/non_relation_resources" with body:
"""
{"relation": {"foo": "test"}}
"""
Then the response status code should be 201
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
And the JSON should be equal to:
"""
{
"@context": "/contexts/NonRelationResource",
"@id": "/non_relation_resources/1",
"@type": "NonRelationResource",
"relation": {
"foo": "test"
},
"id": 1
}
"""
62 changes: 44 additions & 18 deletions src/JsonApi/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,51 +64,77 @@ public function supportsNormalization($data, $format = null, array $context = []
*/
public function normalize($object, $format = null, array $context = [])
{
if ($this->handleNonResource || null !== $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) {
if (!$this->handleNonResource && null !== $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) {
return parent::normalize($object, $format, $context);
}

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

// Get and populate attributes data
$objectAttributesData = parent::normalize($object, $format, $context);
$attributesData = parent::normalize($object, $format, $context);
if (!\is_array($attributesData)) {
return $attributesData;
}

if ($this->handleNonResource) {
if (isset($context['api_attribute'])) {
return $attributesData;
}

if (!\is_array($objectAttributesData)) {
return $objectAttributesData;
if (isset($context['api_resource'])) {
$resourceClass = $this->resourceClassResolver->getResourceClass($context['api_resource'], $context['resource_class'] ?? null, true);
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);

$resourceData = [
'id' => $this->iriConverter->getIriFromItem($context['api_resource']),
'type' => $resourceMetadata->getShortName(),
];
} else {
$resourceData = [
'id' => \function_exists('spl_object_id') ? spl_object_id($object) : spl_object_hash($object),
'type' => (new \ReflectionClass($this->getObjectClass($object)))->getShortName(),
];
}

if ($attributesData) {
$resourceData['attributes'] = $attributesData;
}

$document = ['data' => $resourceData];

return $document;
}

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

// Get and populate relations
$components = $this->getComponents($object, $format, $context);
$allRelationshipsData = $this->getComponents($object, $format, $context)['relationships'];
$populatedRelationContext = $context;
$objectRelationshipsData = $this->getPopulatedRelations($object, $format, $populatedRelationContext, $components['relationships']);
$objectRelatedResources = $this->getRelatedResources($object, $format, $context, $components['relationships']);
$relationshipsData = $this->getPopulatedRelations($object, $format, $populatedRelationContext, $allRelationshipsData);
$includedResourcesData = $this->getRelatedResources($object, $format, $context, $allRelationshipsData);

$item = [
$resourceData = [
'id' => $this->iriConverter->getIriFromItem($object),
'type' => $resourceMetadata->getShortName(),
];

if ($objectAttributesData) {
$item['attributes'] = $objectAttributesData;
if ($attributesData) {
$resourceData['attributes'] = $attributesData;
}

if ($objectRelationshipsData) {
$item['relationships'] = $objectRelationshipsData;
if ($relationshipsData) {
$resourceData['relationships'] = $relationshipsData;
}

$data = ['data' => $item];
$document = ['data' => $resourceData];

if ($objectRelatedResources) {
$data['included'] = $objectRelatedResources;
if ($includedResourcesData) {
$document['included'] = $includedResourcesData;
}

return $data;
return $document;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Serializer/AbstractItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ public function normalize($object, $format = null, array $context = [])
}

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

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

try {
Expand Down

0 comments on commit 3b69049

Please sign in to comment.