Skip to content

Commit 37601dd

Browse files
committed
feature #13252 [Serializer] Refactoring and object_to_populate support. (dunglas)
This PR was merged into the 2.7 branch. Discussion ---------- [Serializer] Refactoring and object_to_populate support. | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT | Doc PR | n/a This PR is mainly a refactoring. It move up the common code of `PorpertyNormalizer` and `GetSetMethodNormalizer` into `AbstractNormalizer`. It also adds a news feature: the ability to use an existing object instance for denormalization: ```php $dummy = new GetSetDummy(); $dummy->setFoo('foo'); $obj = $this->normalizer->denormalize( array('bar' => 'bar'), 'GetSetDummy', null, array('object_to_populate' => $dummy) ); // $obj and $dummy are references to the same object ``` Commits ------- b5990be [Serializer] Refactoring and object_to_populate support.
2 parents 16b5614 + b5990be commit 37601dd

File tree

4 files changed

+109
-71
lines changed

4 files changed

+109
-71
lines changed

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

+84
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,88 @@ protected function getAllowedAttributes($classOrObject, array $context)
214214

215215
return array_unique($allowedAttributes);
216216
}
217+
218+
/**
219+
* Normalizes the given data to an array. It's particularly useful during
220+
* the denormalization process.
221+
*
222+
* @param object|array $data
223+
*
224+
* @return array
225+
*/
226+
protected function prepareForDenormalization($data)
227+
{
228+
if (is_array($data) || is_object($data) && $data instanceof \ArrayAccess) {
229+
$normalizedData = $data;
230+
} elseif (is_object($data)) {
231+
$normalizedData = array();
232+
233+
foreach ($data as $attribute => $value) {
234+
$normalizedData[$attribute] = $value;
235+
}
236+
} else {
237+
$normalizedData = array();
238+
}
239+
240+
return $normalizedData;
241+
}
242+
243+
/**
244+
* Instantiates an object using contructor parameters when needed.
245+
*
246+
* This method also allows to denormalize data into an existing object if
247+
* it is present in the context with the object_to_populate key.
248+
*
249+
* @param array $data
250+
* @param string $class
251+
* @param array $context
252+
* @param \ReflectionClass $reflectionClass
253+
* @param array|bool $allowedAttributes
254+
*
255+
* @return object
256+
*
257+
* @throws RuntimeException
258+
*/
259+
protected function instantiateObject(array $data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
260+
{
261+
if (
262+
isset($context['object_to_populate']) &&
263+
is_object($context['object_to_populate']) &&
264+
$class === get_class($context['object_to_populate'])
265+
) {
266+
return $context['object_to_populate'];
267+
}
268+
269+
$constructor = $reflectionClass->getConstructor();
270+
if ($constructor) {
271+
$constructorParameters = $constructor->getParameters();
272+
273+
$params = array();
274+
foreach ($constructorParameters as $constructorParameter) {
275+
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
276+
277+
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
278+
$ignored = in_array($paramName, $this->ignoredAttributes);
279+
if ($allowed && !$ignored && isset($data[$paramName])) {
280+
$params[] = $data[$paramName];
281+
// don't run set for a parameter passed to the constructor
282+
unset($data[$paramName]);
283+
} elseif ($constructorParameter->isOptional()) {
284+
$params[] = $constructorParameter->getDefaultValue();
285+
} else {
286+
throw new RuntimeException(
287+
sprintf(
288+
'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.',
289+
$class,
290+
$constructorParameter->name
291+
)
292+
);
293+
}
294+
}
295+
296+
return $reflectionClass->newInstanceArgs($params);
297+
}
298+
299+
return new $class();
300+
}
217301
}

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

+4-42
Original file line numberDiff line numberDiff line change
@@ -86,54 +86,16 @@ public function normalize($object, $format = null, array $context = array())
8686

