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

[Serialized] allow configuring the serialized name of properties through metadata #28505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
[Serialized] allow configuring the serialized name of properties thro…
…ugh metadata
  • Loading branch information
fbourigault authored and dunglas committed Oct 5, 2018
commit d1d1ceb38e171132b6d13e19cd5576bd0dc85cd8
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
Expand Down Expand Up @@ -1363,7 +1363,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
}

if (isset($config['name_converter']) && $config['name_converter']) {
$container->getDefinition('serializer.normalizer.object')->replaceArgument(1, new Reference($config['name_converter']));
$container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter']));
}

if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

<service id="serializer.normalizer.object" class="Symfony\Component\Serializer\Normalizer\ObjectNormalizer">
<argument type="service" id="serializer.mapping.class_metadata_factory" />
<argument>null</argument> <!-- name converter -->
<argument type="service" id="serializer.name_converter.metadata_aware" />
<argument type="service" id="serializer.property_accessor" />
<argument type="service" id="property_info" on-invalid="ignore" />
<argument type="service" id="serializer.mapping.class_discriminator_resolver" on-invalid="ignore" />
Expand Down Expand Up @@ -119,6 +119,10 @@
<!-- Name converter -->
<service id="serializer.name_converter.camel_case_to_snake_case" class="Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter" />

<service id="serializer.name_converter.metadata_aware" class="Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter" >
<argument type="service" id="serializer.mapping.class_metadata_factory"/>
</service>

