8000 [Serializer] Add defaultValue to DiscriminatorMap · symfony/symfony@828656f · GitHub
[go: up one dir, main page]

Skip to content

Commit 828656f

Browse files
committed
[Serializer] Add defaultValue to DiscriminatorMap
1 parent 946883f commit 828656f

18 files changed

+97
-11
lines changed

src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ class DiscriminatorMap
2222
/**
2323
* @param string $typeProperty The property holding the type discriminator
2424
* @param array<string, class-string> $mapping The mapping between types and classes (i.e. ['admin_user' => AdminUser::class])
25+
* @param ?string $defaultValue The default value for the type
2526
*
2627
* @throws InvalidArgumentException
2728
*/
2829
public function __construct(
2930
private readonly string $typeProperty,
3031
private readonly array $mapping,
32+
private readonly ?string $defaultValue = null,
3133
) {
3234
if (!$typeProperty) {
3335
throw new InvalidArgumentException(\sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class));
@@ -36,6 +38,10 @@ public function __construct(
3638
if (!$mapping) {
3739
throw new InvalidArgumentException(\sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class));
3840
}
41+
42+
if (null !== $this->defaultValue && !\array_key_exists($this->defaultValue, $this->mapping)) {
43+
throw new InvalidArgumentException(\sprintf('Parameter "defaultValue" given to "%s" must be present in "mapping" types.', static::class));
44+
}
3945
}
4046

4147
public function getTypeProperty(): string
@@ -47,6 +53,11 @@ public function getMapping(): array
4753
{
4854
return $this->mapping;
4955
}
56+
57+
public function getDefaultValue(): ?string
58+
{
59+
return $this->defaultValue;
60+
}
5061
}
5162

5263
if (!class_exists(\Symfony\Component\Serializer\Annotation\DiscriminatorMap::class, false)) {

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Deprecate the `CompiledClassMetadataFactory` and `CompiledClassMetadataCacheWarmer` classes
88
* Register `NormalizerInterface` and `DenormalizerInterface` aliases for named serializers
99
* Add `NumberNormalizer` to normalize `BcMath\Number` and `GMP` as `string`
10+
* Add `defaultValue` to `DiscriminatorMap`
1011

1112
7.2
1213
---

src/Symfony/Component/Serializer/Mapping/ClassDiscriminatorMapping.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ClassDiscriminatorMapping
2222
public function __construct(
2323
private readonly string $typeProperty,
2424
private array $typesMapping = [],
25+
private readonly ?string $defaultValue = null,
2526
) {
2627
uasort($this->typesMapping, static function (string $a, string $b): int {
2728
if (is_a($a, $b, true)) {
@@ -61,4 +62,9 @@ public function getTypesMapping(): array
6162
{
6263
return $this->typesMapping;
6364
}
65+
66+
public function getDefaultValue(): ?string
67+
{
68+
return $this->defaultValue;
69+
}
6470
}

src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string
5555
$classDiscriminatorMapping = $classMetadata->getClassDiscriminatorMapping() ? [
5656
$classMetadata->getClassDiscriminatorMapping()->getTypeProperty(),
5757
$classMetadata->getClassDiscriminatorMapping()->getTypesMapping(),
58+
$classMetadata->getClassDiscriminatorMapping()->getDefaultValue(),
5859
] : null;
5960

6061
$compiled .= \sprintf("\n'%s' => %s,", $classMetadata->getName(), VarExporter::export([

src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
5959

6060
foreach ($this->loadAttributes($reflectionClass) as $attribute) {
6161
match (true) {
62-
$attribute instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($attribute->getTypeProperty(), $attribute->getMapping())),
62+
$attribute instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($attribute->getTypeProperty(), $attribute->getMapping(), $attribute->getDefaultValue())),
6363
$attribute instanceof Groups => $classGroups = $attribute->getGroups(),
6464
$attribute instanceof Context => $classContextAttribute = $attribute,
6565
default => null,

src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
107107

108108
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
109109
(string) $xml->{'discriminator-map'}->attributes()->{'type-property'},
110-
$mapping
110+
$mapping,
111+
$xml->{'discriminator-map'}->attributes()->{'default-value'} ?? null
111112
));
112113
}
113114

src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
133133

134134
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
135135
$yaml['discriminator_map']['type_property'],
136-
$yaml['discriminator_map']['mapping']
136+
$yaml['discriminator_map']['mapping'],
137+
$yaml['discriminator_map']['default_value'] ?? null
137138
));
138139
}
139140

src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<xsd:element name="mapping" type="discriminator-map-mapping" maxOccurs="unbounded" />
4848
</xsd:choice>
4949
<xsd:attribute name="type-property" type="xsd:string" use="required" />
50+
<xsd:attribute name="default-value" type="xsd:string" />
5051
</xsd:complexType>
5152

5253
<xsd:complexType name="discriminator-map-mapping">

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ private function getMappedClass(array $data, string $class, array $context): str
11791179
return $class;
11801180
}
11811181

