Skip to content

Commit e8ee3a6

Browse files
committed
[Serializer] Move discrimination to abstract
1 parent 48be4b3 commit e8ee3a6

File tree

6 files changed

+141
-14
lines changed

6 files changed

+141
-14
lines changed

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

+24-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
*/
3838
abstract class AbstractObjectNormalizer extends AbstractNormalizer
3939
{
40+
/** @var array<string, string|null> */
41+
private array $discriminatorCache = [];
42+
4043
/**
4144
* Set to true to respect the max depth metadata on fields.
4245
*/
@@ -180,7 +183,9 @@ public function normalize($object, string $format = null, array $context = [])
180183
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);
181184

182185
try {
183-
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
186+
$attributeValue = $attribute === $this->getDiscriminatorProperty($object)
187+
? $this->classDiscriminatorResolver->getTypeForMappedObject($object)
188+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
184189
} catch (UninitializedPropertyException $e) {
185190
if ($context[self::SKIP_UNINITIALIZED_VALUES] ?? $this->defaultContext[self::SKIP_UNINITIALIZED_VALUES] ?? true) {
186191
continue;
@@ -387,7 +392,9 @@ public function denormalize($data, string $type, string $format = null, array $c
387392

388393
if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
389394
try {
390-
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
395+
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $this->getDiscriminatorProperty($object)
396+
? $this->classDiscriminatorResolver->getTypeForMappedObject($object)
397+
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
391398
} catch (NoSuchPropertyException $e) {
392399
}
393400
}
@@ -787,4 +794,19 @@ private function isUninitializedValueError(\Error $e): bool
787794
&& str_starts_with($e->getMessage(), 'Typed property')
788795
&& str_ends_with($e->getMessage(), 'must not be accessed before initialization');
789796
}
797+
798+
protected function getDiscriminatorProperty(object $object): ?string
799+
{
800+
if (null === $this->classDiscriminatorResolver) {
801+
return null;
802+
}
803+
804+
$cacheKey = \get_class($object);
805+
806+
if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
807+
$this->discriminatorCache[$cacheKey] = $this->classDiscriminatorResolver->getMappingForMappedObject($object)?->getTypeProperty();
808+
}
809+
810+
return $this->discriminatorCache[$cacheKey];
811+
}
790812
}

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

+3-12
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,9 @@ 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-
}
138-
}
139-
140-
return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute);
129+
return $attribute === $this->getDiscriminatorProperty($object)
130+
? $this->getDiscriminatorType($object)
131+
: $this->propertyAccessor->getValue($object, $attribute);
141132
}
142133

143134
/**

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)