bug #37628 [Serializer] Support multiple levels of discriminator mapping (jeroennoten)
This PR was merged into the 4.4 branch.
Discussion
----------
[Serializer] Support multiple levels of discriminator mapping
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | yes
| New feature? | no
| Deprecations? | no
| License | MIT
When using multiple levels of discriminator mapping for denormalizing to a multiple level class hierarchy, only the top level discriminator mapping is considered, resulting in an error: **Cannot instantiate abstract class ...**
Example:
```php
/**
* @DiscriminatorMap(typeProperty="type", mapping={"group"=GroupNode::class, "item"=ItemNode::class})
*/
abstract class Node {
// ...
}
class GroupNode extends Node {
// ...
}
/**
* @DiscriminatorMap(typeProperty="item_type", mapping={"foo"=FooItemNode::class, "bar"=BarItemNode::class})
*/
abstract class ItemNode extends Node {
// ...
}
class FooItemNode {
// ...
}
class BarItemNode {
// ...
}
$objectNormalizer = new ObjectNormalizer(new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())));
$node = $objectNormalizer->denormalize(['type' => 'item', 'item_type' => 'foo']);
```
This results in an error: **Cannot instantiate abstract class ItemNode**. Expected is that `$node` is of type `FooItemNode` after denormalization.
The solution is to recursively call `AbstractObjectNormalizer::instantiateObject()` when a mapping is found, instead of always calling `parent::instantiateObject()`.
Commits
-------
324ad95fee
[Serializer] Support multiple levels of discriminator mapping
This commit is contained in:
commit
2dbbe50d0c
@ -230,8 +230,9 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
|||||||
throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s".', $type, $class));
|
throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s".', $type, $class));
|
||||||
}
|
}
|
||||||
|
|
||||||
$class = $mappedClass;
|
if ($mappedClass !== $class) {
|
||||||
$reflectionClass = new \ReflectionClass($class);
|
return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format);
|
return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format);
|
||||||
|
@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
|||||||
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
||||||
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
|
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
|
||||||
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
|
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
|
||||||
|
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
|
||||||
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
use Symfony\Component\Serializer\Mapping\ClassMetadata;
|
||||||
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
|
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
|
||||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||||
@ -235,6 +236,43 @@ class AbstractObjectNormalizerTest extends TestCase
|
|||||||
$this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux);
|
$this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDenormalizeWithNestedDiscriminatorMap()
|
||||||
|
{
|
||||||
|
$classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface {
|
||||||
|
public function getMappingForClass(string $class): ?ClassDiscriminatorMapping
|
||||||
|
{
|
||||||
|
switch ($class) {
|
||||||
|
case AbstractDummy::class:
|
||||||
|
return new ClassDiscriminatorMapping('type', [
|
||||||
|
'foo' => AbstractDummyFirstChild::class,
|
||||||
|
]);
|
||||||
|
case AbstractDummyFirstChild::class:
|
||||||
|
return new ClassDiscriminatorMapping('nested_type', [
|
||||||
|
'bar' => AbstractDummySecondChild::class,
|
||||||
|
]);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMappingForMappedObject($object): ?ClassDiscriminatorMapping
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeForMappedObject($object): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$normalizer = new AbstractObjectNormalizerDummy(null, null, null, $classDiscriminatorResolver);
|
||||||
|
|
||||||
|
$denormalizedData = $normalizer->denormalize(['type' => 'foo', 'nested_type' => 'bar'], AbstractDummy::class);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that additional attributes throw an exception if no metadata factory is specified.
|
* Test that additional attributes throw an exception if no metadata factory is specified.
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user