Skip to content

Commit 6b733b4

Browse files
committed
[Serializer] Add option to skip uninitialized typed properties
1 parent 7c531f5 commit 6b733b4

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
1515
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
16+
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
1617
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1718
use Symfony\Component\PropertyInfo\Type;
1819
use Symfony\Component\Serializer\Encoder\CsvEncoder;
@@ -58,6 +59,12 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
5859
*/
5960
public const SKIP_NULL_VALUES = 'skip_null_values';
6061

62+
/**
63+
* Flag to control whether uninitialized PHP>=7.4 typed class properties
64+
* should be excluded when normalizing.
65+
*/
66+
public const SKIP_UNINITIALIZED_VALUES = 'skip_uninitialized_values';
67+
6168
/**
6269
* Callback to allow to set a value for an attribute when the max depth has
6370
* been reached.
@@ -180,7 +187,16 @@ public function normalize($object, string $format = null, array $context = [])
180187
}
181188

182189
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);
183-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
190+
191+
try {
192+
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
193+
} catch (UninitializedPropertyException $exception) {
194+
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? false) {
195+
continue;
196+
}
197+
throw $exception;
198+
}
199+
184200
if ($maxDepthReached) {
185201
$attributeValue = $maxDepthHandler($attributeValue, $object, $attribute, $format, $attributeContext);
186202
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Normalizer\Features;
4+
5+
use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
6+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
7+
8+
/**
9+
* Test AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES.
10+
*/
11+
trait SkipUninitializedValuesTestTrait
12+
{
13+
abstract protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface;
14+
15+
/**
16+
* @requires PHP 7.4
17+
*/
18+
public function testSkipUninitializedValues()
19+
{
20+
$object = new TypedPropertiesObject();
21+
22+
$normalizer = $this->getNormalizerForSkipUninitializedValues();
23+
$result = $normalizer->normalize($object, null, ['skip_uninitialized_values' => true, 'groups' => ['foo']]);
24+
$this->assertSame(['initialized' => 'value'], $result);
25+
}
26+
27+
/**
28+
* @requires PHP 7.4
29+
*/
30+
public function testWithoutSkipUninitializedValues()
31+
{
32+
$object = new TypedPropertiesObject();
33+
34+
$normalizer = $this->getNormalizerForSkipUninitializedValues();
35+
$this->expectException(UninitializedPropertyException::class);
36+
$normalizer->normalize($object, null, ['groups' => ['foo']]);
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Normalizer\Features;
4+
5+
use Symfony\Component\Serializer\Annotation\Groups;
6+
7+
class TypedPropertiesObject
8+
{
9+
/**
10+
* @Groups({"foo"})
11+
*/
12+
public string $unInitialized;
13+
14+
/**
15+
* @Groups({"foo"})
16+
*/
17+
public string $initialized = 'value';
18+
19+
/**
20+
* @Groups({"bar"})
21+
*/
22+
public string $initialized2 = 'value';
23+
}

src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummy;
5050
use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectToPopulateTestTrait;
5151
use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipNullValuesTestTrait;
52+
use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipUninitializedValuesTestTrait;
5253
use Symfony\Component\Serializer\Tests\Normalizer\Features\TypeEnforcementTestTrait;
5354

5455
/**
@@ -66,6 +67,7 @@ class ObjectNormalizerTest extends TestCase
6667
use MaxDepthTestTrait;
6768
use ObjectToPopulateTestTrait;
6869
use SkipNullValuesTestTrait;
70+
use SkipUninitializedValuesTestTrait;
6971
use TypeEnforcementTestTrait;
7072

7173
/**
@@ -534,6 +536,15 @@ protected function getNormalizerForSkipNullValues(): ObjectNormalizer
534536
return new ObjectNormalizer();
535537
}
536538

539+
// skip uninitialized
540+
541+
protected function getNormalizerForSkipUninitializedValues(): ObjectNormalizer
542+
{
543+
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
544+
545+
return new ObjectNormalizer($classMetadataFactory);
546+
}
547+
537548
// type enforcement
538549

539550
protected function getDenormalizerForTypeEnforcement(): ObjectNormalizer

0 commit comments

Comments
 (0)