From 3b69049af439079d39fbc97ea7086c9c8cb2285d Mon Sep 17 00:00:00 2001 From: Teoh Han Hui Date: Mon, 18 Mar 2019 17:38:11 +0100 Subject: [PATCH] [jsonapi] Correct normalization of output class and non-resource --- features/bootstrap/DoctrineContext.php | 10 ++-- features/jsonapi/input_output.feature | 64 +++++++++++++++++++++++ features/main/input_output.feature | 30 ++--------- features/main/non_resource.feature | 24 +++++++++ src/JsonApi/Serializer/ItemNormalizer.php | 62 +++++++++++++++------- src/Serializer/AbstractItemNormalizer.php | 2 +- 6 files changed, 141 insertions(+), 51 deletions(-) create mode 100644 features/jsonapi/input_output.feature diff --git a/features/bootstrap/DoctrineContext.php b/features/bootstrap/DoctrineContext.php index 43eb85993ea..528088023fa 100644 --- a/features/bootstrap/DoctrineContext.php +++ b/features/bootstrap/DoctrineContext.php @@ -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(); diff --git a/features/jsonapi/input_output.feature b/features/jsonapi/input_output.feature new file mode 100644 index 00000000000..960ec873d4b --- /dev/null +++ b/features/jsonapi/input_output.feature @@ -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 + } + } + ] + } + """ diff --git a/features/main/input_output.feature b/features/main/input_output.feature index 5dd83239559..771ebecf434 100644 --- a/features/main/input_output.feature +++ b/features/main/input_output.feature @@ -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 @@ -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 @@ -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: """ @@ -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" diff --git a/features/main/non_resource.feature b/features/main/non_resource.feature index 0cbe67ed308..97751d396ac 100644 --- a/features/main/non_resource.feature +++ b/features/main/non_resource.feature @@ -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 + } + """ diff --git a/src/JsonApi/Serializer/ItemNormalizer.php b/src/JsonApi/Serializer/ItemNormalizer.php index 778f8348cd3..1e354c99e87 100644 --- a/src/JsonApi/Serializer/ItemNormalizer.php +++ b/src/JsonApi/Serializer/ItemNormalizer.php @@ -64,7 +64,7 @@ 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); } @@ -72,43 +72,69 @@ public function normalize($object, $format = null, array $context = []) $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; } /** diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index a6ff04af53b..e7483d9a588 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -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); @@ -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 {