Skip to content

Commit ec28e96

Browse files
committed
Normalize non-resource objects in a standalone normalizer
1 parent 1a7b783 commit ec28e96

File tree

28 files changed

+487
-376
lines changed

28 files changed

+487
-376
lines changed

features/bootstrap/JsonContext.php

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,6 @@ public function __construct(HttpCallResultPool $httpCallResultPool)
2424
parent::__construct($httpCallResultPool);
2525
}
2626

27-
private function sortArrays($obj)
28-
{
29-
$isObject = is_object($obj);
30-
31-
foreach ($obj as $key => $value) {
32-
if (null === $value || is_scalar($value)) {
33-
continue;
34-
}
35-
36-
if (is_array($value)) {
37-
sort($value);
38-
}
39-
40-
$value = $this->sortArrays($value);
41-
42-
$isObject ? $obj->{$key} = $value : $obj[$key] = $value;
43-
}
44-
45-
return $obj;
46-
}
47-
4827
/**
4928
* @Then /^the JSON should be deep equal to:$/
5029
*/
@@ -75,4 +54,25 @@ public function theJsonIsASupersetOf(PyStringNode $content)
7554
$actual = json_decode($this->httpCallResultPool->getResult()->getValue(), true);
7655
Assert::assertArraySubset(json_decode($content->getRaw(), true), $actual);
7756
}
57+
58+
private function sortArrays($obj)
59+
{
60+
$isObject = is_object($obj);
61+
62+
foreach ($obj as $key => $value) {
63+
if (null === $value || is_scalar($value)) {
64+
continue;
65+
}
66+
67+
if (is_array($value)) {
68+
sort($value);
69+
}
70+
71+
$value = $this->sortArrays($value);
72+
73+
$isObject ? $obj->{$key} = $value : $obj[$key] = $value;
74+
}
75+
76+
return $obj;
77+
}
7878
}

features/jsonapi/related-resouces-inclusion.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,11 +363,11 @@ Feature: JSON API Inclusion of Related Resources
363363
"dummyDate": null,
364364
"dummyBoolean": null,
365365
"embeddedDummy": {
366+
"dummyName": null,
366367
"dummyBoolean": null,
367368
"dummyDate": null,
368369
"dummyFloat": null,
369370
"dummyPrice": null,
370-
"dummyName": null,
371371
"symfony": null
372372
},
373373
"_id": 1,

