Skip to content

Commit 01ea141

Browse files
committed
feat: allow to output null links for haljson
Issue: #4372
1 parent e7049cb commit 01ea141

File tree

5 files changed

+83
-3
lines changed

5 files changed

+83
-3
lines changed

src/Hal/Serializer/ItemNormalizer.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,16 @@ private function populateRelation(array $data, object $object, ?string $format,
305305

306306
$attributeValue = $this->getAttributeValue($object, $relation['name'], $format, $childContext);
307307

308-
if (empty($attributeValue)) {
308+
if (empty($attributeValue) && ($context[self::SKIP_NULL_TO_ONE_RELATIONS] ?? $this->defaultContext[self::SKIP_NULL_TO_ONE_RELATIONS] ?? true)) {
309309
continue;
310310
}
311311

312312
if ('one' === $relation['cardinality']) {
313313
if ('links' === $type) {
314-
$data[$key][$relationName]['href'] = $this->getRelationIri($attributeValue);
315-
continue;
314+
if (null !== $attributeValue) {
315+
$data[$key][$relationName]['href'] = $this->getRelationIri($attributeValue);
316+
continue;
317+
}
316318
}
317319

318320
$data[$key][$relationName] = $attributeValue;

src/Serializer/AbstractItemNormalizer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer
6565
use ContextTrait;
6666
use InputOutputMetadataTrait;
6767
use OperationContextTrait;
68+
/**
69+
* Flag to control whether to one relation with the value `null` should be output
70+
* when normalizing or omitted.
71+
*/
72+
public const SKIP_NULL_TO_ONE_RELATIONS = 'skip_null_to_one_relations';
6873

6974
protected PropertyAccessorInterface $propertyAccessor;
7075
protected array $localCache = [];

src/Serializer/SerializerContextBuilder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public function createFromRequest(Request $request, bool $normalization, ?array
6060
$context['operation'] = $operation;
6161
$context['resource_class'] = $attributes['resource_class'];
6262
$context['skip_null_values'] ??= true;
63+
$context[AbstractItemNormalizer::SKIP_NULL_TO_ONE_RELATIONS] ??= true;
6364
$context['iri_only'] ??= false;
6465
$context['request_uri'] = $request->getRequestUri();
6566
$context['uri'] = $request->getUri();

tests/Fixtures/TestBundle/ApiResource/Issue4372/ToOneRelationPropertyMayBeNull.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use ApiPlatform\Metadata\Get;
1919
use ApiPlatform\Metadata\GetCollection;
2020
use ApiPlatform\Metadata\Operation;
21+
use ApiPlatform\Serializer\AbstractItemNormalizer;
2122
use Doctrine\Common\Collections\ArrayCollection;
2223
use Doctrine\Common\Collections\Collection;
2324

@@ -28,6 +29,13 @@
2829
),
2930
new Get(
3031
uriTemplate: self::ITEM_ROUTE.'{._format}',
32+
normalizationContext: [
33+
AbstractItemNormalizer::SKIP_NULL_TO_ONE_RELATIONS => false,
34+
],
35+
provider: [self::class, 'provide']
36+
),
37+
new Get(
38+
uriTemplate: self::ITEM_SKIP_NULL_TO_ONE_RELATION_ROUTE.'{._format}',
3139
provider: [self::class, 'provide']
3240
),
3341
],
@@ -36,6 +44,8 @@ class ToOneRelationPropertyMayBeNull
3644
{
3745
public const ROUTE = '/my-route';
3846
public const ITEM_ROUTE = self::ROUTE.'/{id}';
47+
public const SKIP_NULL_TO_ONE_RELATION_ROUTE = '/skip-null-relation-route';
48+
public const ITEM_SKIP_NULL_TO_ONE_RELATION_ROUTE = self::SKIP_NULL_TO_ONE_RELATION_ROUTE.'/{id}';
3949
public const ENTITY_ID = 1;
4050

4151
/** @noinspection PhpPropertyOnlyWrittenInspection */

tests/Functional/NotSkipNullToOneRelationTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,68 @@ public function testNullRelationsAreNotSkippedWhenConfigured(): void
6666

6767
$this->assertResponseStatusCodeSame(200);
6868

69+
$this->assertJsonEquals(
70+
[
71+
'_links' => [
72+
'self' => [
73+
'href' => $itemIri,
74+
],
75+
'relatedEntity' => null,
76+
'relatedEntity2' => [
77+
'href' => '/related_entities/1',
78+
],
79+
],
80+
'collection' => [
81+
[
82+
'_links' => [
83+
'self' => [
84+
'href' => '/related_entities/1',
85+
],
86+
],
87+
'id' => 1,
88+
],
89+
[
90+
'_links' => [
91+
'self' => [
92+
'href' => '/related_entities/2',
93+
],
94+
],
95+
'id' => 2,
96+
],
97+
],
98+
]
99+
);
100+
}
101+
102+
/**
103+
* @throws TransportExceptionInterface
104+
* @throws ServerExceptionInterface
105+
* @throws RedirectionExceptionInterface
106+
* @throws DecodingExceptionInterface
107+
* @throws ClientExceptionInterface
108+
* @throws \JsonException
109+
*/
110+
public function testNullRelationsAreSkippedByDefault(): void
111+
{
112+
$itemIri = str_replace(
113+
'{id}',
114+
ToOneRelationPropertyMayBeNull::ENTITY_ID.'',
115+
ToOneRelationPropertyMayBeNull::ITEM_SKIP_NULL_TO_ONE_RELATION_ROUTE
116+
);
117+
$this->checkRoutesAreCorrectlySetUp();
118+
119+
self::createClient()->request(
120+
'GET',
121+
$itemIri,
122+
[
123+
'headers' => [
124+
'accept' => 'application/hal+json',
125+
],
126+
]
127+
);
128+
129+
$this->assertResponseStatusCodeSame(200);
130+
69131
$this->assertJsonEquals(
70132
[
71133
'_links' => [

0 commit comments

Comments
 (0)