Skip to content

Commit e14854f

Browse files
committed
[Serializer] Name converter support
1 parent fef2bd4 commit e14854f

9 files changed

+361
-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+
```
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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
@@ -14,6 +14,8 @@
1414
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1515
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1616
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
17+
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
18+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1719

1820
/**
1921
* Normalizer implementation.
@@ -25,18 +27,21 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N
2527
protected $circularReferenceLimit = 1;
2628
protected $circularReferenceHandler;
2729
protected $classMetadataFactory;
30+
protected $nameConverter;
2831
protected $callbacks = array();
2932
protected $ignoredAttributes = array();
3033
protected $camelizedAttributes = array();
3134

3235
/**
3336
* Sets the {@link ClassMetadataFactory} to use.
3437
*
35-
* @param ClassMetadataFactory $classMetadataFactory
38+
* @param ClassMetadataFactory|null $classMetadataFactory
39+
* @param NameConverterInterface|null $nameConverter
3640
*/
37-
public function __construct(ClassMetadataFactory $classMetadataFactory = null)
41+
public function __construct(ClassMetadataFactory $classMetadataFactory = null, NameConverterInterface $nameConverter = null)
3842
{
3943
$this->classMetadataFactory = $classMetadataFactory;
44+
$this->nameConverter = $nameConverter;
4045
}
4146

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

125145
return $this;
126146
}
@@ -178,18 +198,17 @@ protected function handleCircularReference($object)
178198
/**
179199
* Format an attribute name, for example to convert a snake_case name to camelCase.
180200
*
201+
* @deprecated Deprecated since version 2.7, to be removed in 3.0. Use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter instead.
202+
*
181203
* @param string $attributeName
204+
*
182205
* @return string
183206
*/
184207
protected function formatAttribute($attributeName)
185208
{
186-
if (in_array($attributeName, $this->camelizedAttributes)) {
187-
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
188-
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
189-
}, $attributeName);
190-
}
209+
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);
191210

192-
return $attributeName;
211+
return $this->nameConverter ? $this->nameConverter->normalize($attributeName) : $attributeName;
193212
}
194213

195214
/**
@@ -272,14 +291,15 @@ protected function instantiateObject(array $data, $class, array &$context, \Refl
272291

273292
$params = array();
274293
foreach ($constructorParameters as $constructorParameter) {
275-
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
294+
$paramName = $constructorParameter->name;
295+
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
276296

277297
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
278298
$ignored = in_array($paramName, $this->ignoredAttributes);
279-
if ($allowed && !$ignored && isset($data[$paramName])) {
280-
$params[] = $data[$paramName];
299+
if ($allowed && !$ignored && isset($data[$key])) {
300+
$params[] = $data[$key];
281301
// don't run set for a parameter passed to the constructor
282-
unset($data[$paramName]);
302+
unset($data[$key]);
283303
} elseif ($constructorParameter->isOptional()) {
284304
$params[] = $constructorParameter->getDefaultValue();
285305
} 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 <[email protected]>
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 <[email protected]>
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)