1182-
if (null === $type = $data[$mapping->getTypeProperty()] ?? null) {
1182+
if (null === $type = $data[$mapping->getTypeProperty()] ?? $mapping->getDefaultValue()) {
11831183
throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false);
11841184
}
11851185

src/Symfony/Component/Serializer/Tests/Attribute/DiscriminatorMapTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,15 @@ public function testExceptionWithEmptyTypeProperty()
4040
new DiscriminatorMap(typeProperty: '', mapping: ['foo' => 'FooClass']);
4141
}
4242

43-
public function testExceptionWitEmptyMappingProperty()
43+
public function testExceptionWithEmptyMappingProperty()
4444
{
4545
$this->expectException(InvalidArgumentException::class);
4646
new DiscriminatorMap(typeProperty: 'type', mapping: []);
4747
}
48+
49+
public function testExceptionWithAbsentDefaultValueInMapping()
50+
{
51+
$this->expectException(InvalidArgumentException::class);
52+
new DiscriminatorMap(typeProperty: 'type', mapping: ['foo' => 'FooClass'], defaultValue: 'bar');
53+
}
4854
}

src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummy.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
'first' => AbstractDummyFirstChild::class,
1818
'second' => AbstractDummySecondChild::class,
1919
'third' => AbstractDummyThirdChild::class,
20-
])]
20+
], defaultValue: 'third')]
2121
abstract class AbstractDummy
2222
{
2323
public $foo;

src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
</class>
3636

3737
<class name="Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy">
38-
<discriminator-map type-property="type">
38+
<discriminator-map type-property="type" default-value="second">
3939
<mapping type="first" class="Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild" />
4040
<mapping type="second" class="Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild" />
4141
</discriminator-map>

src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
mapping:
3333
first: 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild'
3434
second: 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild'
35+
default_value: first
3536
attributes:
3637
foo: ~
3738
'Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy':

src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
1616
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryCompiler;
1717
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
18+
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy;
19+
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyFirstChild;
20+
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummySecondChild;
21+
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummyThirdChild;
1822
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy;
1923
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy;
2024
use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy;
@@ -40,13 +44,15 @@ public function testItDumpMetadata()
4044
$classMetatadataFactory = new ClassMetadataFactory(new AttributeLoader());
4145

4246
$dummyMetadata = $classMetatadataFactory->getMetadataFor(Dummy::class);
47+
$abstractDummyMetadata = $classMetatadataFactory->getMetadataFor(AbstractDummy::class);
4348
$maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class);
4449
$serializedNameDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedNameDummy::class);
4550
$serializedPathDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathDummy::class);
4651
$serializedPathInConstructorDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedPathInConstructorDummy::class);
4752

