Skip to content

Commit b8e9ec3

Browse files
bug symfony#52681 [Serializer] Fix support for DiscriminatorMap in PropertyNormalizer (mtarld)
This PR was merged into the 5.4 branch. Discussion ---------- [Serializer] Fix support for DiscriminatorMap in PropertyNormalizer | Q | A | ------------- | --- | Branch? | 5.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix symfony#36585 | License | MIT Follow up of symfony#36695 Commits ------- 50d086c [Serializer] Move discrimination to abstract
2 parents 1da3a5c + 50d086c commit b8e9ec3

File tree

6 files changed

+136
-12
lines changed

6 files changed

+136
-12
lines changed

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

+16-2
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,15 @@ public function normalize($object, string $format = null, array $context = [])
179179

180180
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);
181181

182+
$discriminatorProperty = null;
183+
if (null !== $this->classDiscriminatorResolver && null !== $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object)) {
184+
$discriminatorProperty = $mapping->getTypeProperty();
185+
}
186+
182187
try {
183-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
188+
$attributeValue = $attribute === $discriminatorProperty
189+
? $this->classDiscriminatorResolver->getTypeForMappedObject($object)
190+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
184191
} catch (UninitializedPropertyException $e) {
185192
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? true) {
186193
continue;
@@ -382,8 +389,15 @@ public function denormalize($data, string $type, string $format = null, array $c
382389
}
383390

384391
if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
392+
$discriminatorProperty = null;
393+
if (null !== $this->classDiscriminatorResolver && null !== $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object)) {
394+
$discriminatorProperty = $mapping->getTypeProperty();
395+
}
396+
385397
try {
386-
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
398+
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $discriminatorProperty
399+
? $this->classDiscriminatorResolver->getTypeForMappedObject($object)
400+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
387401
} catch (NoSuchPropertyException $e) {
388402
}
389403
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public function hasCacheableSupportsMethod(): bool
6767
*/
6868
private function supports(string $class): bool
6969
{
70+
if (null !== $this->classDiscriminatorResolver && $this->classDiscriminatorResolver->getMappingForClass($class)) {
71+
return true;
72+
}
73+
7074
$class = new \ReflectionClass($class);
7175
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
7276
foreach ($methods as $method) {

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

+6-10
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ class ObjectNormalizer extends AbstractObjectNormalizer
3030
{
3131
protected $propertyAccessor;
3232

33-
private $discriminatorCache = [];
34-
3533
private $objectClassResolver;
3634

3735
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
@@ -128,16 +126,14 @@ protected function extractAttributes(object $object, string $format = null, arra
128126
*/
129127
protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = [])
130128
{
131-
$cacheKey = \get_class($object);
132-
if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
133-
$this->discriminatorCache[$cacheKey] = null;
134-
if (null !== $this->classDiscriminatorResolver) {
135-
$mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object);
136-
$this->discriminatorCache[$cacheKey] = null === $mapping ? null : $mapping->getTypeProperty();
137-
}
129+
$discriminatorProperty = null;
130+
if (null !== $this->classDiscriminatorResolver && null !== $mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object)) {
131+
$discriminatorProperty = $mapping->getTypeProperty();
138132
}
139133

140-
return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute);
134+
return $attribute === $discriminatorProperty
135+
? $this->classDiscriminatorResolver->getTypeForMappedObject($object)
136+
: $this->propertyAccessor->getValue($object, $attribute);
141137
}
142138