<!-- PropertyInfo extractor -->
<service id="property_info.serializer_extractor" class="Symfony\Component\PropertyInfo\Extractor\SerializerExtractor">
<argument type="service" id="serializer.mapping.class_metadata_factory" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ public function testSerializerEnabled()
$this->assertCount(2, $argument);
$this->assertEquals('Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', $argument[0]->getClass());
$this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1));
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.name_converter.metadata_aware')->getArgument(1));
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
$this->assertEquals(array('setCircularReferenceHandler', array(new Reference('my.circular.reference.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[0]);
$this->assertEquals(array('setMaxDepthHandler', array(new Reference('my.max.depth.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[1]);
Expand Down
2 changes: 1 addition & 1 deletion 2 src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"symfony/process": "~3.4|~4.0",
"symfony/security-core": "~3.4|~4.0",
"symfony/security-csrf": "~3.4|~4.0",
"symfony/serializer": "^4.1",
"symfony/serializer": "^4.2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm ok to bump this dependency. But feature detection may be used to support both versions. WDYT @symfony/deciders?

"symfony/stopwatch": "~3.4|~4.0",
"symfony/translation": "~4.2",
"symfony/templating": "~3.4|~4.0",
Expand Down
48 changes: 48 additions & 0 deletions 48 src/Symfony/Component/Serializer/Annotation/SerializedName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\Annotation;

use Symfony\Component\Serializer\Exception\InvalidArgumentException;

/**
* Annotation class for @SerializedName().
*
* @Annotation
* @Target({"PROPERTY", "METHOD"})
*
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
*/
final class SerializedName
{
/**
* @var string
*/
private $serializedName;

public function __construct(array $data)
{
if (!isset($data['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" should be set.', \get_class($this)));
}

if (!\is_string($data['value']) || empty($data['value'])) {
throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a non-empty string.', \get_class($this)));
}

$this->serializedName = $data['value'];
}

public function getSerializedName(): string
{
return $this->serializedName;
}
}
1 change: 1 addition & 0 deletions 1 src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ CHANGELOG
either `EncoderInterface` or `DecoderInterface`
* added the optional `$objectClassResolver` argument in `AbstractObjectNormalizer`
and `ObjectNormalizer` constructor
* added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata

4.1.0
-----
Expand Down
32 changes: 31 additions & 1 deletion 32 src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ class AttributeMetadata implements AttributeMetadataInterface
*/
public $maxDepth;

/**
* @var string|null
*
* @internal This property is public in order to reduce the size of the
* class' serialized representation. Do not access it. Use
* {@link getSerializedName()} instead.
*/
public $serializedName;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it still really necessary to make this property public? We do that everywhere in validator (and I copied this behavior in Serializer), but I'm not sure that it's still accurate.
Anyway, keep it here for consistency.


public function __construct(string $name)
{
$this->name = $name;
Expand Down Expand Up @@ -88,6 +97,22 @@ public function getMaxDepth()
return $this->maxDepth;
}

/**
* {@inheritdoc}
*/
public function setSerializedName(string $serializedName = null)
{
$this->serializedName = $serializedName;
}

/**
* {@inheritdoc}
*/
public function getSerializedName(): ?string
{
return $this->serializedName;
}

/**
* {@inheritdoc}
*/
Expand All @@ -101,6 +126,11 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
if (null === $this->maxDepth) {
$this->maxDepth = $attributeMetadata->getMaxDepth();
}

// Overwrite only if not defined
if (null === $this->serializedName) {
$this->serializedName = $attributeMetadata->getSerializedName();
}
}

/**
Expand All @@ -110,6 +140,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata)
*/
public function __sleep()
{
return array('name', 'groups', 'maxDepth');
return array('name', 'groups', 'maxDepth', 'serializedName');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ public function setMaxDepth($maxDepth);
*/
public function getMaxDepth();

/**
* Sets the serialization name for this attribute.
*/
public function setSerializedName(string $serializedName = null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BC break? why not otherwise?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, internal interface.


/**
* Gets the serialization name for this attribute.
*/
public function getSerializedName(): ?string;

/**
* Merges an {@see AttributeMetadataInterface} with in the current one.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Serializer\Annotation\SerializedName;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
Expand Down Expand Up @@ -68,6 +69,8 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
}
} elseif ($annotation instanceof MaxDepth) {
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
} elseif ($annotation instanceof SerializedName) {
$attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
}

$loaded = true;
Expand Down Expand Up @@ -107,6 +110,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
}

$attributeMetadata->setMaxDepth($annotation->getMaxDepth());
} elseif ($annotation instanceof SerializedName) {
if (!$accessorOrMutator) {
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());
}

$loaded = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
if (isset($attribute['max-depth'])) {
$attributeMetadata->setMaxDepth((int) $attribute['max-depth']);
}

if (isset($attribute['serialized-name'])) {
$attributeMetadata->setSerializedName((string) $attribute['serialized-name']);
}
}

if (isset($xml->{'discriminator-map'})) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)

$attributeMetadata->setMaxDepth($data['max_depth']);
}

if (isset($data['serialized_name'])) {
if (!\is_string($data['serialized_name']) || empty($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()));
}

$attributeMetadata->setSerializedName($data['serialized_name']);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="serialized-name">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:minLength value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>

</xsd:schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Serializer\NameConverter;

use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;

/**
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
*/
final class MetadataAwareNameConverter implements AdvancedNameConverterInterface
{
private $metadataFactory;

/**
* @var NameConverterInterface|AdvancedNameConverterInterface|null
*/
private $fallbackNameConverter;

private static $normalizeCache = array();

private static $denormalizeCache = array();

private static $attributesMetadataCache = array();

public function __construct(ClassMetadataFactoryInterface $metadataFactory, NameConverterInterface $fallbackNameConverter = null)
{
$this->metadataFactory = $metadataFactory;
$this->fallbackNameConverter = $fallbackNameConverter;
}

/**
* {@inheritdoc}
*/
public function normalize($propertyName, string $class = null, string $format = null, array $context = array())
{
if (null === $class) {
return $this->normalizeFallback($propertyName, $class, $format, $context);
}

if (!isset(self::$normalizeCache[$class][$propertyName])) {
self::$normalizeCache[$class][$propertyName] = $this->getCacheValueForNormalization($propertyName, $class);
}

fbourigault marked this conversation as resolved.
Show resolved Hide resolved
return self::$normalizeCache[$class][$propertyName] ?? $this->normalizeFallback($propertyName, $class, $format, $context);
}

/**
* {@inheritdoc}
*/
public function denormalize($propertyName, string $class = null, string $format = null, array $context = array())
{
if (null === $class) {
return $this->denormalizeFallback($propertyName, $class, $format, $context);
}

if (!isset(self::$denormalizeCache[$class][$propertyName])) {
self::$denormalizeCache[$class][$propertyName] = $this->getCacheValueForDenormalization($propertyName, $class);
}

return self::$denormalizeCache[$class][$propertyName] ?? $this->denormalizeFallback($propertyName, $class, $format, $context);
}

private function getCacheValueForNormalization(string $propertyName, string $class): ?string
{
if (!$this->metadataFactory->hasMetadataFor($class)) {
return null;
}

return $this->metadataFactory->getMetadataFor($class)->getAttributesMetadata()[$propertyName]->getSerializedName() ?? null;
}

private function normalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = array()): string
{
return $this->fallbackNameConverter ? $this->fallbackNameConverter->normalize($propertyName, $class, $format, $context) : $propertyName;
}

private function getCacheValueForDenormalization(string $propertyName, string $class): ?string
{
if (!isset(self::$attributesMetadataCache[$class])) {
self::$attributesMetadataCache[$class] = $this->getCacheValueForAttributesMetadata($class);
}

return self::$attributesMetadataCache[$class][$propertyName] ?? null;
}

private function denormalizeFallback(string $propertyName, string $class = null, string $format = null, array $context = array()): string
{
return $this->fallbackNameConverter ? $this->fallbackNameConverter->denormalize($propertyName, $class, $format, $context) : $propertyName;
}

private function getCacheValueForAttributesMetadata(string $class): array
{
if (!$this->metadataFactory->hasMetadataFor($class)) {
return array();
}

$classMetadata = $this->metadataFactory->getMetadataFor($class);

$cache = array();
foreach ($classMetadata->getAttributesMetadata() as $name => $metadata) {
if (null === $metadata->getSerializedName()) {
continue;
}

$cache[$metadata->getSerializedName()] = $name;
}

return $cache;
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.