Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Serializer] Allow (de)normalization of empty objects in PropertyNormalizer #46417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: 7.3
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 2 src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function testSymfonySerialize()
EOF;

$extractor = new PhpDocExtractor();
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor);
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor, null, null, [], true);
$serializer = new Serializer([
new ArrayDenormalizer(),
new MimeMessageNormalizer($propertyNormalizer),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(),
null,
[],
false,
])

->alias(PropertyNormalizer::class, 'serializer.normalizer.property')
Expand Down
2 changes: 1 addition & 1 deletion 2 src/Symfony/Component/Mime/Tests/EmailTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ public function testSymfonySerialize()
EOF;

$extractor = new PhpDocExtractor();
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor);
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor, null, null, [], true);
$serializer = new Serializer([
new ArrayDenormalizer(),
new MimeMessageNormalizer($propertyNormalizer),
Expand Down
2 changes: 1 addition & 1 deletion 2 src/Symfony/Component/Mime/Tests/MessageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public function testSymfonySerialize()
EOF;

$extractor = new PhpDocExtractor();
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor);
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor, null, null, [], true);
$serializer = new Serializer([
new ArrayDenormalizer(),
new MimeMessageNormalizer($propertyNormalizer),
Expand Down
2 changes: 2 additions & 0 deletions 2 src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ CHANGELOG
* Add support for constructor promoted properties to `Context` attribute
* Add context option `PropertyNormalizer::NORMALIZE_VISIBILITY` with bitmask flags `PropertyNormalizer::NORMALIZE_PUBLIC`, `PropertyNormalizer::NORMALIZE_PROTECTED`, `PropertyNormalizer::NORMALIZE_PRIVATE`
* Add method `withNormalizeVisibility` to `PropertyNormalizerContextBuilder`
* Add `allowNormalizationOfObjectsWithoutAnyProperties` option to `PropertyNormalizer`
* Add `allowNormalizationOfObjectsWithoutAnyGetters` option to `GetSetMethodNormalizer`

6.1
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

namespace Symfony\Component\Serializer\Normalizer;

use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;

/**
* Converts between objects with getter and setter methods and arrays.
*
Expand All @@ -36,6 +41,25 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer
{
private static $setterAccessibleCache = [];

private $allowNormalizationOfObjectsWithoutAnyGetters;
olsavmic marked this conversation as resolved.
Show resolved Hide resolved

public function __construct(
ClassMetadataFactoryInterface $classMetadataFactory = null,
NameConverterInterface $nameConverter = null,
PropertyTypeExtractorInterface $propertyTypeExtractor = null,
ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null,
callable $objectClassResolver = null,
array $defaultContext = [],
bool $allowNormalizationOfObjectsWithoutAnyGetters = false,
) {
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext);
$this->allowNormalizationOfObjectsWithoutAnyGetters = $allowNormalizationOfObjectsWithoutAnyGetters;

if (\func_num_args() < 7) {
trigger_deprecation('symfony/serializer', '6.2', '$allowNormalizationOfObjectsWithoutAnyGetters parameter of %s() should be explicitly provided since the default value will change to `true` in symfony/serializer >=7.0', __METHOD__);
}
}

/**
* {@inheritdoc}
*
Expand Down Expand Up @@ -69,6 +93,10 @@ public function hasCacheableSupportsMethod(): bool
*/
private function supports(string $class): bool
{
if ($this->allowNormalizationOfObjectsWithoutAnyGetters) {
return true;
}

$class = new \ReflectionClass($class);
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,21 @@ class PropertyNormalizer extends AbstractObjectNormalizer
*/
public const NORMALIZE_VISIBILITY = 'normalize_visibility';

public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
private $allowNormalizationOfObjectsWithoutAnyProperties;
olsavmic marked this conversation as resolved.
Show resolved Hide resolved

public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [], bool $allowNormalizationOfObjectsWithoutAnyProperties = false)
{
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext);

if (!isset($this->defaultContext[self::NORMALIZE_VISIBILITY])) {
$this->defaultContext[self::NORMALIZE_VISIBILITY] = self::NORMALIZE_PUBLIC | self::NORMALIZE_PROTECTED | self::NORMALIZE_PRIVATE;
}

$this->allowNormalizationOfObjectsWithoutAnyProperties = $allowNormalizationOfObjectsWithoutAnyProperties;

if (\func_num_args() < 7) {
trigger_deprecation('symfony/serializer', '6.2', '$allowNormalizationOfObjectsWithoutAnyProperties parameter of %s() should be explicitly provided since the default value will change to `true` in symfony/serializer >=7.0', __METHOD__);
}
}

/**
Expand Down Expand Up @@ -87,6 +95,10 @@ public function hasCacheableSupportsMethod(): bool
*/
private function supports(string $class): bool
{
if ($this->allowNormalizationOfObjectsWithoutAnyProperties) {
return true;
}

$class = new \ReflectionClass($class);

// We look for at least one non-static property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ public function getNormalizer()
{
$extractor = new PhpDocExtractor();

yield [new PropertyNormalizer()];
yield [new PropertyNormalizer(null, null, $extractor)];
yield [new PropertyNormalizer(null, null, null, null, null, [], true)];
yield [new PropertyNormalizer(null, null, $extractor, null, null, [], true)];
yield [new ObjectNormalizer()];
yield [new ObjectNormalizer(null, null, null, $extractor)];
}
Expand All @@ -222,7 +222,7 @@ public function testIgnore()
$dummy = new IgnoreDummy();
$dummy->ignored1 = 'hello';

$normalizer = new PropertyNormalizer($this->classMetadata);
$normalizer = new PropertyNormalizer($this->classMetadata, null, null, null, null, [], true);

$this->assertSame([], $normalizer->normalize($dummy));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ protected function setUp(): void
private function createNormalizer(array $defaultContext = [])
{
$this->serializer = $this->createMock(SerializerNormalizer::class);
$this->normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext);
$this->normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext, true);
$this->normalizer->setSerializer($this->serializer);
}