143139
/**

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

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public function hasCacheableSupportsMethod(): bool
6161
*/
6262
private function supports(string $class): bool
6363
{
64+
if (null !== $this->classDiscriminatorResolver && $this->classDiscriminatorResolver->getMappingForClass($class)) {
65+
return true;
66+
}
67+
6468
$class = new \ReflectionClass($class);
6569

6670
// We look for at least one non-static property

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

+63
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1717
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1818
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
19+
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
1920
use Symfony\Component\Serializer\Exception\LogicException;
21+
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
2022
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
2123
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
2224
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
@@ -498,6 +500,27 @@ protected function getNormalizerForSkipUninitializedValues(): NormalizerInterfac
498500
{
499501
return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
500502
}
503+
504+
public function testNormalizeWithDiscriminator()
505+
{
506+
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
507+
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
508+
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, $discriminator);
509+
510+
$this->assertSame(['type' => 'one', 'url' => 'URL_ONE'], $normalizer->normalize(new GetSetMethodDiscriminatedDummyOne()));
511+
}
512+
513+
public function testDenormalizeWithDiscriminator()
514+
{
515+
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
516+
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
517+
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, $discriminator);
518+
519+
$denormalized = new GetSetMethodDiscriminatedDummyTwo();
520+
$denormalized->setUrl('url');
521+
522+
$this->assertEquals($denormalized, $normalizer->denormalize(['type' => 'two', 'url' => 'url'], GetSetMethodDummyInterface::class));
523+
}
501524
}
502525

503526
class GetSetDummy
@@ -762,3 +785,43 @@ public function __call($key, $value)
762785
throw new \RuntimeException('__call should not be called. Called with: '.$key);
763786
}
764787
}
788+
789+
/**
790+
* @DiscriminatorMap(typeProperty="type", mapping={
791+
* "one" = GetSetMethodDiscriminatedDummyOne::class,
792+
* "two" = GetSetMethodDiscriminatedDummyTwo::class,
793+
* })
794+
*/
795+
interface GetSetMethodDummyInterface
796+
{
797+
}
798+
799+
class GetSetMethodDiscriminatedDummyOne implements GetSetMethodDummyInterface
800+
{
801+
private string $url = 'URL_ONE';
802+
803+
public function getUrl(): string
804+
{
805+
return $this->url;
806+
}
807+
808+
public function setUrl(string $url): void
809+
{
810+
$this->url = $url;
811+
}
812+
}
813+
814+
class GetSetMethodDiscriminatedDummyTwo implements GetSetMethodDummyInterface
815+
{
816+
private string $url = 'URL_TWO';
817+
818+
public function getUrl(): string
819+
{
820+
return $this->url;
821+
}
822+
823+
public function setUrl(string $url): void
824+
{
825+
$this->url = $url;
826+
}
827+
}

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

+43
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
1717
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1818
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
19+
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
1920
use Symfony\Component\Serializer\Exception\LogicException;
21+
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
2022
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
2123
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
2224
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
@@ -457,6 +459,27 @@ protected function getNormalizerForSkipUninitializedValues(): NormalizerInterfac
457459
{
458460
return new PropertyNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
459461
}
462+
463+
public function testNormalizeWithDiscriminator()
464+
{
465+
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
466+
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
467+
$normalizer = new PropertyNormalizer($classMetadataFactory, null, null, $discriminator);
468+
469+
$this->assertSame(['type' => 'one', 'url' => 'URL_ONE'], $normalizer->normalize(new PropertyDiscriminatedDummyOne()));
470+
}
471+
472+
public function testDenormalizeWithDiscriminator()
473+
{
474+
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
475+
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
476+
$normalizer = new PropertyNormalizer($classMetadataFactory, null, null, $discriminator);
477+
478+
$denormalized = new PropertyDiscriminatedDummyTwo();
479+
$denormalized->url = 'url';
480+
481+
$this->assertEquals($denormalized, $normalizer->denormalize(['type' => 'two', 'url' => 'url'], PropertyDummyInterface::class));
482+
}
460483
}
461484

462485
class PropertyDummy
@@ -560,3 +583,23 @@ public function getIntMatrix(): array
560583
return $this->intMatrix;
561584
}
562585
}
586+
587+
/**
588+
* @DiscriminatorMap(typeProperty="type", mapping={
589+
* "one" = PropertyDiscriminatedDummyOne::class,
590+
* "two" = PropertyDiscriminatedDummyTwo::class,
591+
* })
592+
*/
593+
interface PropertyDummyInterface
594+
{
595+
}
596+
597+
class PropertyDiscriminatedDummyOne implements PropertyDummyInterface
598+
{
599+
public string $url = 'URL_ONE';
600+
}
601+
602+
class PropertyDiscriminatedDummyTwo implements PropertyDummyInterface
603+
{
604+
public string $url = 'URL_TWO';
605+
}

0 commit comments

Comments
 (0)