features/main/input_output.feature

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,11 @@ Feature: DTO input and output
179179
},
180180
"@type": "User",
181181
"@id": "/users/1",
182-
"dummy": "/dummies/1"
182+
"dummy": {
183+
"@context": "/contexts/Dummy",
184+
"@id": "/dummies/1",
185+
"@type": "Dummy"
186+
}
183187
}
184188
"""
185189

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -107,30 +107,6 @@
107107
<tag name="serializer.normalizer" priority="-923" />
108108
</service>
109109

110-
<service id="api_platform.serializer.normalizer.item.non_resource" class="ApiPlatform\Core\Serializer\ItemNormalizer" public="false">
111-
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
112-
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
113-
<argument type="service" id="api_platform.iri_converter" />
114-
<argument type="service" id="api_platform.resource_class_resolver" />
115-
<argument type="service" id="api_platform.property_accessor" />
116-
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
117-
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
118-
<argument type="service" id="api_platform.item_data_provider" on-invalid="ignore" />
119-
<argument>%api_platform.allow_plain_identifiers%</argument>
120-
<argument>null</argument>
121-
<argument type="tagged" tag="api_platform.data_transformer" on-invalid="ignore" />
122-
<argument type="service" id="api_platform.metadata.resource.metadata_factory" on-invalid="ignore" />
123-
<argument>true</argument>
124-
125-
<!-- After the DataUriNormalizer but before the ObjectNormalizer -->
126-
<tag name="serializer.normalizer" priority="-925" />
127-
</service>
128-
129-
<service id="api_platform.serializer.normalizer.no_op_scalar" class="ApiPlatform\Core\Serializer\NoOpScalarNormalizer" public="false">
130-
<!-- Run before our non-cacheable normalizers -->
131-
<tag name="serializer.normalizer" priority="-900" />
132-
</service>
133-
134110
<!-- Resources Operations path resolver -->
135111

136112
<service id="api_platform.operation_path_resolver" alias="api_platform.operation_path_resolver.router" public="false" />

src/Bridge/Symfony/Bundle/Resources/config/graphql.xml

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,9 @@
8686
<tag name="serializer.normalizer" priority="-922" />
8787
</service>
8888

89-
<service id="api_platform.graphql.normalizer.item.non_resource" class="ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer" public="false">
90-
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
91-
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
89+
<service id="api_platform.graphql.normalizer.non_resource_object" class="ApiPlatform\Core\GraphQl\Serializer\NonResourceObjectNormalizer" public="false">
90+
<argument type="service" id="serializer.normalizer.object" />
9291
<argument type="service" id="api_platform.iri_converter" />
93-
<argument type="service" id="api_platform.resource_class_resolver" />
94-
<argument type="service" id="api_platform.property_accessor" />
95-
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
96-
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
97-
<argument type="service" id="api_platform.item_data_provider" on-invalid="ignore" />
98-
<argument>%api_platform.allow_plain_identifiers%</argument>
99-
<argument>null</argument>
100-
<argument type="tagged" tag="api_platform.data_transformer" on-invalid="ignore" />
101-
<argument type="service" id="api_platform.metadata.resource.metadata_factory" on-invalid="ignore" />
102-
<argument>true</argument>
10392

10493
<!-- After the DataUriNormalizer but before the ObjectNormalizer -->
10594
<tag name="serializer.normalizer" priority="-924" />

src/Bridge/Symfony/Bundle/Resources/config/hal.xml

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,9 @@
4545
<tag name="serializer.normalizer" priority="-922" />
4646
</service>
4747

48-
<service id="api_platform.hal.normalizer.item.non_resource" class="ApiPlatform\Core\Hal\Serializer\ItemNormalizer" public="false">
49-
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
50-
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
48+
<service id="api_platform.hal.normalizer.non_resource_object" class="ApiPlatform\Core\Hal\Serializer\NonResourceObjectNormalizer" public="false">
49+
<argument type="service" id="serializer.normalizer.object" />
5150
<argument type="service" id="api_platform.iri_converter" />
52-
<argument type="service" id="api_platform.resource_class_resolver" />
53-
<argument type="service" id="api_platform.property_accessor" />
54-
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
55-
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
56-
<argument>null</argument>
57-
<argument>false</argument>
58-
<argument type="collection" />
59-
<argument type="tagged" tag="api_platform.data_transformer" on-invalid="ignore" />
60-
<argument type="service" id="api_platform.metadata.resource.metadata_factory" on-invalid="ignore" />
61-
<argument>true</argument>
6251

6352
<!-- After the DataUriNormalizer but before the ObjectNormalizer -->
6453
<tag name="serializer.normalizer" priority="-924" />

src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,11 @@
4747
<tag name="serializer.normalizer" priority="-922" />
4848
</service>
4949

50-
<service id="api_platform.jsonapi.normalizer.item.non_resource" class="ApiPlatform\Core\JsonApi\Serializer\ItemNormalizer" public="false">
51-
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
52-
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
50+
<service id="api_platform.jsonapi.normalizer.non_resource_object" class="ApiPlatform\Core\JsonApi\Serializer\NonResourceObjectNormalizer" public="false">
51+
<argument type="service" id="serializer.normalizer.object" />
5352
<argument type="service" id="api_platform.iri_converter" />
5453
<argument type="service" id="api_platform.resource_class_resolver" />
55-
<argument type="service" id="api_platform.property_accessor" />
56-
<argument type="service" id="api_platform.jsonapi.name_converter.reserved_attribute_name" />
5754
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
58-
<argument type="collection" />
59-
<argument type="tagged" tag="api_platform.data_transformer" on-invalid="ignore" />
60-
<argument>true</argument>
6155

6256
<!-- After the DataUriNormalizer but before the ObjectNormalizer -->
6357
<tag name="serializer.normalizer" priority="-924" />

src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,10 @@
3333
<tag name="serializer.normalizer" priority="-922" />
3434
</service>
3535

36-
<service id="api_platform.jsonld.normalizer.item.non_resource" class="ApiPlatform\Core\JsonLd\Serializer\ItemNormalizer" public="false">
37-
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
38-
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
39-
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
36+
<service id="api_platform.jsonld.normalizer.non_resource_object" class="ApiPlatform\Core\JsonLd\Serializer\NonResourceObjectNormalizer" public="false">
37+
<argument type="service" id="serializer.normalizer.object" />
4038
<argument type="service" id="api_platform.iri_converter" />
41-
<argument type="service" id="api_platform.resource_class_resolver" />
4239
<argument type="service" id="api_platform.jsonld.context_builder" />
43-
<argument type="service" id="api_platform.property_accessor" />
44-
<argument type="service" id="api_platform.name_converter" on-invalid="ignore" />
45-
<argument type="service" id="serializer.mapping.class_metadata_factory" on-invalid="ignore" />
46-
<argument type="collection" />
47-
<argument type="tagged" tag="api_platform.data_transformer" on-invalid="ignore" />
48-
<argument>true</argument>
4940

5041
<!-- After the DataUriNormalizer but before the ObjectNormalizer -->
5142
<tag name="serializer.normalizer" priority="-924" />

src/GraphQl/Serializer/ItemNormalizer.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ final class ItemNormalizer extends BaseItemNormalizer
3333
/**
3434
* {@inheritdoc}
3535
*/
36-
public function supportsNormalization($data, $format = null, array $context = [])
36+
public function supportsNormalization($data, $format = null, array $context = []): bool
3737
{
3838
return self::FORMAT === $format && parent::supportsNormalization($data, $format, $context);
3939
}
@@ -45,7 +45,7 @@ public function supportsNormalization($data, $format = null, array $context = []
4545
*/
4646
public function normalize($object, $format = null, array $context = [])
4747
{
48-
if (!$this->handleNonResource && null !== $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) {
48+
if (null !== $outputClass = $this->getOutputClass($this->getObjectClass($object), $context)) {
4949
return parent::normalize($object, $format, $context);
5050
}
5151

@@ -54,15 +54,6 @@ public function normalize($object, $format = null, array $context = [])
5454
throw new UnexpectedValueException('Expected data to be an array');
5555
}
5656

57-
if ($this->handleNonResource) {
58-
// when using an output class, get the IRI from the resource
59-
if (isset($context['api_resource']) && isset($data['id'])) {
60-
$data['_id'] = $data['id'];
61-
$data['id'] = $this->iriConverter->getIriFromItem($context['api_resource']);
62-
unset($context['api_resource']);
63-
}
64-
}
65-
6657
$data[self::ITEM_KEY] = serialize($object); // calling serialize prevent weird normalization process done by Webonyx's GraphQL PHP
6758

6859
return $data;
@@ -80,7 +71,7 @@ protected function normalizeCollectionOfRelations(PropertyMetadata $propertyMeta
8071
/**
8172
* {@inheritdoc}
8273
*/
83-
public function supportsDenormalization($data, $type, $format = null, array $context = [])
74+
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
8475
{
8576
return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format, $context);
8677
}
@@ -103,7 +94,7 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu
10394
/**
10495
* {@inheritdoc}
10596
*/
106-
protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = [])
97+
protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = []): void
10798
{
10899
if ('_id' === $attribute) {
109100
$attribute = 'id';
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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\GraphQl\Serializer;
15+
16+
use ApiPlatform\Core\Api\IriConverterInterface;
17+
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
18+
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
19+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
20+
21+
/**
22+
* Normalizes non-resource objects.
23+
*
24+
* It decorates the output with GraphQL metadata when appropriate, but otherwise
25+
* just passes through to the decorated normalizer.
26+
*/
27+
final class NonResourceObjectNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface
28+
{
29+
public const FORMAT = 'graphql';
30+
public const ITEM_KEY = '#item';
31+
32+
private $decorated;
33+
private $iriConverter;
34+
35+
public function __construct(NormalizerInterface $decorated, IriConverterInterface $iriConverter)
36+
{
37+
$this->decorated = $decorated;
38+
$this->iriConverter = $iriConverter;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function supportsNormalization($data, $format = null, array $context = []): bool
45+
{
46+
return self::FORMAT === $format && $this->decorated->supportsNormalization($data, $format, $context);
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function hasCacheableSupportsMethod(): bool
53+
{
54+
return $this->decorated->hasCacheableSupportsMethod();
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*
60+
* @throws UnexpectedValueException
61+
*/
62+
public function normalize($object, $format = null, array $context = [])
63+
{
64+
if (isset($context['api_resource'])) {
65+
$originalResource = $context['api_resource'];
66+
unset($context['api_resource']);
67+
}
68+
69+
$data = $this->decorated->normalize($object, $format, $context);
70+
if (!\is_array($data)) {
71+
throw new UnexpectedValueException('Expected data to be an array');
72+
}
73+
74+
// when using an output class, get the IRI from the resource
75+
if (isset($originalResource) && isset($data['id'])) {
76+
$data['_id'] = $data['id'];
77+
$data['id'] = $this->iriConverter->getIriFromItem($originalResource);
78+
}
79+
80+
$data[self::ITEM_KEY] = serialize($object); // calling serialize prevent weird normalization process done by Webonyx's GraphQL PHP
81+
82+
return $data;
83+
}
84+
}

0 commit comments

Comments
 (0)