From 03d905e0eca2738bbb0c5884314b2b878c94e0e8 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Sat, 21 May 2022 22:26:10 +0200 Subject: [PATCH] [Serializer] Allow to add groups to SerializedName annotation/attribute --- UPGRADE-6.2.md | 5 + .../Serializer/Annotation/SerializedName.php | 23 +++- src/Symfony/Component/Serializer/CHANGELOG.md | 2 + .../Serializer/Mapping/AttributeMetadata.php | 74 +++++++++-- .../Mapping/AttributeMetadataInterface.php | 26 +++- .../Factory/ClassMetadataFactoryCompiler.php | 2 +- .../Mapping/Loader/AnnotationLoader.php | 4 +- .../Mapping/Loader/XmlFileLoader.php | 8 +- .../Mapping/Loader/YamlFileLoader.php | 21 +++- .../serializer-mapping-1.0.xsd | 14 +++ .../MetadataAwareNameConverter.php | 22 ++-- .../Tests/Annotation/SerializedNameTest.php | 17 ++- .../Annotations/SerializedNameDummy.php | 7 ++ .../Attributes/SerializedNameDummy.php | 5 + .../Tests/Fixtures/serialization.xml | 9 ++ .../Tests/Fixtures/serialization.yml | 4 + .../Fixtures/serializer.class.metadata.php | 8 +- .../Tests/Mapping/AttributeMetadataTest.php | 118 +++++++++++++++++- .../ClassMetadataFactoryCompilerTest.php | 32 +++-- .../Mapping/Loader/AnnotationLoaderTest.php | 7 ++ .../Mapping/Loader/XmlFileLoaderTest.php | 4 + .../Mapping/Loader/YamlFileLoaderTest.php | 4 + 22 files changed, 362 insertions(+), 54 deletions(-) diff --git a/UPGRADE-6.2.md b/UPGRADE-6.2.md index c803677a28272..75fb5db3aee05 100644 --- a/UPGRADE-6.2.md +++ b/UPGRADE-6.2.md @@ -108,6 +108,11 @@ Translation * Deprecate `PhpExtractor` in favor of `PhpAstExtractor` * Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed) +Serializer +---------- + + * Add argument `$groups` to `AttributeMetadata::setSerializedName()` and `AttributeMetadata::getSerializedName()` + Validator --------- diff --git a/src/Symfony/Component/Serializer/Annotation/SerializedName.php b/src/Symfony/Component/Serializer/Annotation/SerializedName.php index b6c6027e8568e..e87adeddd549a 100644 --- a/src/Symfony/Component/Serializer/Annotation/SerializedName.php +++ b/src/Symfony/Component/Serializer/Annotation/SerializedName.php @@ -22,13 +22,22 @@ * * @author Fabien Bourigault */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] final class SerializedName { - public function __construct(private string $serializedName) + /** + * @var string[] + */ + private array $groups; + + /** + * @param string|string[] $groups + */ + public function __construct(private string $serializedName, string|array $groups = []) { + $this->groups = (array) $groups; if ('' === $serializedName) { - throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', self::class)); + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', static::class)); } } @@ -36,4 +45,12 @@ public function getSerializedName(): string { return $this->serializedName; } + + /** + * @return string[] + */ + public function getGroups(): array + { + return $this->groups; + } } diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 3342ada2fea86..9478572c5c3b8 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -12,6 +12,8 @@ CHANGELOG * Change the signature of `ClassMetadataInterface::setClassDiscriminatorMapping()` to `setClassDiscriminatorMapping(?ClassDiscriminatorMapping)` * Add option YamlEncoder::YAML_INDENTATION to YamlEncoder constructor options to configure additional indentation for each level of nesting. This allows configuring indentation in the service configuration. * Add `SerializedPath` annotation to flatten nested attributes + * Add serialized name group support + * Add argument `$groups` to `AttributeMetadata::setSerializedName()` and `AttributeMetadata::getSerializedName()` 6.1 --- diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 22cc711a7e2c8..9cc1ca3aca311 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -42,13 +42,13 @@ class AttributeMetadata implements AttributeMetadataInterface public $maxDepth; /** - * @var string|null + * @var array An array of serialized names by group * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use - * {@link getSerializedName()} instead. + * {@link getSerializedNames()} instead. */ - public $serializedName; + public $serializedName = []; /** * @internal This property is public in order to reduce the size of the @@ -116,20 +116,68 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - public function setSerializedName(string $serializedName = null) + public function setSerializedNames(array $serializedNames): void + { + $this->serializedName = $serializedNames; + } + + /** + * Set a serialization name for given groups. + * + * @param string[] $groups + */ + public function setSerializedName(string $serializedName = null/* , array $groups = [] */) { if (1 > \func_num_args()) { trigger_deprecation('symfony/serializer', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); } + + if (\func_num_args() < 2) { + $groups = []; + } else { + $groups = func_get_arg(1); + + if (!\is_array($groups)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, "%s" given.', __METHOD__, get_debug_type($groups))); + } + } - $this->serializedName = $serializedName; + foreach ($groups ?: ['*'] as $group) { + $this->serializedName[$group] = $serializedName; + } } - public function getSerializedName(): ?string + public function getSerializedNames(): array { return $this->serializedName; } + /** + * Gets the serialization name for given groups. + * + * @param string[] $groups + */ + public function getSerializedName(/* array $groups = [] */): ?string + { + if (\func_num_args() < 1) { + $groups = []; + } else { + $groups = func_get_arg(0); + + if (!\is_array($groups)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be array, "%s" given.', __METHOD__, get_debug_type($groups))); + } + } + + foreach ($groups as $group) { + if (isset($this->serializedName[$group])) { + return $this->serializedName[$group]; + } + } + + return $this->serializedName['*'] ?? null; + } + public function setSerializedPath(PropertyPath $serializedPath = null): void { $this->serializedPath = $serializedPath; @@ -210,7 +258,7 @@ public function merge(AttributeMetadataInterface $attributeMetadata) // Overwrite only if not defined $this->maxDepth ??= $attributeMetadata->getMaxDepth(); - $this->serializedName ??= $attributeMetadata->getSerializedName(); + $this->serializedName ??= $attributeMetadata->getSerializedNames(); $this->serializedPath ??= $attributeMetadata->getSerializedPath(); // Overwrite only if both contexts are empty @@ -233,4 +281,16 @@ public function __sleep(): array { return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts']; } + + public function __wakeup() + { + // Preserve compatibility with existing serialized payloads + if (null === $this->serializedName) { + $this->serializedName = []; + } elseif (\is_string($this->serializedName)) { + $this->serializedName = [ + '*' => $this->serializedName, + ]; + } + } } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 67ca8d3c0631c..70d38db2bf3e0 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -52,14 +52,32 @@ public function setMaxDepth(?int $maxDepth); public function getMaxDepth(): ?int; /** - * Sets the serialization name for this attribute. + * Sets the serialization names for given groups. + * + * @param array $serializedNames + */ + public function setSerializedNames(array $serializedNames): void; + + /** + * Set a serialization name for given groups. + * + * @param string[] $groups */ - public function setSerializedName(?string $serializedName); + public function setSerializedName(?string $serializedName/* , array $groups = [] */); /** - * Gets the serialization name for this attribute. + * Gets the serialization name for given groups. + * + * @param string[] $groups + */ + public function getSerializedName(/* array $groups = [] */): ?string; + + /** + * Gets all the serialization names per group ("*" being the default serialization name). + * + * @return array */ - public function getSerializedName(): ?string; + public function getSerializedNames(): array; public function setSerializedPath(?PropertyPath $serializedPath): void; diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php b/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php index f01fe9ce2f085..ea82f68ca9529 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php @@ -47,7 +47,7 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string $attributesMetadata[$attributeMetadata->getName()] = [ $attributeMetadata->getGroups(), $attributeMetadata->getMaxDepth(), - $attributeMetadata->getSerializedName(), + $attributeMetadata->getSerializedNames(), $attributeMetadata->getSerializedPath(), ]; } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php index cfcee8bd5013d..052bd9cac14de 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php @@ -82,7 +82,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } elseif ($annotation instanceof MaxDepth) { $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); } elseif ($annotation instanceof SerializedName) { - $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); + $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName(), $annotation->getGroups()); } elseif ($annotation instanceof SerializedPath) { $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); } elseif ($annotation instanceof Ignore) { @@ -137,7 +137,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedName($annotation->getSerializedName()); + $attributeMetadata->setSerializedName($annotation->getSerializedName(), $annotation->getGroups()); } elseif ($annotation instanceof SerializedPath) { if (!$accessorOrMutator) { throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php index 3dc3b96c69c94..c0f00936752aa 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php @@ -67,7 +67,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } if (isset($attribute['serialized-name'])) { - $attributeMetadata->setSerializedName((string) $attribute['serialized-name']); + $attributeMetadata->setSerializedName((string) $attribute['serialized-name'], []); + } + + foreach ($attribute->serialized_name as $node) { + $serializedName = (string) $node['name']; + $groups = (array) $node->group; + $attributeMetadata->setSerializedName('' === $serializedName ? null : $serializedName, $groups); } if (isset($attribute['serialized-path'])) { diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php index 0fdfcc511093a..993cf6542e8ca 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php @@ -86,11 +86,24 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } if (isset($data['serialized_name'])) { - if (!\is_string($data['serialized_name']) || '' === $data['serialized_name']) { - throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); - } + $serializedNames = $data['serialized_name']; - $attributeMetadata->setSerializedName($data['serialized_name']); + if (\is_string($serializedNames)) { + if ('' === $serializedNames) { + throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } + $attributeMetadata->setSerializedName($serializedNames, []); + } elseif (\is_array($serializedNames)) { + foreach ($serializedNames as $serializedName => $groups) { + if (!\is_string($serializedName) || !$serializedName) { + throw new MappingException(sprintf('The key for "serialized_name" array must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } + + $attributeMetadata->setSerializedName($serializedName, (array) $groups); + } + } else { + throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string or an array of serialized name/groups in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } } if (isset($data['serialized_path'])) { diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd index f5f6cca9f0f54..50a3019d0b60d 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd +++ b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd @@ -54,6 +54,19 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php index 920e81869561e..93fb159b52e8f 100644 --- a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php @@ -46,7 +46,7 @@ public function normalize(string $propertyName, string $class = null, string $fo } if (!\array_key_exists($class, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$class])) { - self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class); + self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class, $context); } return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); @@ -66,7 +66,7 @@ public function denormalize(string $propertyName, string $class = null, string $ return self::$denormalizeCache[$cacheKey][$propertyName] ?? $this->denormalizeFallback($propertyName, $class, $format, $context); } - private function getCacheValueForNormalization(string $propertyName, string $class): ?string + private function getCacheValueForNormalization(string $propertyName, string $class, array $context): ?string { if (!$this->metadataFactory->hasMetadataFor($class)) { return null; @@ -77,11 +77,14 @@ private function getCacheValueForNormalization(string $propertyName, string $cla return null; } - if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { + $groups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + + $serializedName = $attributesMetadata[$propertyName]->getSerializedName($groups); + if (null !== $serializedName && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $propertyName, $class)); } - return $attributesMetadata[$propertyName]->getSerializedName() ?? null; + return $serializedName ?? null; } private function normalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = []): string @@ -114,23 +117,24 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex $cache = []; foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) { - if (null === $metadata->getSerializedName()) { + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + if (null === $serializedName = $metadata->getSerializedName($contextGroups)) { continue; } - if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { + if (null !== $metadata->getSerializedPath()) { throw new LogicException(sprintf('Found SerializedName and SerializedPath annotations on property "%s" of class "%s".', $name, $class)); } $groups = $metadata->getGroups(); - if (!$groups && ($context[AbstractNormalizer::GROUPS] ?? [])) { + if (!$groups && $contextGroups) { continue; } - if ($groups && !array_intersect($groups, (array) ($context[AbstractNormalizer::GROUPS] ?? []))) { + if ($groups && !array_intersect($groups, $contextGroups)) { continue; } - $cache[$metadata->getSerializedName()] = $name; + $cache[$serializedName] = $name; } return $cache; diff --git a/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php b/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php index f4dd82d7fad9b..a1744fd1308a6 100644 --- a/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php +++ b/src/Symfony/Component/Serializer/Tests/Annotation/SerializedNameTest.php @@ -31,6 +31,21 @@ public function testNotAStringSerializedNameParameter() public function testSerializedNameParameters() { $maxDepth = new SerializedName('foo'); - $this->assertEquals('foo', $maxDepth->getSerializedName()); + $this->assertSame('foo', $maxDepth->getSerializedName()); + $this->assertSame([], $maxDepth->getGroups()); + } + + public function testSerializedNameParametersWithArrayGroups() + { + $maxDepth = new SerializedName('foo', ['bar', 'baz']); + $this->assertSame('foo', $maxDepth->getSerializedName()); + $this->assertSame(['bar', 'baz'], $maxDepth->getGroups()); + } + + public function testSerializedNameParametersWithStringGroup() + { + $maxDepth = new SerializedName('foo', 'bar'); + $this->assertSame('foo', $maxDepth->getSerializedName()); + $this->assertSame(['bar'], $maxDepth->getGroups()); } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedNameDummy.php index 1eaa579b466fa..7fbc840f3f84a 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedNameDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Annotations/SerializedNameDummy.php @@ -32,6 +32,13 @@ class SerializedNameDummy */ public $child; + /** + * @SerializedName("nameOne") + * @SerializedName("nameTwo", groups={"a", "b"}) + * @SerializedName("nameThree", groups="c") + */ + public $corge; + /** * @SerializedName("qux") */ diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php index fe0a67e83cf67..c728bd52d59ef 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php @@ -30,6 +30,11 @@ class SerializedNameDummy */ public $child; + #[SerializedName('nameOne')] + #[SerializedName('nameTwo', ['a', 'b'])] + #[SerializedName('nameThree', ['c'])] + public $corge; + #[SerializedName('qux')] public function getBar() { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml index 4890f56bfd0f9..52396bd55aeb2 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml @@ -23,6 +23,15 @@ + + + groupA + groupB + + + groupC + + diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml index 7519b979efa96..c57fa87059c25 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml @@ -16,6 +16,10 @@ serialized_name: 'baz' bar: serialized_name: 'qux' + quux: + serialized_name: + nameOne: ['groupA', 'groupB'] + nameTwo: 'groupC' 'Symfony\Component\Serializer\Tests\Fixtures\Annotations\SerializedPathDummy': attributes: three: diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php b/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php index 4773d22675091..d05977f69c47c 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php @@ -5,10 +5,10 @@ return [ 'Symfony\Component\Serializer\Tests\Fixtures\Dummy' => [ [ - 'foo' => [[], null, null], - 'bar' => [[], null, null], - 'baz' => [[], null, null], - 'qux' => [[], null, null], + 'foo' => [[], null, []], + 'bar' => [[], null, []], + 'baz' => [[], null, []], + 'qux' => [[], null, []], ], null, ], diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php index 1f1b291beb7f0..02dd0e7dde428 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php @@ -51,12 +51,64 @@ public function testMaxDepth() $this->assertEquals(69, $attributeMetadata->getMaxDepth()); } - public function testSerializedName() + public function testSerializedNames() { $attributeMetadata = new AttributeMetadata('name'); - $attributeMetadata->setSerializedName('serialized_name'); - $this->assertEquals('serialized_name', $attributeMetadata->getSerializedName()); + $this->assertSame([], $attributeMetadata->getSerializedNames()); + + $attributeMetadata->setSerializedNames([ + 'foo-group' => 'foo', + 'bar-group' => 'bar', + ]); + + $this->assertEquals([ + 'foo-group' => 'foo', + 'bar-group' => 'bar', + ], $attributeMetadata->getSerializedNames()); + + $attributeMetadata->setSerializedName('baz', ['baz-group']); + $this->assertEquals([ + 'foo-group' => 'foo', + 'bar-group' => 'bar', + 'baz-group' => 'baz', + ], $attributeMetadata->getSerializedNames()); + + $attributeMetadata->setSerializedName('bar', ['baz-group']); + $this->assertEquals([ + 'foo-group' => 'foo', + 'bar-group' => 'bar', + 'baz-group' => 'bar', + ], $attributeMetadata->getSerializedNames()); + + $this->assertNull($attributeMetadata->getSerializedName(['unknown'])); + $this->assertSame('bar', $attributeMetadata->getSerializedName(['bar-group'])); + $this->assertSame('foo', $attributeMetadata->getSerializedName(['foo-group', 'bar-group'])); + $this->assertSame('bar', $attributeMetadata->getSerializedName(['bar-group', 'foo-group'])); + } + + public function testSerializedNamesWithoutSpecificGroup() + { + $attributeMetadata = new AttributeMetadata('name'); + + $attributeMetadata->setSerializedName('foo', []); + $this->assertSame('foo', $attributeMetadata->getSerializedName([])); + $this->assertSame('foo', $attributeMetadata->getSerializedName(['bar'])); + + $this->assertSame([ + '*' => 'foo', + ], $attributeMetadata->getSerializedNames()); + } + + public function testNullSerializedNames() + { + $attributeMetadata = new AttributeMetadata('name'); + + $attributeMetadata->setSerializedName(null, []); + + $this->assertSame([ + '*' => null, + ], $attributeMetadata->getSerializedNames()); } public function testSerializedPath() @@ -133,6 +185,10 @@ public function testMerge() $attributeMetadata1 = new AttributeMetadata('a1'); $attributeMetadata1->addGroup('a'); $attributeMetadata1->addGroup('b'); + $attributeMetadata1->setSerializedNames([ + 'group-a' => 'name-a', + 'group-b' => 'name-b', + ]); $attributeMetadata2 = new AttributeMetadata('a2'); $attributeMetadata2->addGroup('a'); @@ -149,7 +205,10 @@ public function testMerge() $this->assertEquals(['a', 'b', 'c'], $attributeMetadata1->getGroups()); $this->assertEquals(2, $attributeMetadata1->getMaxDepth()); - $this->assertEquals('a3', $attributeMetadata1->getSerializedName()); + $this->assertEquals([ + 'group-a' => 'name-a', + 'group-b' => 'name-b', + ], $attributeMetadata1->getSerializedNames()); $this->assertEquals($serializedPath, $attributeMetadata1->getSerializedPath()); $this->assertSame(['a' => ['foo' => 'bar']], $attributeMetadata1->getNormalizationContexts()); $this->assertSame(['c' => ['baz' => 'qux']], $attributeMetadata1->getDenormalizationContexts()); @@ -157,6 +216,27 @@ public function testMerge() } public function testContextsNotMergedIfAlreadyDefined() + { + $attributeMetadata1 = new AttributeMetadata('a1'); + $attributeMetadata1->setSerializedNames([ + 'group-a' => 'name-a', + 'group-b' => 'name-b', + ]); + + $attributeMetadata2 = new AttributeMetadata('a2'); + $attributeMetadata2->setSerializedNames([ + 'group-b' => 'name-c', + ]); + + $attributeMetadata1->merge($attributeMetadata2); + + self::assertSame([ + 'group-a' => 'name-a', + 'group-b' => 'name-b', + ], $attributeMetadata1->getSerializedNames()); + } + + public function testSerializedNamesNotMergedIfAlreadyDefined() { $attributeMetadata1 = new AttributeMetadata('a1'); $attributeMetadata1->setNormalizationContextForGroups(['foo' => 'not overridden'], ['a']); @@ -178,11 +258,39 @@ public function testSerialize() $attributeMetadata->addGroup('a'); $attributeMetadata->addGroup('b'); $attributeMetadata->setMaxDepth(3); - $attributeMetadata->setSerializedName('serialized_name'); + $attributeMetadata->setSerializedNames([ + 'group-a' => 'name-a', + ]); $serializedPath = new PropertyPath('[serialized][path]'); $attributeMetadata->setSerializedPath($serializedPath); $serialized = serialize($attributeMetadata); $this->assertEquals($attributeMetadata, unserialize($serialized)); } + + public function testSerializeSerializedNameNullCompatibility() + { + $attributeMetadata = new AttributeMetadata('attribute'); + $attributeMetadata->serializedName = null; + + $serialized = serialize($attributeMetadata); + $unserialized = unserialize($serialized); + + $this->assertSame([], $unserialized->serializedName); + $this->assertNull($unserialized->getSerializedName()); + } + + public function testSerializeSerializedNameStringCompatibility() + { + $attributeMetadata = new AttributeMetadata('attribute'); + $attributeMetadata->serializedName = 'foo'; + + $serialized = serialize($attributeMetadata); + $unserialized = unserialize($serialized); + + $this->assertSame([ + '*' => 'foo', + ], $unserialized->serializedName); + $this->assertSame('foo', $unserialized->getSerializedName()); + } } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 5ce1931ba0cab..d0abaa2bec06c 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -62,10 +62,10 @@ public function testItDumpMetadata() $this->assertArrayHasKey(Dummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], null, null, null], - 'bar' => [[], null, null, null], - 'baz' => [[], null, null, null], - 'qux' => [[], null, null, null], + 'foo' => [[], null, [], null], + 'bar' => [[], null, [], null], + 'baz' => [[], null, [], null], + 'qux' => [[], null, [], null], ], null, ], $compiledMetadata[Dummy::class]); @@ -73,9 +73,9 @@ public function testItDumpMetadata() $this->assertArrayHasKey(MaxDepthDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], 2, null, null], - 'bar' => [[], 3, null, null], - 'child' => [[], null, null, null], + 'foo' => [[], 2, [], null], + 'bar' => [[], 3, [], null], + 'child' => [[], null, [], null], ], null, ], $compiledMetadata[MaxDepthDummy::class]); @@ -83,10 +83,16 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedNameDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'foo' => [[], null, 'baz', null], - 'bar' => [[], null, 'qux', null], - 'quux' => [[], null, null, null], - 'child' => [[], null, null, null], + 'foo' => [[], null, ['*' => 'baz'], null], + 'bar' => [[], null, ['*' => 'qux'], null], + 'quux' => [[], null, [], null], + 'child' => [[], null, [], null], + 'corge' => [[], null, [ + 'a' => 'nameTwo', + 'b' => 'nameTwo', + 'c' => 'nameThree', + '*' => 'nameOne', + ], null], ], null, ], $compiledMetadata[SerializedNameDummy::class]); @@ -94,8 +100,8 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedPathDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'three' => [[], null, null, '[one][two]'], - 'seven' => [[], null, null, '[three][four]'], + 'three' => [[], null, [], '[one][two]'], + 'seven' => [[], null, [], '[three][four]'], ], null, ], $compiledMetadata[SerializedPathDummy::class]); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php index 0747ca3f54c7a..0d8cd5ea9269c 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -89,8 +89,15 @@ public function testLoadSerializedName() $this->loader->loadClassMetadata($classMetadata); $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals([ + 'a' => 'nameTwo', + 'b' => 'nameTwo', + 'c' => 'nameThree', + '*' => 'nameOne', + ], $attributesMetadata['corge']->getSerializedNames()); } public function testLoadSerializedPath() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index b1e9ed7222636..a24463d319594 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -82,6 +82,10 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + + $this->assertEquals('nameDefault', $attributesMetadata['quux']->getSerializedName()); + $this->assertEquals('nameOne', $attributesMetadata['quux']->getSerializedName(['groupB'])); + $this->assertEquals('nameTwo', $attributesMetadata['quux']->getSerializedName(['groupC'])); } public function testSerializedPath() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index bbe0a99aeab89..27b854b684cae 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -96,6 +96,10 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + + $this->assertEquals('nameOne', $attributesMetadata['quux']->getSerializedName(['groupA'])); + $this->assertEquals('nameOne', $attributesMetadata['quux']->getSerializedName(['groupB'])); + $this->assertEquals('nameTwo', $attributesMetadata['quux']->getSerializedName(['groupC'])); } public function testSerializedPath()