Expand Down Expand Up @@ -234,20 +234,20 @@ protected function getNormalizerForCallbacksWithPropertyTypeExtractor(): GetSetM
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));

return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), $this->getCallbackPropertyTypeExtractor());
return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), $this->getCallbackPropertyTypeExtractor(), null, null, [], true);
}

protected function getNormalizerForCallbacks(): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));

return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory));
return new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, [], true);
}

protected function getNormalizerForCircularReference(array $defaultContext): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, $defaultContext);
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, $defaultContext, true);
new Serializer([$normalizer]);

return $normalizer;
Expand All @@ -261,7 +261,7 @@ protected function getSelfReferencingModel()
protected function getDenormalizerForConstructArguments(): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$denormalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory));
$denormalizer = new GetSetMethodNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory), null, null, null, [], true);
new Serializer([$denormalizer]);

return $denormalizer;
Expand All @@ -271,20 +271,20 @@ protected function getNormalizerForGroups(): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));

return new GetSetMethodNormalizer($classMetadataFactory);
return new GetSetMethodNormalizer($classMetadataFactory, null, null, null, null, [], true);
}

protected function getDenormalizerForGroups(): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));

return new GetSetMethodNormalizer($classMetadataFactory);
return new GetSetMethodNormalizer($classMetadataFactory, null, null, null, null, [], true);
}

public function testGroupsNormalizeWithNameConverter()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
$this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter(), null, null, null, [], true);
$this->normalizer->setSerializer($this->serializer);

$obj = new GroupDummy();
Expand All @@ -305,7 +305,7 @@ public function testGroupsNormalizeWithNameConverter()
public function testGroupsDenormalizeWithNameConverter()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
$this->normalizer = new GetSetMethodNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter(), null, null, null, [], true);
$this->normalizer->setSerializer($this->serializer);

$obj = new GroupDummy();
Expand All @@ -326,7 +326,7 @@ public function testGroupsDenormalizeWithNameConverter()
protected function getNormalizerForMaxDepth(): NormalizerInterface
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new GetSetMethodNormalizer($classMetadataFactory);
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, null, null, [], true);
$serializer = new Serializer([$normalizer]);
$normalizer->setSerializer($serializer);

Expand All @@ -336,7 +336,7 @@ protected function getNormalizerForMaxDepth(): NormalizerInterface
protected function getDenormalizerForObjectToPopulate(): DenormalizerInterface
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor());
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor(), null, null, [], true);
new Serializer([$normalizer]);

return $normalizer;
Expand All @@ -345,7 +345,7 @@ protected function getDenormalizerForObjectToPopulate(): DenormalizerInterface
protected function getDenormalizerForTypeEnforcement(): DenormalizerInterface
{
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
$normalizer = new GetSetMethodNormalizer(null, null, $extractor);
$normalizer = new GetSetMethodNormalizer(null, null, $extractor, null, null, [], true);
$serializer = new Serializer([new ArrayDenormalizer(), $normalizer]);
$normalizer->setSerializer($serializer);

Expand All @@ -357,10 +357,32 @@ public function testRejectInvalidKey()
$this->markTestSkipped('This test makes no sense with the GetSetMethodNormalizer');
}

public function testNormalizeObjectWithoutAnyProperties()
{
$obj = new EmptyObjectDummy();

$this->assertTrue($this->normalizer->supportsNormalization($obj));
$this->assertEquals(
[],
$this->normalizer->normalize($obj),
);
}

public function testDenormalizeObjectWithoutAnyProperties()
{
$obj = new EmptyObjectDummy();

$this->assertTrue($this->normalizer->supportsDenormalization($obj, \get_class($obj)));
$this->assertEquals(
$obj,
$this->normalizer->denormalize([], \get_class($obj)),
);
}

protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor());
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor(), null, null, [], true);
new Serializer([$normalizer]);

return $normalizer;
Expand All @@ -369,7 +391,7 @@ protected function getNormalizerForIgnoredAttributes(): GetSetMethodNormalizer
protected function getDenormalizerForIgnoredAttributes(): GetSetMethodNormalizer
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor());
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, new PhpDocExtractor(), null, null, [], true);
new Serializer([$normalizer]);

return $normalizer;
Expand Down Expand Up @@ -427,7 +449,8 @@ public function testNoTraversableSupport()

public function testNoStaticGetSetSupport()
{
$this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy()));
$normalizer = new GetSetMethodNormalizer(null, null, null, null, null, [], false);
$this->assertFalse($normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy()));
}

public function testPrivateSetter()
Expand Down Expand Up @@ -466,12 +489,12 @@ protected function getObjectCollectionWithExpectedArray(): array

protected function getNormalizerForCacheableObjectAttributesTest(): GetSetMethodNormalizer
{
return new GetSetMethodNormalizer();
return new GetSetMethodNormalizer(null, null, null, null, null, [], true);
}

protected function getNormalizerForSkipUninitializedValues(): NormalizerInterface
{
return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())), null, null, null, null, [], true);
}
}

Expand Down Expand Up @@ -722,3 +745,7 @@ public function hasFoo()
return $this->foo;
}
}

class EmptyObjectDummy
{
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.