Skip to content

Commit ceb2888

Browse files
committed
[Serializer] Name converter support
1 parent c1c5cd5 commit ceb2888

File tree

9 files changed

+362
-61
lines changed

9 files changed

+362
-61
lines changed

UPGRADE-2.7.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
UPGRADE FROM 2.6 to 2.7
2+
=======================
3+
4+
Serializer
5+
----------
6+
7+
* The `setCamelizedAttributes()` method of the
8+
`Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer` and
9+
`Symfony\Component\Serializer\Normalizer\PropertyNormalizer` classes is marked
10+
as deprecated in favor of the new NameConverter system.
11+
12+
Before:
13+
14+
```php
15+
$normalizer->setCamelizedAttributes(array('foo_bar', 'bar_foo'));
16+
```
17+
18+
After:
19+
20+
```php
21+
use Symfony\Component\Serializer\NameConverter\CamelCaseToUnderscoreNameConverter;
22+
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
23+
24+
$nameConverter = new CamelCaseToUnderscoreNameConverter(array('fooBar', 'barFoo'));
25+
$normalizer = new GetSetMethodNormalizer(null, $nameConverter);
26+
```
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
/**
16+
* CamelCase to Underscore name converter.
17+
*
18+
* @author Kévin Dunglas <dunglas@gmail.com>
19+
*/
20+
class CamelCaseToUnderscoreNameConverter implements NameConverterInterface
21+
{
22+
/**
23+
* @var array|null
24+
*/
25+
private $attributes;
26+
/**
27+
* @var bool
28+
*/
29+
private $lowerCamelCase;
30+
31+
/**
32+
* @param null|array $attributes The list of attributes to rename or null for all attributes.
33+
* @param bool $lowerCamelCase Use lowerCamelCase style.
34+
*/
35+
public function __construct(array $attributes = null, $lowerCamelCase = true)
36+
{
37+
$this->attributes = $attributes;
38+
$this->lowerCamelCase = $lowerCamelCase;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function normalize($propertyName)
45+
{
46+
if (null === $this->attributes || in_array($propertyName, $this->attributes)) {
47+
$underscoredName = '';
48+
49+
$len = strlen($propertyName);
50+
for ($i = 0; $i < $len; $i++) {
51+
if (ctype_upper($propertyName[$i])) {
52+
$underscoredName .= '_'.strtolower($propertyName[$i]);
53+
} else {
54+
$underscoredName .= strtolower($propertyName[$i]);
55+
}
56+
}
57+
58+
return $underscoredName;
59+
}
60+
61+
return $propertyName;
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function denormalize($propertyName)
68+
{
69+
$camelCasedName = preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
70+
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
71+
}, $propertyName);
72+
73+
if ($this->lowerCamelCase) {
74+
$camelCasedName = lcfirst($camelCasedName);
75+
}
76+
77+
if (null === $this->attributes || in_array($camelCasedName, $this->attributes)) {
78+
return $this->lowerCamelCase ? lcfirst($camelCasedName) : $camelCasedName;
79+
}
80+
81+
return $propertyName;
82+
}
83+
}
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: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1515
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
16+
use Symfony\Component\Serializer\NameConverter\CamelCaseToUnderscore;
17+
use Symfony\Component\Serializer\NameConverter\CamelCaseToUnderscoreNameConverter;
18+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
1619

1720
/**
1821
* Normalizer implementation.
@@ -22,18 +25,24 @@
2225
abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface
2326
{
2427
protected $classMetadataFactory;
28+
protected $nameConverter;
2529
protected $callbacks = array();
2630
protected $ignoredAttributes = array();
2731
protected $camelizedAttributes = array();
2832

2933
/**
3034
* Sets the {@link ClassMetadataFactory} to use.
3135
*
32-
* @param ClassMetadataFactory $classMetadataFactory
36+
* @param ClassMetadataFactory $classMetadataFactory
37+
* @param NameConverterInterface $nameConverter
3338
*/
34-
public function __construct(ClassMetadataFactory $classMetadataFactory = null)
39+
public function __construct(
40+
ClassMetadataFactory $classMetadataFactory = null,
41+
NameConverterInterface $nameConverter = null
42+
)
3543
{
3644
$this->classMetadataFactory = $classMetadataFactory;
45+
$this->nameConverter = $nameConverter;
3746
}
3847

3948
/**
@@ -77,32 +86,28 @@ public function setIgnoredAttributes(array $ignoredAttributes)
7786
/**
7887
* Set attributes to be camelized on denormalize.
7988
*
89+
* @deprecated since 2.7, to be removed in 3.0. Use Symfony\Component\Serializer\Naming\CamelCaseToUnderscoreNameConverter instead.
90+
*
8091
* @param array $camelizedAttributes
8192
*
93+
* @throws \RuntimeException
94+
*
8295
* @return self
8396
*/
8497
public function setCamelizedAttributes(array $camelizedAttributes)
8598
{
86-
$this->camelizedAttributes = $camelizedAttributes;
87-
88-
return $this;
89-
}
99+
trigger_error('The setCamelizedAttributes() has been deprecated. Use Symfony\Component\Serializer\Naming\CamelCaseToUnderscoreNameConverter instead.', E_USER_DEPRECATED);
90100

