diff --git a/src/Symfony/Component/Serializer/Annotation/SerializedName.php b/src/Symfony/Component/Serializer/Annotation/SerializedName.php index 167064a0b9d17..899850fb4cf56 100644 --- a/src/Symfony/Component/Serializer/Annotation/SerializedName.php +++ b/src/Symfony/Component/Serializer/Annotation/SerializedName.php @@ -14,7 +14,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\SerializedName::class); if (false) { - #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedName extends \Symfony\Component\Serializer\Attribute\SerializedName { } diff --git a/src/Symfony/Component/Serializer/Annotation/SerializedPath.php b/src/Symfony/Component/Serializer/Annotation/SerializedPath.php index 76e4c7eff2c5c..919c2da34efdf 100644 --- a/src/Symfony/Component/Serializer/Annotation/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Annotation/SerializedPath.php @@ -14,7 +14,7 @@ class_exists(\Symfony\Component\Serializer\Attribute\SerializedPath::class); if (false) { - #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] + #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedPath extends \Symfony\Component\Serializer\Attribute\SerializedPath { } diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedName.php b/src/Symfony/Component/Serializer/Attribute/SerializedName.php index f1c6cefe2be64..45ee6a146d6eb 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedName.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedName.php @@ -16,23 +16,41 @@ /** * @author Fabien Bourigault */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedName { + private array $groups; + /** - * @param string $serializedName The name of the property as it will be serialized + * @param string $serializedName The name of the property as it will be serialized + * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(private readonly string $serializedName) - { + public function __construct( + private readonly string $serializedName, + string|array $groups = ['*'], + ) { if ('' === $serializedName) { throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } + + $this->groups = ((array) $groups) ?: ['*']; + + foreach ($this->groups as $group) { + if (!\is_string($group)) { + throw new InvalidArgumentException(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + } + } } public function getSerializedName(): string { return $this->serializedName; } + + public function getGroups(): array + { + return $this->groups; + } } if (!class_exists(\Symfony\Component\Serializer\Annotation\SerializedName::class, false)) { diff --git a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php index 71254bfb96fab..9c3910754edbd 100644 --- a/src/Symfony/Component/Serializer/Attribute/SerializedPath.php +++ b/src/Symfony/Component/Serializer/Attribute/SerializedPath.php @@ -18,27 +18,43 @@ /** * @author Tobias Bönner */ -#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] class SerializedPath { private PropertyPath $serializedPath; + private array $groups; + /** - * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation + * @param string $serializedPath A path using a valid PropertyAccess syntax where the value is stored in a normalized representation + * @param string|string[] $groups The groups to use when serializing or deserializing */ - public function __construct(string $serializedPath) + public function __construct(string $serializedPath, string|array $groups = ['*']) { try { $this->serializedPath = new PropertyPath($serializedPath); } catch (InvalidPropertyPathException $pathException) { throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } + + $this->groups = ((array) $groups) ?: ['*']; + + foreach ($this->groups as $group) { + if (!\is_string($group)) { + throw new InvalidArgumentException(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + } + } } public function getSerializedPath(): PropertyPath { return $this->serializedPath; } + + public function getGroups(): array + { + return $this->groups; + } } if (!class_exists(\Symfony\Component\Serializer\Annotation\SerializedPath::class, false)) { diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 9b7a1fac345f0..9d187ed3541ea 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG default contexts, name converters, sets of normalizers and encoders * Add support for collection profiles of multiple serializer instances * Deprecate `AdvancedNameConverterInterface`, use `NameConverterInterface` instead + * Add support for serialized names and paths configuration per group 7.1 --- diff --git a/src/Symfony/Component/Serializer/Command/DebugCommand.php b/src/Symfony/Component/Serializer/Command/DebugCommand.php index 7df4d6bc8041d..e6fc4c707fae2 100644 --- a/src/Symfony/Component/Serializer/Command/DebugCommand.php +++ b/src/Symfony/Component/Serializer/Command/DebugCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; @@ -100,8 +101,8 @@ private function getAttributesData(ClassMetadataInterface $classMetadata): array $data[$attributeMetadata->getName()] = [ 'groups' => $attributeMetadata->getGroups(), 'maxDepth' => $attributeMetadata->getMaxDepth(), - 'serializedName' => $attributeMetadata->getSerializedName(), - 'serializedPath' => $attributeMetadata->getSerializedPath() ? (string) $attributeMetadata->getSerializedPath() : null, + 'serializedNames' => AttributeMetadata::getSerializedNamesFromAttributeMetadata($attributeMetadata), + 'serializedPaths' => array_map('strval', AttributeMetadata::getSerializedPathsFromAttributeMetadata($attributeMetadata)), 'ignore' => $attributeMetadata->isIgnored(), 'normalizationContexts' => $attributeMetadata->getNormalizationContexts(), 'denormalizationContexts' => $attributeMetadata->getDenormalizationContexts(), diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 12d1cf6bfd59e..a39ab2dac56ea 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -40,18 +40,22 @@ class AttributeMetadata implements AttributeMetadataInterface public ?int $maxDepth = null; /** + * @var array Serialized names per group name ("*" applies to all groups) + * * @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 ?string $serializedName = null; + public array $serializedNames = []; /** + * @var array Serialized paths per group name ("*" applies to all groups) + * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use - * {@link getSerializedPath()} instead. + * {@link getSerializedPaths()} instead. */ - public ?PropertyPath $serializedPath = null; + public array $serializedPaths = []; /** * @internal This property is public in order to reduce the size of the @@ -110,24 +114,70 @@ public function getMaxDepth(): ?int return $this->maxDepth; } - public function setSerializedName(?string $serializedName): void + public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void + { + $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; + + if (isset($serializedName)) { + foreach ($groups as $group) { + $this->serializedNames[$group] = $serializedName; + } + } else { + foreach ($groups as $group) { + unset($this->serializedNames[$group]); + } + } + } + + public function getSerializedName(/* array $groups = ['*'] */): ?string + { + $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; + + foreach ($groups as $group) { + if (isset($this->serializedNames[$group])) { + return $this->serializedNames[$group]; + } + } + + return $this->serializedNames['*'] ?? null; + } + + public function getSerializedNames(): array { - $this->serializedName = $serializedName; + return $this->serializedNames; } - public function getSerializedName(): ?string + public function setSerializedPath(?PropertyPath $serializedPath = null /* , array $groups = ['*'] */): void { - return $this->serializedName; + $groups = 2 <= \func_num_args() ? (func_get_arg(1) ?: ['*']) : ['*']; + + if (isset($serializedPath)) { + foreach ($groups as $group) { + $this->serializedPaths[$group] = $serializedPath; + } + } else { + foreach ($groups as $group) { + unset($this->serializedPaths[$group]); + } + } } - public function setSerializedPath(?PropertyPath $serializedPath = null): void + public function getSerializedPath(/* array $groups = ['*'] */): ?PropertyPath { - $this->serializedPath = $serializedPath; + $groups = 1 <= \func_num_args() ? func_get_arg(0) : ['*']; + + foreach ($groups as $group) { + if (isset($this->serializedPaths[$group])) { + return $this->serializedPaths[$group]; + } + } + + return $this->serializedPaths['*'] ?? null; } - public function getSerializedPath(): ?PropertyPath + public function getSerializedPaths(): array { - return $this->serializedPath; + return $this->serializedPaths; } public function setIgnore(bool $ignore): void @@ -200,8 +250,16 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void // Overwrite only if not defined $this->maxDepth ??= $attributeMetadata->getMaxDepth(); - $this->serializedName ??= $attributeMetadata->getSerializedName(); - $this->serializedPath ??= $attributeMetadata->getSerializedPath(); + + // Overwrite only if serialized names are empty + if (!$this->serializedNames) { + $this->serializedNames = self::getSerializedNamesFromAttributeMetadata($attributeMetadata); + } + + // Overwrite only if serialized paths are empty + if (!$this->serializedPaths) { + $this->serializedPaths = self::getSerializedPathsFromAttributeMetadata($attributeMetadata); + } // Overwrite only if both contexts are empty if (!$this->normalizationContexts && !$this->denormalizationContexts) { @@ -214,6 +272,48 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void } } + /** + * BC layer for extraction of serialized names from attribute metadata. + * Can be removed as soon as AttributeMetadataInterface::getSerializedNames() become part of the interface. + * + * @internal + * + * @return array + */ + public static function getSerializedNamesFromAttributeMetadata(AttributeMetadataInterface $attributeMetadata): array + { + if (method_exists($attributeMetadata, 'getSerializedNames')) { + return $attributeMetadata->getSerializedNames(); + } + + if (null !== $serializedName = $attributeMetadata->getSerializedName()) { + return ['*' => $serializedName]; + } + + return []; + } + + /** + * BC layer for extraction of serialized paths from attribute metadata. + * Can be removed as soon as AttributeMetadataInterface::getSerializedPaths() become part of the interface. + * + * @internal + * + * @return array + */ + public static function getSerializedPathsFromAttributeMetadata(AttributeMetadataInterface $attributeMetadata): array + { + if (method_exists($attributeMetadata, 'getSerializedPaths')) { + return $attributeMetadata->getSerializedPaths(); + } + + if (null !== $serializedPath = $attributeMetadata->getSerializedPath()) { + return ['*' => $serializedPath]; + } + + return []; + } + /** * Returns the names of the properties that should be serialized. * @@ -221,6 +321,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata): void */ public function __sleep(): array { - return ['name', 'groups', 'maxDepth', 'serializedName', 'serializedPath', 'ignore', 'normalizationContexts', 'denormalizationContexts']; + return ['name', 'groups', 'maxDepth', 'serializedNames', 'serializedPaths', 'ignore', 'normalizationContexts', 'denormalizationContexts']; } } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 9d430602c50a0..bc8c08af2bcbf 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -21,6 +21,9 @@ * @internal * * @author Kévin Dunglas + * + * @method string[] getSerializedNames() Gets all the serialized names per group. + * @method PropertyPath[] getSerializedPaths() Gets all the serialized paths per group. */ interface AttributeMetadataInterface { @@ -53,17 +56,31 @@ public function getMaxDepth(): ?int; /** * Sets the serialization name for this attribute. + * + * @param string[] $groups */ - public function setSerializedName(?string $serializedName): void; + public function setSerializedName(?string $serializedName /* , array $groups = ['*'] */): void; /** * Gets the serialization name for this attribute. + * + * @param string[] $groups */ - public function getSerializedName(): ?string; + public function getSerializedName(/* array $groups = ['*'] */): ?string; - public function setSerializedPath(?PropertyPath $serializedPath): void; + /** + * Sets the serialization path for this attribute. + * + * @param string[] $groups + */ + public function setSerializedPath(?PropertyPath $serializedPath /* , array $groups = ['*'] */): void; - public function getSerializedPath(): ?PropertyPath; + /** + * Gets the serialization path for this attribute. + * + * @param string[] $groups + */ + public function getSerializedPath(/* array $groups = ['*'] */): ?PropertyPath; /** * Sets if this attribute must be ignored or not. diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php b/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php index 1e9202b7d81fb..0d600f68a07f5 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/ClassMetadataFactoryCompiler.php @@ -47,8 +47,10 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string $attributesMetadata[$attributeMetadata->getName()] = [ $attributeMetadata->getGroups(), $attributeMetadata->getMaxDepth(), - $attributeMetadata->getSerializedName(), - $attributeMetadata->getSerializedPath(), + method_exists($attributeMetadata, 'getSerializedNames') + ? $attributeMetadata->getSerializedNames() : $attributeMetadata->getSerializedName(), + method_exists($attributeMetadata, 'getSerializedPaths') + ? $attributeMetadata->getSerializedPaths() : $attributeMetadata->getSerializedPath(), ]; } diff --git a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php index ec25d74406d49..b5f8e80d6bbe2 100644 --- a/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/src/Symfony/Component/Serializer/Mapping/Factory/CompiledClassMetadataFactory.php @@ -53,7 +53,12 @@ public function getMetadataFor(string|object $value): ClassMetadataInterface $classMetadata = new ClassMetadata($className); foreach ($this->compiledClassMetadata[$className][0] as $name => $compiledAttributesMetadata) { $classMetadata->attributesMetadata[$name] = $attributeMetadata = new AttributeMetadata($name); - [$attributeMetadata->groups, $attributeMetadata->maxDepth, $attributeMetadata->serializedName] = $compiledAttributesMetadata; + [$attributeMetadata->groups, $attributeMetadata->maxDepth, $serializedNames] = $compiledAttributesMetadata; + $attributeMetadata->serializedNames = match (true) { + \is_array($serializedNames) => $serializedNames, + null === $serializedNames => [], + default => ['*' => $serializedNames], + }; } $classMetadata->classDiscriminatorMapping = $this->compiledClassMetadata[$className][1] ? new ClassDiscriminatorMapping(...$this->compiledClassMetadata[$className][1]) diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php index 13c59d1c35ab6..5cf895f7f7e4a 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/AttributeLoader.php @@ -95,8 +95,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool match (true) { $attribute instanceof MaxDepth => $attributeMetadata->setMaxDepth($attribute->getMaxDepth()), - $attribute instanceof SerializedName => $attributeMetadata->setSerializedName($attribute->getSerializedName()), - $attribute instanceof SerializedPath => $attributeMetadata->setSerializedPath($attribute->getSerializedPath()), + $attribute instanceof SerializedName => $this->setAttributeSerializedNameForGroups($attribute, $attributeMetadata), + $attribute instanceof SerializedPath => $this->setAttributeSerializedPathForGroups($attribute, $attributeMetadata), $attribute instanceof Ignore => $attributeMetadata->setIgnore(true), $attribute instanceof Context => $this->setAttributeContextsForGroups($attribute, $attributeMetadata), default => null, @@ -146,13 +146,13 @@ 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($attribute->getSerializedName()); + $this->setAttributeSerializedNameForGroups($attribute, $attributeMetadata); } elseif ($attribute 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)); } - $attributeMetadata->setSerializedPath($attribute->getSerializedPath()); + $this->setAttributeSerializedPathForGroups($attribute, $attributeMetadata); } elseif ($attribute instanceof Ignore) { if ($accessorOrMutator) { $attributeMetadata->setIgnore(true); @@ -195,6 +195,16 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } } + private function setAttributeSerializedNameForGroups(SerializedName $attribute, AttributeMetadataInterface $attributeMetadata): void + { + $attributeMetadata->setSerializedName($attribute->getSerializedName(), $attribute->getGroups()); + } + + private function setAttributeSerializedPathForGroups(SerializedPath $attribute, AttributeMetadataInterface $attributeMetadata): void + { + $attributeMetadata->setSerializedPath($attribute->getSerializedPath(), $attribute->getGroups()); + } + private function setAttributeContextsForGroups(Context $attribute, AttributeMetadataInterface $attributeMetadata): void { $context = $attribute->getContext(); diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php index 44ba89df18072..3ff7be7fe16f6 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php @@ -78,6 +78,22 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata->setIgnore(XmlUtils::phpize($attribute['ignore'])); } + foreach ($attribute->serialized as $mapping) { + $groups = (array) $mapping->group; + + if (isset($mapping['name'])) { + $attributeMetadata->setSerializedName((string) $mapping['name'], $groups); + } + + if (isset($mapping['path'])) { + try { + $attributeMetadata->setSerializedPath(new PropertyPath((string) $mapping['path']), $groups); + } catch (InvalidPropertyPathException) { + throw new MappingException(\sprintf('The "path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); + } + } + } + foreach ($attribute->context as $node) { $groups = (array) $node->group; $context = $this->parseContext($node->entry); diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php index ca71cbcbae0b4..5ab0ca3bd0e08 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php @@ -103,6 +103,26 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata->setIgnore($data['ignore']); } + foreach ($data['mappings'] ?? [] as $line) { + $groups = $line['groups'] ?? []; + + if ($serializedName = $line['serialized_name'] ?? false) { + if (!\is_string($serializedName) || '' === $serializedName) { + 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($serializedName, $groups); + } + + if ($serializedPath = $line['serialized_path'] ?? false) { + try { + $attributeMetadata->setSerializedPath(new PropertyPath((string) $serializedPath), $groups); + } catch (InvalidPropertyPathException) { + throw new MappingException(\sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + } + } + } + foreach ($data['contexts'] ?? [] as $line) { $groups = $line['groups'] ?? []; 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..e0203fb427990 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 @@ -62,6 +62,7 @@ + @@ -74,23 +75,19 @@ - - - - - - - - - - - - - - + + + + + + + + + + @@ -112,4 +109,10 @@ + + + + + + diff --git a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php index c72f148eb0240..f07c27fe7be7c 100644 --- a/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php +++ b/src/Symfony/Component/Serializer/NameConverter/MetadataAwareNameConverter.php @@ -47,11 +47,12 @@ public function normalize(string $propertyName, ?string $class = null, ?string $ return $this->normalizeFallback($propertyName, $class, $format, $context); } - if (!\array_key_exists($class, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$class])) { - self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class); + $cacheKey = $this->getCacheKey($class, $context); + if (!\array_key_exists($cacheKey, self::$normalizeCache) || !\array_key_exists($propertyName, self::$normalizeCache[$cacheKey])) { + self::$normalizeCache[$cacheKey][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class, $context); } - return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); + return self::$normalizeCache[$cacheKey][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context); } public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string @@ -68,7 +69,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; @@ -79,11 +80,14 @@ private function getCacheValueForNormalization(string $propertyName, string $cla return null; } - if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $contextGroups = array_merge($contextGroups, ['Default', (false !== $nsSep = strrpos($class, '\\')) ? substr($class, $nsSep + 1) : $class]); + + if (null !== $attributesMetadata[$propertyName]->getSerializedName($contextGroups) && null !== $attributesMetadata[$propertyName]->getSerializedPath($contextGroups)) { throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); } - return $attributesMetadata[$propertyName]->getSerializedName() ?? null; + return $attributesMetadata[$propertyName]->getSerializedName($contextGroups) ?? null; } private function normalizeFallback(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string @@ -117,18 +121,19 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex $classMetadata = $this->metadataFactory->getMetadataFor($class); + $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); + $cache = []; foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) { - if (null === $metadata->getSerializedName()) { + if (null === $serializedName = $metadata->getSerializedName($contextGroups)) { continue; } - if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { + if (null !== $metadata->getSerializedPath($contextGroups)) { throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); } $metadataGroups = $metadata->getGroups(); - $contextGroups = (array) ($context[AbstractNormalizer::GROUPS] ?? []); if ($contextGroups && !$metadataGroups) { continue; @@ -138,7 +143,7 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex continue; } - $cache[$metadata->getSerializedName()] = $name; + $cache[$serializedName] = $name; } return $cache; diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 9db2412980930..e6af3927971ca 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -318,7 +318,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $mappedClass = $this->getMappedClass($normalizedData, $type, $context); - $nestedAttributes = $this->getNestedAttributes($mappedClass); + $nestedAttributes = $this->getNestedAttributes($mappedClass, $context); $nestedData = $originalNestedData = []; $propertyAccessor = PropertyAccess::createPropertyAccessor(); foreach ($nestedAttributes as $property => $serializedPath) { @@ -954,7 +954,7 @@ private function updateData(array $data, string $attribute, mixed $attributeValu return $data; } - if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) { + if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath($this->getGroups($context))) { $propertyAccessor = PropertyAccess::createPropertyAccessor(); if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) { throw new LogicException(\sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); @@ -1060,7 +1060,7 @@ private function isUninitializedValueError(\Error|UninitializedPropertyException /** * Returns all attributes with a SerializedPath attribute and the respective path. */ - private function getNestedAttributes(string $class): array + private function getNestedAttributes(string $class, array $context): array { if (!$this->classMetadataFactory?->hasMetadataFor($class)) { return []; @@ -1070,7 +1070,7 @@ private function getNestedAttributes(string $class): array $serializedPaths = []; $classMetadata = $this->classMetadataFactory->getMetadataFor($class); foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) { - if (!$serializedPath = $metadata->getSerializedPath()) { + if (!$serializedPath = $metadata->getSerializedPath($this->getGroups($context))) { continue; } $pathIdentifier = implode(',', $serializedPath->getElements()); diff --git a/src/Symfony/Component/Serializer/Tests/Attribute/SerializedNameTest.php b/src/Symfony/Component/Serializer/Tests/Attribute/SerializedNameTest.php index c645e7e853f86..7c231ec0a79a0 100644 --- a/src/Symfony/Component/Serializer/Tests/Attribute/SerializedNameTest.php +++ b/src/Symfony/Component/Serializer/Tests/Attribute/SerializedNameTest.php @@ -28,6 +28,14 @@ public function testNotAStringSerializedNameParameter() new SerializedName(''); } + public function testInvalidGroupOption() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', SerializedName::class)); + + new SerializedName('foo', ['fine', new \stdClass()]); + } + public function testSerializedNameParameters() { $foo = new SerializedName('foo'); diff --git a/src/Symfony/Component/Serializer/Tests/Attribute/SerializedPathTest.php b/src/Symfony/Component/Serializer/Tests/Attribute/SerializedPathTest.php index 7ba31fc226e8f..0071d43101eeb 100644 --- a/src/Symfony/Component/Serializer/Tests/Attribute/SerializedPathTest.php +++ b/src/Symfony/Component/Serializer/Tests/Attribute/SerializedPathTest.php @@ -29,6 +29,14 @@ public function testEmptyStringSerializedPathParameter() new SerializedPath(''); } + public function testInvalidGroupOption() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', SerializedPath::class)); + + new SerializedPath('foo', ['fine', new \stdClass()]); + } + public function testSerializedPath() { $path = '[one][two]'; diff --git a/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php index 7bfdf93ddd55c..432c98081c225 100644 --- a/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Serializer/Tests/Command/DebugCommandTest.php @@ -36,43 +36,47 @@ public function testOutputWithClassArgument() Symfony\Component\Serializer\Tests\Dummy\DummyClassOne ------------------------------------------------------ - +----------+---------------------------------------+ - | Property | Options | - +----------+---------------------------------------+ - | code | [ | - | | "groups" => [ | - | | "book:read", | - | | "book:write" | - | | ], | - | | "maxDepth" => 1, | - | | "serializedName" => "identifier", | - | | "serializedPath" => null, | - | | "ignore" => true, | - | | "normalizationContexts" => [ | - | | "*" => [ | - | | "groups" => [ | - | | "book:read" | - | | ] | - | | ] | - | | ], | - | | "denormalizationContexts" => [ | - | | "*" => [ | - | | "groups" => [ | - | | "book:write" | - | | ] | - | | ] | - | | ] | - | | ] | - | name | [ | - | | "groups" => [], | - | | "maxDepth" => null, | - | | "serializedName" => null, | - | | "serializedPath" => "[data][name]", | - | | "ignore" => false, | - | | "normalizationContexts" => [], | - | | "denormalizationContexts" => [] | - | | ] | - +----------+---------------------------------------+ + +----------+-----------------------------------+ + | Property | Options | + +----------+-----------------------------------+ + | code | [ | + | | "groups" => [ | + | | "book:read", | + | | "book:write" | + | | ], | + | | "maxDepth" => 1, | + | | "serializedNames" => [ | + | | "*" => "identifier" | + | | ], | + | | "serializedPaths" => [], | + | | "ignore" => true, | + | | "normalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:read" | + | | ] | + | | ] | + | | ], | + | | "denormalizationContexts" => [ | + | | "*" => [ | + | | "groups" => [ | + | | "book:write" | + | | ] | + | | ] | + | | ] | + | | ] | + | name | [ | + | | "groups" => [], | + | | "maxDepth" => null, | + | | "serializedNames" => [], | + | | "serializedPaths" => [ | + | | "*" => "[data][name]" | + | | ], | + | | "ignore" => false, | + | | "normalizationContexts" => [], | + | | "denormalizationContexts" => [] | + | | ] | + +----------+-----------------------------------+ TXT, $tester->getDisplay(true), diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php index 27679b00922cf..a9041df379ba2 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedNameDummy.php @@ -25,6 +25,9 @@ class SerializedNameDummy public $quux; + #[SerializedName('duxi'), SerializedName('duxa', 'a')] + public $duux; + /** * @var self */ diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php index 8b627b7926faa..9d2b997562b54 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathDummy.php @@ -23,6 +23,9 @@ class SerializedPathDummy public $seven; + #[SerializedPath('[five][six]'), SerializedPath('[six][five]', 'a')] + public $eleven; + #[SerializedPath('[three][four]')] public function getSeven() { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php index e463429c0759a..757533f05fee0 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/SerializedPathInConstructorDummy.php @@ -17,6 +17,8 @@ class SerializedPathInConstructorDummy { public function __construct( #[SerializedPath('[one][two]')] public $three, + #[SerializedPath('[five][six]'), SerializedPath('[six][five]', 'a')] + public $eleven, ) { } } diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php index 86fc6ead1fdb1..c72d3adcf4d01 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/OtherSerializedNameDummy.php @@ -22,6 +22,12 @@ class OtherSerializedNameDummy #[Groups(['a'])] private $buz; + #[Groups(['a']), SerializedName('duxi'), SerializedName('duxa', 'a')] + public $duux; + + #[Groups(['i', 'a']), SerializedName('puxi', 'i'), SerializedName('puxa', 'a')] + public $puux; + public function setBuz($buz) { $this->buz = $buz; diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml index 512736db4bbe4..6bebc14704a92 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml @@ -23,15 +23,32 @@ + + + + a + + + + + + a + + + + + a + + diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml index 4371016e34be3..b50eb0be49a7b 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml @@ -15,17 +15,35 @@ foo: serialized_name: 'baz' bar: - serialized_name: 'qux' + mappings: + - serialized_name: 'qux' + duux: + mappings: + - serialized_name: 'duxi' + - serialized_name: 'duxa' + groups: [a] + 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy': attributes: three: serialized_path: '[one][two]' seven: - serialized_path: '[three][four]' + mappings: + - serialized_path: '[three][four]' + eleven: + mappings: + - serialized_path: '[five][six]' + - serialized_path: '[six][five]' + groups: [a] 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathInConstructorDummy': attributes: three: serialized_path: '[one][two]' + eleven: + serialized_path: '[five][six]' + mappings: + - serialized_path: '[six][five]' + groups: [a] 'Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy': discriminator_map: type_property: type 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/Factory/ClassMetadataFactoryCompilerTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 40dcb50152c66..8ed2662ffebd1 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -61,10 +61,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, [], []], + 'bar' => [[], null, [], []], + 'baz' => [[], null, [], []], + 'qux' => [[], null, [], []], ], null, ], $compiledMetadata[Dummy::class]); @@ -72,9 +72,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, [], []], + 'bar' => [[], 3, [], []], + 'child' => [[], null, [], []], ], null, ], $compiledMetadata[MaxDepthDummy::class]); @@ -82,10 +82,11 @@ 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'], []], + 'bar' => [[], null, ['*' => 'qux'], []], + 'quux' => [[], null, [], []], + 'duux' => [[], null, ['*' => 'duxi', 'a' => 'duxa'], []], + 'child' => [[], null, [], []], ], null, ], $compiledMetadata[SerializedNameDummy::class]); @@ -93,8 +94,9 @@ 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]']], + 'eleven' => [[], null, [], ['*' => '[five][six]', 'a' => '[six][five]']], ], null, ], $compiledMetadata[SerializedPathDummy::class]); @@ -102,7 +104,8 @@ public function testItDumpMetadata() $this->assertArrayHasKey(SerializedPathInConstructorDummy::class, $compiledMetadata); $this->assertEquals([ [ - 'three' => [[], null, null, '[one][two]'], + 'three' => [[], null, [], ['*' => '[one][two]']], + 'eleven' => [[], null, [], ['*' => '[five][six]', 'a' => '[six][five]']], ], null, ], $compiledMetadata[SerializedPathInConstructorDummy::class]); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php index 2af244a6f704c..80753eec8fb90 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -111,6 +111,8 @@ public function testLoadSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testLoadSerializedPath() @@ -121,6 +123,8 @@ public function testLoadSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadSerializedPathInConstructor() @@ -130,6 +134,8 @@ public function testLoadSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadClassMetadataAndMerge() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index c0298129efa59..9f3e2ca0beef1 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -80,6 +80,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -90,6 +92,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); $this->assertEquals('[three][four]', $attributesMetadata['seven']->getSerializedPath()); + $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -99,6 +103,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('[one][two]', $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals('[five][six]', $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals('[six][five]', $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index 48e95aecd9245..7bb18a10d4391 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -97,6 +97,8 @@ public function testSerializedName() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals('baz', $attributesMetadata['foo']->getSerializedName()); $this->assertEquals('qux', $attributesMetadata['bar']->getSerializedName()); + $this->assertEquals('duxi', $attributesMetadata['duux']->getSerializedName()); + $this->assertEquals('duxa', $attributesMetadata['duux']->getSerializedName(['a'])); } public function testSerializedPath() @@ -107,6 +109,8 @@ public function testSerializedPath() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); $this->assertEquals(new PropertyPath('[three][four]'), $attributesMetadata['seven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testSerializedPathInConstructor() @@ -116,6 +120,8 @@ public function testSerializedPathInConstructor() $attributesMetadata = $classMetadata->getAttributesMetadata(); $this->assertEquals(new PropertyPath('[one][two]'), $attributesMetadata['three']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[five][six]'), $attributesMetadata['eleven']->getSerializedPath()); + $this->assertEquals(new PropertyPath('[six][five]'), $attributesMetadata['eleven']->getSerializedPath(['a'])); } public function testLoadDiscriminatorMap() diff --git a/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php b/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php index c6ccd2601c98e..0273fdd94a774 100644 --- a/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php +++ b/src/Symfony/Component/Serializer/Tests/NameConverter/MetadataAwareNameConverterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\NameConverter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Serializer\Attribute\SerializedPath; use Symfony\Component\Serializer\Exception\LogicException; @@ -101,6 +102,7 @@ public static function attributeProvider(): array ['foo', 'baz'], ['bar', 'qux'], ['quux', 'quux'], + ['duux', 'duxi'], [0, 0], ]; } @@ -111,6 +113,7 @@ public static function fallbackAttributeProvider(): array ['foo', 'baz'], ['bar', 'qux'], ['quux', 'QUUX'], + ['duux', 'duxi'], [0, 0], ]; } @@ -127,6 +130,24 @@ public function testNormalizeWithGroups(string $propertyName, string $expected, $this->assertEquals($expected, $nameConverter->normalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } + /** + * @dataProvider fallbackAttributeAndContextProvider + */ + public function testNormalizeWithGroupsAndFallback(string $propertyName, string $expected, array $context = []) + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + + $fallback = $this->createMock(NameConverterInterface::class); + $fallback + ->method('normalize') + ->willReturnCallback(static fn ($propertyName) => strtoupper($propertyName)) + ; + + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); + + $this->assertSame($expected, $nameConverter->normalize($propertyName, OtherSerializedNameDummy::class, null, $context)); + } + /** * @dataProvider attributeAndContextProvider */ @@ -139,6 +160,24 @@ public function testDenormalizeWithGroups(string $expected, string $propertyName $this->assertEquals($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); } + /** + * @dataProvider fallbackAttributeAndContextProvider + */ + public function testDenormalizeWithGroupsAndFallback(string $expected, string $propertyName, array $context = []) + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + + $fallback = $this->createMock(NameConverterInterface::class); + $fallback + ->method('denormalize') + ->willReturnCallback(static fn ($propertyName) => strtolower($propertyName)) + ; + + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory, $fallback); + + $this->assertSame($expected, $nameConverter->denormalize($propertyName, OtherSerializedNameDummy::class, null, $context)); + } + public static function attributeAndContextProvider(): array { return [ @@ -149,6 +188,37 @@ public static function attributeAndContextProvider(): array ['buz', 'buz', ['groups' => ['c']]], ['buz', 'buz', []], ['buzForExport', 'buz', ['groups' => ['*']]], + ['duux', 'duxi', []], + ['duux', 'duxi', ['groups' => ['*']]], + ['duux', 'duxa', ['groups' => ['a']]], + ['duux', 'duxi', ['groups' => ['z']]], + ['puux', 'puux', []], + ['puux', 'puux', ['groups' => ['*']]], + ['puux', 'puxi', ['groups' => ['i']]], + ['puux', 'puxa', ['groups' => ['a']]], + ['puux', 'puux', ['groups' => ['z']]], + ]; + } + + public static function fallbackAttributeAndContextProvider(): array + { + return [ + ['buz', 'BUZ', ['groups' => ['a']]], + ['buzForExport', 'buz', ['groups' => ['b']]], + ['buz', 'BUZ', ['groups' => 'a']], + ['buzForExport', 'buz', ['groups' => 'b']], + ['buz', 'BUZ', ['groups' => ['c']]], + ['buz', 'BUZ', []], + ['buzForExport', 'buz', ['groups' => ['*']]], + ['duux', 'duxi', []], + ['duux', 'duxi', ['groups' => ['*']]], + ['duux', 'duxa', ['groups' => ['a']]], + ['duux', 'duxi', ['groups' => ['z']]], + ['puux', 'PUUX', []], + ['puux', 'PUUX', ['groups' => ['*']]], + ['puux', 'puxi', ['groups' => ['i']]], + ['puux', 'puxa', ['groups' => ['a']]], + ['puux', 'PUUX', ['groups' => ['z']]], ]; } @@ -169,7 +239,23 @@ public function testDenormalizeWithNestedPathAndName() $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); $this->expectException(LogicException::class); $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); - $nameConverter->denormalize('foo', NestedPathAndName::class); + $nameConverter->denormalize('five', NestedPathAndName::class); + } + + public function testDenormalizeWithNestedPathAndNameSameGroup() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "bar" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndNameSameGroup".'); + $nameConverter->denormalize('eight', NestedPathAndNameSameGroup::class, null, ['groups' => 'a']); + } + + public function testDenormalizeWithNestedPathAndNameDifferentGroups() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->assertSame('baz', $nameConverter->denormalize('eleven', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); } public function testNormalizeWithNestedPathAndName() @@ -180,6 +266,22 @@ public function testNormalizeWithNestedPathAndName() $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "foo" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndName".'); $nameConverter->normalize('foo', NestedPathAndName::class); } + + public function testNormalizeWithNestedPathAndNameSameGroup() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Found SerializedName and SerializedPath attributes on property "bar" of class "Symfony\Component\Serializer\Tests\NameConverter\NestedPathAndNameSameGroup".'); + $nameConverter->normalize('bar', NestedPathAndNameSameGroup::class, null, ['groups' => 'a']); + } + + public function testNormalizeWithNestedPathAndNameDifferentGroups() + { + $classMetadataFactory = new ClassMetadataFactory(new AttributeLoader()); + $nameConverter = new MetadataAwareNameConverter($classMetadataFactory); + $this->assertSame('eleven', $nameConverter->normalize('baz', NestedPathAndNameDifferentGroups::class, null, ['groups' => 'a'])); + } } class NestedPathAndName @@ -187,3 +289,15 @@ class NestedPathAndName #[SerializedName('five'), SerializedPath('[one][two][three]')] public $foo; } + +class NestedPathAndNameSameGroup +{ + #[Groups(['a']), SerializedName('eight', 'a'), SerializedPath('[four][five][six]', 'a')] + public $bar; +} + +class NestedPathAndNameDifferentGroups +{ + #[Groups(['a', 'b']), SerializedName('eleven', 'a'), SerializedPath('[seven][eight][nine]', 'b')] + public $baz; +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy