Skip to content

Commit

Permalink
[Serializer] Move discrimination to abstract
Browse files Browse the repository at this point in the history
  • Loading branch information
mtarld committed Nov 22, 2023
1 parent 48be4b3 commit e8ee3a6
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
*/
abstract class AbstractObjectNormalizer extends AbstractNormalizer
{
/** @var array<string, string|null> */
private array $discriminatorCache = [];

/**
* Set to true to respect the max depth metadata on fields.
*/
Expand Down Expand Up @@ -180,7 +183,9 @@ public function normalize($object, string $format = null, array $context = [])
$attributeContext = $this->getAttributeNormalizationContext($object, $attribute, $context);

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

if ($attributeContext[self::DEEP_OBJECT_TO_POPULATE] ?? $this->defaultContext[self::DEEP_OBJECT_TO_POPULATE] ?? false) {
try {
$attributeContext[self::OBJECT_TO_POPULATE] = $this->getAttributeValue($object, $attribute, $format, $attributeContext);
$attributeContext[self::OBJECT_TO_POPULATE] = $attribute === $this->getDiscriminatorProperty($object)
? $this->classDiscriminatorResolver->getTypeForMappedObject($object)
: $this->getAttributeValue($object, $attribute, $format, $attributeContext);
} catch (NoSuchPropertyException $e) {
}
}
Expand Down Expand Up @@ -787,4 +794,19 @@ private function isUninitializedValueError(\Error $e): bool
&& str_starts_with($e->getMessage(), 'Typed property')
&& str_ends_with($e->getMessage(), 'must not be accessed before initialization');
}

protected function getDiscriminatorProperty(object $object): ?string
{
if (null === $this->classDiscriminatorResolver) {
return null;
}

$cacheKey = \get_class($object);

if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
$this->discriminatorCache[$cacheKey] = $this->classDiscriminatorResolver->getMappingForMappedObject($object)?->getTypeProperty();
}

return $this->discriminatorCache[$cacheKey];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public function hasCacheableSupportsMethod(): bool
*/
private function supports(string $class): bool
{
if (null !== $this->classDiscriminatorResolver && $this->classDiscriminatorResolver->getMappingForClass($class)) {
return true;
}

$class = new \ReflectionClass($class);
$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
Expand Down
15 changes: 3 additions & 12 deletions src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ class ObjectNormalizer extends AbstractObjectNormalizer
{
protected $propertyAccessor;

private $discriminatorCache = [];

private $objectClassResolver;

public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
Expand Down Expand Up @@ -128,16 +126,9 @@ protected function extractAttributes(object $object, string $format = null, arra
*/
protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = [])
{
$cacheKey = \get_class($object);
if (!\array_key_exists($cacheKey, $this->discriminatorCache)) {
$this->discriminatorCache[$cacheKey] = null;
if (null !== $this->classDiscriminatorResolver) {
$mapping = $this->classDiscriminatorResolver->getMappingForMappedObject($object);
$this->discriminatorCache[$cacheKey] = null === $mapping ? null : $mapping->getTypeProperty();
}
}

return $attribute === $this->discriminatorCache[$cacheKey] ? $this->classDiscriminatorResolver->getTypeForMappedObject($object) : $this->propertyAccessor->getValue($object, $attribute);
return $attribute === $this->getDiscriminatorProperty($object)
? $this->getDiscriminatorType($object)
: $this->propertyAccessor->getValue($object, $attribute);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public function hasCacheableSupportsMethod(): bool
*/
private function supports(string $class): bool
{
if (null !== $this->classDiscriminatorResolver && $this->classDiscriminatorResolver->getMappingForClass($class)) {
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 @@ -16,7 +16,9 @@
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
Expand Down Expand Up @@ -498,6 +500,27 @@ protected function getNormalizerForSkipUninitializedValues(): NormalizerInterfac
{
return new GetSetMethodNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
}

public function testNormalizeWithDiscriminator()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, $discriminator);

$this->assertSame(['type' => 'one', 'url' => 'URL_ONE'], $normalizer->normalize(new GetSetMethodDiscriminatedDummyOne()));
}

public function testDenormalizeWithDiscriminator()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$normalizer = new GetSetMethodNormalizer($classMetadataFactory, null, null, $discriminator);

$denormalized = new GetSetMethodDiscriminatedDummyTwo();
$denormalized->setUrl('url');

$this->assertEquals($denormalized, $normalizer->denormalize(['type' => 'two', 'url' => 'url'], GetSetMethodDummyInterface::class));
}
}

class GetSetDummy
Expand Down Expand Up @@ -762,3 +785,43 @@ public function __call($key, $value)
throw new \RuntimeException('__call should not be called. Called with: '.$key);
}
}

/**
* @DiscriminatorMap(typeProperty="type", mapping={
* "one" = GetSetMethodDiscriminatedDummyOne::class,
* "two" = GetSetMethodDiscriminatedDummyTwo::class,
* })
*/
interface GetSetMethodDummyInterface
{
}

class GetSetMethodDiscriminatedDummyOne implements GetSetMethodDummyInterface
{
private string $url = 'URL_ONE';

public function getUrl(): string
{
return $this->url;
}

public function setUrl(string $url): void
{
$this->url = $url;
}
}

class GetSetMethodDiscriminatedDummyTwo implements GetSetMethodDummyInterface
{
private string $url = 'URL_TWO';

public function getUrl(): string
{
return $this->url;
}

public function setUrl(string $url): void
{
$this->url = $url;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
Expand Down Expand Up @@ -457,6 +459,27 @@ protected function getNormalizerForSkipUninitializedValues(): NormalizerInterfac
{
return new PropertyNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
}

public function testNormalizeWithDiscriminator()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$normalizer = new PropertyNormalizer($classMetadataFactory, null, null, $discriminator);

$this->assertSame(['type' => 'one', 'url' => 'URL_ONE'], $normalizer->normalize(new PropertyDiscriminatedDummyOne()));
}

public function testDenormalizeWithDiscriminator()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$discriminator = new ClassDiscriminatorFromClassMetadata($classMetadataFactory);
$normalizer = new PropertyNormalizer($classMetadataFactory, null, null, $discriminator);

$denormalized = new PropertyDiscriminatedDummyTwo();
$denormalized->url = 'url';

$this->assertEquals($denormalized, $normalizer->denormalize(['type' => 'two', 'url' => 'url'], PropertyDummyInterface::class));
}
}

class PropertyDummy
Expand Down Expand Up @@ -560,3 +583,23 @@ public function getIntMatrix(): array
return $this->intMatrix;
}
}

/**
* @DiscriminatorMap(typeProperty="type", mapping={
* "one" = PropertyDiscriminatedDummyOne::class,
* "two" = PropertyDiscriminatedDummyTwo::class,
* })
*/
interface PropertyDummyInterface
{
}

class PropertyDiscriminatedDummyOne implements PropertyDummyInterface
{
public string $url = 'URL_ONE';
}

class PropertyDiscriminatedDummyTwo implements PropertyDummyInterface
{
public string $url = 'URL_TWO';
}

0 comments on commit e8ee3a6

Please sign in to comment.