8787
/**
8888
* {@inheritdoc}
89+
*
90+
* @throws RuntimeException
8991
*/
9092
public function denormalize($data, $class, $format = null, array $context = array())
9193
{
9294
$allowedAttributes = $this->getAllowedAttributes($class, $context);
93-
94-
if (is_array($data) || is_object($data) && $data instanceof \ArrayAccess) {
95-
$normalizedData = $data;
96-
} elseif (is_object($data)) {
97-
$normalizedData = array();
98-
99-
foreach ($data as $attribute => $value) {
100-
$normalizedData[$attribute] = $value;
101-
}
102-
} else {
103-
$normalizedData = array();
104-
}
95+
$normalizedData = $this->prepareForDenormalization($data);
10596

10697
$reflectionClass = new \ReflectionClass($class);
107-
$constructor = $reflectionClass->getConstructor();
108-
109-
if ($constructor) {
110-
$constructorParameters = $constructor->getParameters();
111-
112-
$params = array();
113-
foreach ($constructorParameters as $constructorParameter) {
114-
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
115-
116-
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
117-
$ignored = in_array($paramName, $this->ignoredAttributes);
118-
if ($allowed && !$ignored && isset($normalizedData[$paramName])) {
119-
$params[] = $normalizedData[$paramName];
120-
// don't run set for a parameter passed to the constructor
121-
unset($normalizedData[$paramName]);
122-
} elseif ($constructorParameter->isOptional()) {
123-
$params[] = $constructorParameter->getDefaultValue();
124-
} else {
125-
throw new RuntimeException(
126-
'Cannot create an instance of '.$class.
127-
' from serialized data because its constructor requires '.
128-
'parameter "'.$constructorParameter->name.
129-
'" to be present.');
130-
}
131-
}
132-
133-
$object = $reflectionClass->newInstanceArgs($params);
134-
} else {
135-
$object = new $class();
136-
}
98+
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
13799

138100
foreach ($normalizedData as $attribute => $value) {
139101
$allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);

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

+4-29
Original file line numberDiff line numberDiff line change
@@ -79,41 +79,16 @@ public function normalize($object, $format = null, array $context = array())
7979

8080
/**
8181
* {@inheritdoc}
82+
*
83+
* @throws RuntimeException
8284
*/
8385
public function denormalize($data, $class, $format = null, array $context = array())
8486
{
8587
$allowedAttributes = $this->getAllowedAttributes($class, $context);
88+
$data = $this->prepareForDenormalization($data);
8689

8790
$reflectionClass = new \ReflectionClass($class);
88-
$constructor = $reflectionClass->getConstructor();
89-
90-
if ($constructor) {
91-
$constructorParameters = $constructor->getParameters();
92-
93-
$params = array();
94-
foreach ($constructorParameters as $constructorParameter) {
95-
$paramName = lcfirst($this->formatAttribute($constructorParameter->name));
96-
97-
$allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes);
98-
$ignored = in_array($paramName, $this->ignoredAttributes);
99-
if ($allowed && !$ignored && isset($data[$paramName])) {
100-
$params[] = $data[$paramName];
101-
// don't run set for a parameter passed to the constructor
102-
unset($data[$paramName]);
103-
} elseif (!$constructorParameter->isOptional()) {
104-
throw new RuntimeException(sprintf(
105-
'Cannot create an instance of %s from serialized data because '.
106-
'its constructor requires parameter "%s" to be present.',
107-
$class,
108-
$constructorParameter->name
109-
));
110-
}
111-
}
112-
113-
$object = $reflectionClass->newInstanceArgs($params);
114-
} else {
115-
$object = new $class();
116-
}
91+
$object = $this->instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes);
11792

11893
foreach ($data as $propertyName => $value) {
11994
$propertyName = lcfirst($this->formatAttribute($propertyName));

src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php

+17
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,23 @@ public function testCircularReferenceHandler()
385385
$expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy');
386386
$this->assertEquals($expected, $this->normalizer->normalize($obj));
387387
}
388+
389+
public function testObjectToPopulate()
390+
{
391+
$dummy = new GetSetDummy();
392+
$dummy->setFoo('foo');
393+
394+
$obj = $this->normalizer->denormalize(
395+
array('bar' => 'bar'),
396+
__NAMESPACE__.'\GetSetDummy',
397+
null,
398+
array('object_to_populate' => $dummy)
399+
);
400+
401+
$this->assertEquals($dummy, $obj);
402+
$this->assertEquals('foo', $obj->getFoo());
403+
$this->assertEquals('bar', $obj->getBar());
404+
}
388405
}
389406

390407
class GetSetDummy

0 commit comments

Comments
 (0)