8000 feature #13120 [Serializer] Name converter support (dunglas) · symfony/symfony@fcd6d60 · GitHub
[go: up one dir, main page]

Skip to content
< 8000 /react-partial>

Commit fcd6d60

Browse files
committed
feature #13120 [Serializer] Name converter support (dunglas)
This PR was merged into the 2.7 branch. Discussion ---------- [Serializer] Name converter support | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #12212 | License | MIT | Doc PR | symfony/symfony-docs#4692 This PR adds support for custom property naming strategies to the serializer and provides a built-in NameConverter using this new system: (CamelCase to underscore). It handles normalization and denormalization (convert `fooBar` to `foo_bar` when serializing, then from `foo_bar` to `fooBar` when deserializing). It also has a flag to convert only some attributes. The `setCamelizedAttributes()` is deprecated in favor of this new method (more flexible, allows to rename all attributes of a class and support deserialization) and now uses it internally. Commits ------- 86b84a5 [Serializer] Update changelog e14854f [Serializer] Name converter support
2 parents e25b751 + 86b84a5 commit fcd6d60

File tree

10 files changed

+376
-49
lines changed
  • Tests
  • 10 files changed

    +376
    -49
    lines changed

    UPGRADE-2.7.md

    Lines changed: 24 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -59,3 +59,27 @@ Form
    5959
    }
    6060
    }
    6161
    ```
    62+
    63+
    Serializer
    64+
    ----------
    65+
    66+
    * The `setCamelizedAttributes()` method of the
    67+
    `Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer` and
    68+
    `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` classes is marked
    69+
    as deprecated in favor of the new NameConverter system.
    70+
    71+
    Before:
    72+
    73+
    ```php
    74+
    $normalizer->setCamelizedAttributes(array('foo_bar', 'bar_foo'));
    75+
    ```
    76+
    77+
    After:
    78+
    79+
    ```php
    80+
    use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
    81+
    use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
    82+
    83+
    $nameConverter = new CamelCaseToSnakeCaseNameConverter(array('fooBar', 'barFoo'));
    84+
    $normalizer = new GetSetMethodNormalizer(null, $nameConverter);
    85+
    ```

    src/Symfony/Component/Serializer/CHANGELOG.md

    Lines changed: 15 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1,11 +1,26 @@
    11
    CHANGELOG
    22
    =========
    33

    4+
    2.7.0
    5+
    -----
    6+
    7+
    * added support for serialization and deserialization groups including
    8+
    annotations, XML and YAML mapping.
    9+
    * added `AbstractNormalizer` to factorise code and ease normalizers development
    10+
    * added circular references handling for `PropertyNormalizer`
    11+
    * added support for a context key called `object_to_populate` in `AbstractNormalizer`
    12+
    to reuse existing objects in the deserialization process
    13+
    * added `NameConverterInterface` and `CamelCaseToSnakeCaseNameConverter`
    14+
    * [DEPRECATION] `GetSetMethodNormalizer::setCamelizedAttributes()` and
    15+
    `PropertyNormalizer::setCamelizedAttributes()` are replaced by
    16+
    `CamelCaseToSnakeCaseNameConverter`
    17+
    418
    2.6.0
    519
    -----
    620

    721
    * added a new serializer: `PropertyNormalizer`. Like `GetSetMethodNormalizer`,
    822
    this normalizer will map an object's properties to an array.
    23+
    * added circular references handling for `GetSetMethodNormalizer`
    924

    1025
    2.5.0
    1126
    -----
    Lines changed: 82 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,82 @@
    1+
    <?php
    2+
    3+
    /*
    4+
    * This file is part of the Symfony package.
    5+
    *
    6+
    * (c) Fabien Potencier <fabien@symfony.com>
    7+
    *
    8+
    * For the full copyright and license information, please view the LICENSE
    9+
    * file that was distributed with this source code.
    10+
    */
    11+
    12+
    namespace Symfony\Component\Serializer\NameConverter;
    13+
    14+
    /**
    15+
    * CamelCase to Underscore name converter.
    16+
    *
    17+
    * @author Kévin Dunglas <dunglas@gmail.com>
    18+
    */
    19+
    class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface
    20+
    {
    21+
    /**
    22+
    * @var array|null
    23+
    */
    24+
    private $attributes;
    25+
    /**
    26+
    * @var bool
    27+
    */
    28+
    private $lowerCamelCase;
    29+
    30+
    /**
    31+
    * @param null|array $attributes The list of attributes to rename or null for all attributes.
    32+
    * @param bool $lowerCamelCase Use lowerCamelCase style.
    33+
    */
    34+
    public function __construct(array $attributes = null, $lowerCamelCase = true)
    35+
    {
    36+
    $this->attributes = $attributes;
    37+
    $this->lowerCamelCase = $lowerCamelCase;
    38+
    }
    39+
    40+
    /**
    41+
    * {@inheritdoc}
    42+
    */
    43+
    public function normalize($propertyName)
    44+
    {
    45+
    if (null === $this->attributes || in_array($propertyName, $this->attributes)) {
    46+
    $snakeCasedName = '';
    47+
    48+
    $len = strlen($propertyName);
    49+
    for ($i = 0; $i < $len; $i++) {
    50+
    if (ctype_upper($propertyName[$i])) {
    51+
    $snakeCasedName .= '_'.strtolower($propertyName[$i]);
    52+
    } else {
    53+
    $snakeCasedName .= strtolower($propertyName[$i]);
    54+
    }
    55+
    }
    56+
    57+
    return $snakeCasedName;
    58+
    }
    59+
    60+
    return $propertyName;
    61+
    }
    62+
    63+
    /**
    64+
    * {@inheritdoc}
    65+
    */
    66+
    public function denormalize($propertyName)
    67+
    {
    68+
    $camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
    69+
    return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
    70+
    }, $propertyName);
    71+
    72+
    if ($this->lowerCamelCase) {
    73+
    $camelCasedName = lcfirst($camelCasedName);
    74+
    }
    75+
    76+
    if (null === $this->attributes || in_array($camelCasedName, $this->attributes)) {
    77+
    return $this->lowerCamelCase ? lcfirst($camelCasedName) : $camelCasedName;
    78+
    }
    79+
    80+
    return $propertyName;
    81+
    }
    82+
    }
    Lines changed: 36 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,36 @@
    1+
    <?php
    2+
    3+
    /*
    4+
    * This file is part of the Symfony package.
    5+
    *
    6+
    * (c) Fabien Potencier <fabien@symfony.com>
    7+
    *
    8+
    * For the full copyright and license information, please view the LICENSE
    9+
    * file that was distributed with this source code.
    10+
    */
    11+
    12+
    namespace Symfony\Component\Serializer\NameConverter;
    13+
    14+
    /**
    15+
    * Defines the interface for property name converters.
    16+
    *
    17+
    * @author Kévin Dunglas <dunglas@gmail.com>
    18+
    */
    19+
    interface NameConverterInterface
    20+
    {
    21+
    /**
    22+
    * Converts a property name to its normalized value.
    23+
    *
    24+
    * @param string $propertyName
    25+
    * @return string
    26+
    */
    27+
    public function normalize($propertyName);
    28+
    29+
    /**
    30+
    * Converts a property name to its denormalized value.
    31+
    *
    32+
    * @param string $propertyName
    33+
    * @return string
    34+
    */
    35+
    public function denormalize($propertyName);
    36+
    }

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

    Lines changed: 33 additions & 13 deletions
    Original file line numberDiff line numberDiff line change
    @@ -15,6 +15,8 @@
    1515
    use Symfony\Component\Serializer\Exception\InvalidArgumentException;
    1616
    use Symfony\Component\Serializer\Exception\RuntimeException;
    1717
    use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
    18+
    use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
    19+
    use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
    1820

    1921
    /**
    2022
    * Normalizer implementation.
    @@ -26,18 +28,21 @@ abstract class Abstr 1241 actNormalizer extends SerializerAwareNormalizer implements N
    2628
    protected $circularReferenceLimit = 1;
    2729
    protected $circularReferenceHandler;
    2830
    protected $classMetadataFactory;
    31+
    protected $nameConverter;
    2932
    protected $callbacks = array();
    3033
    protected $ignoredAttributes = array();
    3134
    protected $camelizedAttributes = array();
    3235

    3336
    /**
    3437
    * Sets the {@link ClassMetadataFactory} to use.
    3538
    *
    36-
    * @param ClassMetadataFactory $classMetadataFactory
    39+
    * @param ClassMetadataFactory|null $classMetadataFactory
    40+
    * @param NameConverterInterface|null $nameConverter
    3741
    */
    38-
    public function __construct(ClassMetadataFactory $classMetadataFactory = null)
    42+
    public function __construct(ClassMetadataFactory $classMetadataFactory = null, NameConverterInterface $nameConverter = null)
    3943
    {
    4044
    $this->classMetadataFactory = $classMetadataFactory;
    45+
    $this->nameConverter = $nameConverter;
    4146
    }
    4247

    4348
    /**
    @@ -115,13 +120,28 @@ public function setIgnoredAttributes(array $ignoredAttributes)
    115120
    /**
    116121
    * Set attributes to be camelized on denormalize.
    117122
    *
    123+
    * @deprecated Deprecated since version 2.7, to be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.
    124+
    *
    118125
    * @param array $camelizedAttributes
    119126
    *
    120127
    * @return self
    121128
    */
    122129
    public function setCamelizedAttributes(array $camelizedAttributes)
    123130
    {
    124-
    $this->camelizedAttributes = $camelizedAttributes;
    131+
    trigger_error(sprintf('%s is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.', __METHOD__), E_USER_DEPRECATED);
    132+
    133+
    if ($this->nameConverter && !$this->nameConverter instanceof CamelCaseToSnakeCaseNameConverter) {
    134+
    throw new \LogicException(sprintf('%s cannot be called if a custom Name Converter is defined.', __METHOD__));
    135+
    }
    136+
    137+
    $attributes = array();
    138+
    foreach ($camelizedAttributes as $camelizedAttribute) {
    139+
    $attributes[] = lcfirst(preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
    140+
    return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
    141+
    }, $camelizedAttribute));
    142+
    }
    143+
    144+
    $this->nameConverter = new CamelCaseToSnakeCaseNameConverter($attributes);
    125145

    126146
    return $this;
    127147
    }
    @@ -179,18 +199,17 @@ protected function handleCircularReference($object)
    179199
    /**
    180200
    * Format an attribute name, for example to convert a snake_case name to camelCase.
    181201
    *
    202+
    * @deprecated Deprecated since version 2.7, to be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.
    203+
    *
    182204
    * @param string $attributeName
    205+
    *
    183206
    * @return string
    184207
    */
    185208
    protected function formatAttribute($attributeName)
    186209
    {
    187-
    if (in_array($attributeName, $this->camelizedAttributes)) {
    188-
    return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
    189-
    return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
    190-
    }, $attributeName);
    191-
    }
    210+
    trigger_error(sprintf('%s is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.', __METHOD__), E_USER_DEPRECATED);
    192211

    193-
    return $attributeName;
    212+
    return $this->nameConverter ? $this->nameConverter->normalize($attributeName) : $attributeName;
    194213
    }
    195214

    196215
    /**
    @@ -273,14 +292,15 @@ protected function instantiateObject(array $data, $class, array &$context, \Refl
    273292

    274293
    $params = array();
    275294
    foreach ($constructorParameters as $constructorParameter) {
    276-
    $paramName = lcfirst($this->formatAttribute($constructorParameter->name));
    295+
    $paramName = $constructorParameter->name;
    296+
    $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
    277297

    278298
    $allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
    279299
    $ignored = in_array($paramName, $this->ignoredAttributes);
    280-
    if ($allowed && !$ignored && isset($data[$paramName])) {
    281-
    $params[] = $data[$paramName];
    300+
    if ($allowed && !$ignored && isset($data[$key])) {
    301+
    $params[] = $data[$key];
    282302
    // don't run set for a parameter passed to the constructor
    283-
    unset($data[$paramName]);
    303+
    unset($data[$key]);
    284304
    } elseif ($constructorParameter->isOptional()) {
    285305
    $params[] = $constructorParameter->getDefaultValue();
    286306
    } else {

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

    Lines changed: 9 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -77,6 +77,10 @@ public function normalize($object, $format = null, array $context = array())
    7777
    $attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
    7878
    }
    7979

    80+
    if ($this->nameConverter) {
    81+
    $attributeName = $this->nameConverter->normalize($attributeName);
    82+
    }
    83+
    8084
    $attributes[$attributeName] = $attributeValue;
    8185
    }
    8286
    }
    @@ -102,7 +106,11 @@ public function denormalize($data, $class, $format = null, array $context = arra
    102106
    $ignored = in_array($attribute, $this->ignoredAttributes);
    103107

    104108
    if ($allowed && !$ignored) {
    105-
    $setter = 'set'.$this->formatAttribute($attribute);
    109+
    if ($this->nameConverter) {
    110+
    $attribute = $this->nameConverter->denormalize($attribute);
    111+
    }
    112+
    113+
    $setter = 'set'.ucfirst($attribute);
    106114

    107115
    if (method_exists($object, $setter)) {
    108116
    $object->$setter($value);

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

    Lines changed: 9 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -71,7 +71,12 @@ public function normalize($object, $format = null, array $context = array())
    7171
    $attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
    7272
    }
    7373

    74-
    $attributes[$property->name] = $attributeValue;
    74+
    $propertyName = $property->name;
    75+
    if ($this->nameConverter) {
    76+
    $propertyName = $this->nameConverter->normalize($propertyName);
    77+
    }
    78+
    79+
    $attributes[$propertyName] = $attributeValue;
    7580
    }
    7681

    7782
    return $attributes;
    @@ -91,7 +96,9 @@ public function denormalize($data, $class, $format = null, array $context = arra
    9196
    $object = $this->instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes);
    9297

    9398
    foreach ($data as $propertyName => $value) {
    94-
    $propertyName = lcfirst($this->formatAttribute($propertyName));
    99+
    if ($this->nameConverter) {
    100+
    $propertyName = $this->nameConverter->denormalize($propertyName);
    101+
    }
    95102

    96103
    $allowed = $allowedAttributes === false || in_array($propertyName, $allowedAttributes);
    97104
    $ignored = in_array($propertyName, $this->ignoredAttributes);
    Lines changed: 47 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,47 @@
    1+
    <?php
    2+
    3+
    /*
    4+
    * This file is part of the Symfony package.
    5+
    *
    6+
    * (c) Fabien Potencier <fabien@symfony.com>
    7+
    *
    8+
    * For the full copyright and license information, please view the LICENSE
    9+
    * file that was distributed with this source code.
    10+
    */
    11+
    12+
    namespace Symfony\Component\Serializer\Tests\NameConverter;
    13+
    14+
    use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
    15+
    16+
    /**
    17+
    * @author Kévin Dunglas <dunglas@gmail.com>
    18+
    */
    19+
    class CamelCaseToSnakeCaseNameConverterTest extends \PHPUnit_Framework_TestCase
    20+
    {
    21+
    /**
    22+
    * @dataProvider attributeProvider
    23+
    */
    24+
    public function testNormalize($underscored, $lowerCamelCased)
    25+
    {
    26+
    $nameConverter = new CamelCaseToSnakeCaseNameConverter();
    27+
    $this->assertEquals($nameConverter->normalize($lowerCamelCased), $underscored);
    28+
    }
    29+
    30+
    /**
    31+
    * @dataProvider attributeProvider
    32+
    */
    33+
    public function testDenormalize($underscored, $lowerCamelCased)
    34+
    {
    35+
    $nameConverter = new CamelCaseToSnakeCaseNameConverter();
    36+
    $this->assertEquals($nameConverter->denormalize($underscored), $lowerCamelCased);
    37+
    }
    38+
    39+
    public function attributeProvider()
    40+
    {
    41+
    return array(
    42+
    array('coop_tilleuls', 'coopTilleuls'),
    43+
    array('_kevin_dunglas', '_kevinDunglas'),
    44+
    array('this_is_a_test', 'thisIsATest'),
    45+
    );
    46+
    }
    47+
    }

    0 commit comments

    Comments
     (0)
    0