91-
/**
92-
* Format an attribute name, for example to convert a snake_case name to camelCase.
93-
*
94-
* @param string $attributeName
95-
* @return string
96-
*/
97-
protected function formatAttribute($attributeName)
98-
{
99-
if (in_array($attributeName, $this->camelizedAttributes)) {
100-
return preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
101-
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
102-
}, $attributeName);
101+
$attributes = array();
102+
foreach ($camelizedAttributes as $camelizedAttribute) {
103+
$attributes[] = lcfirst(preg_replace_callback('/(^|_|\.)+(.)/', function ($match) {
104+
return ('.' === $match[1] ? '_' : '').strtoupper($match[2]);
105+
}, $camelizedAttribute));
103106
}
104107

105-
return $attributeName;
108+
$this->nameConverter = new CamelCaseToUnderscoreNameConverter($attributes);
109+
110+
return $this;
106111
}
107112

108113
/**

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ public function normalize($object, $format = null, array $context = array())
127127
$attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
128128
}
129129

130+
if ($this->nameConverter) {
131+
$attributeName = $this->nameConverter->normalize($attributeName);
132+
}
133+
130134
$attributes[$attributeName] = $attributeValue;
131135
}
132136
}
@@ -161,12 +165,13 @@ public function denormalize($data, $class, $format = null, array $context = arra
161165

162166
$params = array();
163167
foreach ($constructorParameters as $constructorParameter) {
164-
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
168+
$paramName = $constructorParameter->name;
169+
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
165170

166171
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
167172
$ignored = in_array($paramName, $this->ignoredAttributes);
168-
if ($allowed && !$ignored && isset($normalizedData[$paramName])) {
169-
$params[] = $normalizedData[$paramName];
173+
if ($allowed && !$ignored && isset($normalizedData[$key])) {
174+
$params[] = $normalizedData[$key];
170175
// don't run set for a parameter passed to the constructor
171176
unset($normalizedData[$paramName]);
172177
} elseif ($constructorParameter->isOptional()) {
@@ -190,7 +195,11 @@ public function denormalize($data, $class, $format = null, array $context = arra
190195
$ignored = in_array($attribute, $this->ignoredAttributes);
191196

192197
if ($allowed && !$ignored) {
193-
$setter = 'set'.$this->formatAttribute($attribute);
198+
if ($this->nameConverter) {
199+
$attribute = $this->nameConverter->denormalize($attribute);
200+
}
201+
202+
$setter = 'set'.ucfirst($attribute);
194203

195204
if (method_exists($object, $setter)) {
196205
$object->$setter($value);

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,12 @@ public function normalize($object, $format = null, array $context = array())
6464
$attributeValue = $this->serializer->normalize($attributeValue, $format);
6565
}
6666

67-
$attributes[$property->name] = $attributeValue;
67+
$propertyName = $property->name;
68+
if ($this->nameConverter) {
69+
$propertyName = $this->nameConverter->normalize($propertyName);
70+
}
71+
72+
$attributes[$propertyName] = $attributeValue;
6873
}
6974

7075
return $attributes;
@@ -85,12 +90,13 @@ public function denormalize($data, $class, $format = null, array $context = arra
8590

8691
$params = array();
8792
foreach ($constructorParameters as $constructorParameter) {
88-
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
93+
$paramName = $constructorParameter->name;
94+
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName;
8995

9096
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
9197
$ignored = in_array($paramName, $this->ignoredAttributes);
92-
if ($allowed && !$ignored && isset($data[$paramName])) {
93-
$params[] = $data[$paramName];
98+
if ($allowed && !$ignored && isset($data[$key])) {
99+
$params[] = $data[$key];
94100
// don't run set for a parameter passed to the constructor
95101
unset($data[$paramName]);
96102
} elseif (!$constructorParameter->isOptional()) {
@@ -109,7 +115,9 @@ public function denormalize($data, $class, $format = null, array $context = arra
109115
}
110116

111117
foreach ($data as $propertyName => $value) {
112-
$propertyName = lcfirst($this->formatAttribute($propertyName));
118+
if ($this->nameConverter) {
119+
$propertyName = $this->nameConverter->denormalize($propertyName);
120+
}
113121

114122
$allowed = $allowedAttributes === false || in_array($propertyName, $allowedAttributes);
115123
$ignored = in_array($propertyName, $this->ignoredAttributes);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
use Symfony\Component\Serializer\NameConverter\CamelCaseToUnderscoreNameConverter;
14+
15+
/**
16+
* @author Kévin Dunglas <dunglas@gmail.com>
17+
*/
18+
class CamelCaseToUnderscoreNameConverterTest extends \PHPUnit_Framework_TestCase
19+
{
20+
/**
21+
* @dataProvider attributeProvider
22+
*/
23+
public function testNormalize($underscored, $lowerCamelCased)
24+
{
25+
$nameConverter = new CamelCaseToUnderscoreNameConverter();
26+
$this->assertEquals($nameConverter->normalize($lowerCamelCased), $underscored);
27+
}
28+
29+
/**
30+
* @dataProvider attributeProvider
31+
*/
32+
public function testDenormalize($underscored, $lowerCamelCased)
33+
{
34+
$nameConverter = new CamelCaseToUnderscoreNameConverter();
35+
$this->assertEquals($nameConverter->denormalize($underscored), $lowerCamelCased);
36+
}
37+
38+
public function attributeProvider()
39+
{
40+
return array(
41+
array('coop_tilleuls', 'coopTilleuls'),
42+
array('_kevin_dunglas', '_kevinDunglas'),
43+
array('this_is_a_test', 'thisIsATest'),
44+
);
45+
}
46+
}

0 commit comments

Comments
 (0)