Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9d089bd

Browse filesBrowse files
committed
[Serializer] Add defaultType to DiscriminatorMap
1 parent 946883f commit 9d089bd
Copy full SHA for 9d089bd

18 files changed

+98
-11
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Attribute/DiscriminatorMap.php
+11Lines 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 $defaultType The fallback value if nothing specified by $typeProperty
2526
*
2627
* @throws InvalidArgumentException
2728
*/
2829
public function __construct(
2930
private readonly string $typeProperty,
3031
private readonly array $mapping,
32+
private readonly ?string $defaultType = 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->defaultType && !\array_key_exists($this->defaultType, $this->mapping)) {
43+
throw new InvalidArgumentException(\sprintf('Default type "%s" given to "%s" must be present in "mapping" types.', $this->defaultType, 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 getDefaultType(): ?string
58+
{
59+
return $this->defaultType;
60+
}
5061
}
5162

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

‎src/Symfony/Component/Serializer/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/CHANGELOG.md
+1Lines 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 `defaultType` to `DiscriminatorMap`
1011

1112
7.2
1213
---

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/ClassDiscriminatorMapping.php
+6Lines 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 $defaultType = 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 getDefaultType(): ?string
67+
{
68+
return $this->defaultType;
69+
}
6470
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php
+1Lines 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()->getDefaultType(),
5859
] : null;
5960

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php
+1-1Lines 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->getDefaultType())),
6363
$attribute instanceof Groups => $classGroups = $attribute->getGroups(),
6464
$attribute instanceof Context => $classContextAttribute = $attribute,
6565
default => null,

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php
+2-1Lines 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-type'} ?? null
111112
));
112113
}
113114

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php
+2-1Lines 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_type'] ?? null
137138
));
138139
}
139140

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd
+1Lines 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-type" type="xsd:string" />
5051
</xsd:complexType>
5152

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
+1-1Lines 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->getDefaultType()) {
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Attribute/DiscriminatorMapTest.php
+8-1Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,16 @@ 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 testExceptionWithMissingDefaultTypeInMapping()
50+
{
51+
$this->expectException(InvalidArgumentException::class);
52+
$this->expectExceptionMessage(sprintf('Default type "bar" given to "%s" must be present in "mapping" types.', DiscriminatorMap::class));
53+
new DiscriminatorMap(typeProperty: 'type', mapping: ['foo' => 'FooClass'], defaultType: 'bar');
54+
}
4855
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummy.php
+1-1Lines 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+
], defaultType: 'third')]
2121
abstract class AbstractDummy
2222
{
2323
public $foo;

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml
+1-1Lines 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-type="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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml
+1Lines 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_type: first
3536
attributes:
3637
foo: ~
3738
'Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy':

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php
+23-1Lines 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()
5662
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
+35Lines 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(sprintf('"%s" is not handled.', $value));
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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.