4853
$code = (new ClassMetadataFactoryCompiler())->compile([
4954
$dummyMetadata,
55+
$abstractDummyMetadata,
5056
$maxDepthDummyMetadata,
5157
$serializedNameDummyMetadata,
5258
$serializedPathDummyMetadata,
@@ -56,7 +62,7 @@ public function testItDumpMetadata()
56< 10000 /code>62
file_put_contents($this->dumpPath, $code);
5763
$compiledMetadata = require $this->dumpPath;
5864

59-
$this->assertCount(5, $compiledMetadata);
65+
$this->assertCount(6, $compiledMetadata);
6066

6167
$this->assertArrayHasKey(Dummy::class, $compiledMetadata);
6268
$this->assertEquals([
@@ -69,6 +75,22 @@ public function testItDumpMetadata()
6975
null,
7076
], $compiledMetadata[Dummy::class]);
7177

78+
$this->assertArrayHasKey(AbstractDummy::class, $compiledMetadata);
79+
$this->assertEquals([
80+
[
81+
'foo' => [[], null, null, null],
82+
],
83+
[
84+
'type',
85+
[
86+
'first' => AbstractDummyFirstChild::class,
87+
'second' => AbstractDummySecondChild::class,
88+
'third' => AbstractDummyThirdChild::class,
89+
],
90+
'third',
91+
],
92+
], $compiledMetadata[AbstractDummy::class]);
93+
7294
$this->assertArrayHasKey(MaxDepthDummy::class, $compiledMetadata);
7395
$this->assertEquals([
7496
[

src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function testLoadDiscriminatorMap()
8585
'first' => AbstractDummyFirstChild::class,
8686
'second' => AbstractDummySecondChild::class,
8787
'third' => AbstractDummyThirdChild::class,
88-
]));
88+
], 'third'));
8989

9090
$expected->addAttributeMetadata(new AttributeMetadata('foo'));
9191
$expected->getReflectionClass();

src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function testLoadDiscriminatorMap()
109109
$expected = new ClassMetadata(AbstractDummy::class, new ClassDiscriminatorMapping('type', [
110110
'first' => AbstractDummyFirstChild::class,
111111
'second' => AbstractDummySecondChild::class,
112-
]));
112+
], 'second'));
113113

114114
$expected->addAttributeMetadata(new AttributeMetadata('foo'));
115115

src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public function testLoadDiscriminatorMap()
126126
$expected = new ClassMetadata(AbstractDummy::class, new ClassDiscriminatorMapping('type', [
127127
'first' => AbstractDummyFirstChild::class,
128128
'second' => AbstractDummySecondChild::class,
129-
]));
129+
], 'first'));
130130

131131
$expected->addAttributeMetadata(new AttributeMetadata('foo'));
132132

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,41 @@ public function hasMetadataFor($value): bool
560560
$this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux);
561561
}
562562

563+
public function testDenormalizeWithDiscriminatorMapUsesCorrectClassnameWithDefaultType()
564+
{
565+
$factory = new ClassMetadataFactory(new AttributeLoader());
566+
567+
$loaderMock = new class implements ClassMetadataFactoryInterface {
568+
public function getMetadataFor($value): ClassMetadataInterface
569+
{
570+
if (AbstractDummy::class === $value) {
571+
return new ClassMetadata(
572+
AbstractDummy::class,
573+
new ClassDiscriminatorMapping('type', [
574+
'first' => AbstractDummyFirstChild::class,
575+
'second' => AbstractDummySecondChild::class,
576+
], 'second')
577+
);
578+
}
579+
580+
throw new InvalidArgumentException();
581+
}
582+
583+
public function hasMetadataFor($value): bool
584+
{
585+
return AbstractDummy::class === $value;
586+
}
587+
};
588+
589+
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock);
590+
$normalizer = new AbstractObjectNormalizerDummy($factory, null, new PhpDocExtractor(), $discriminatorResolver);
591+
$serializer = new Serializer([$normalizer]);
592+
$normalizer->setSerializer($serializer);
593+
$normalizedData = $normalizer->denormalize(['foo' => 'foo', 'baz' => 'baz', 'quux' => ['value' => 'quux']], AbstractDummy::class);
594+
595+
$this->assertInstanceOf(DummySecondChildQuux::class, $normalizedData->quux);
596+
}
597+
563598
public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrectClassname()
564599
{
565600
$factory = new ClassMetadataFactory(new AttributeLoader());

0 commit comments

Comments
 (0)
0