Skip to content

Commit 0ab3beb

Browse files
committed
feature #13257 [Serializer] ObjectNormalizer (dunglas)
This PR was merged into the 2.7 branch. Discussion ---------- [Serializer] ObjectNormalizer | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT | Doc PR | not yet `PropertyAccessNormalizer` is a new normalizer leveraging the PropertyAccess Component. It is able to handle classes containing both public properties and properties only accessibles trough getters / setters / issers / hassers... As it extends `AbstractNormalizer`, it supports circular reference handling, name converters and existing object population. What do you think about making this new normalizer the default one as it's the most convenient to use and the most consistent with the behavior of other components. #13120, #13252 and #13255 need to be merged to make this PR working. Commits ------- 0050bbb [Serializer] Introduce ObjectNormalizer
2 parents ff70902 + 0050bbb commit 0ab3beb

File tree

4 files changed

+664
-1
lines changed

4 files changed

+664
-1
lines changed

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ CHANGELOG
1515
`PropertyNormalizer::setCamelizedAttributes()` are replaced by
1616
`CamelCaseToSnakeCaseNameConverter`
1717
* [DEPRECATION] the `Exception` interface has been renamed to `ExceptionInterface`
18+
* added `ObjectNormalizer` leveraging the `PropertyAccess` component to normalize
19+
objects containing both properties and getters / setters / issers / hassers methods.
1820

1921
2.6.0
2022
-----
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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\Normalizer;
13+
14+
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
15+
use Symfony\Component\PropertyAccess\PropertyAccess;
16+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
17+
use Symfony\Component\Serializer\Exception\CircularReferenceException;
18+
use Symfony\Component\Serializer\Exception\LogicException;
19+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
20+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
21+
22+
/**
23+
* Converts between objects and arrays using the PropertyAccess component.
24+
*
25+
* @author Kévin Dunglas <dunglas@gmail.com>
26+
*/
27+
class ObjectNormalizer extends AbstractNormalizer
28+
{
29+
/**
30+
* @var PropertyAccessorInterface
31+
*/
32+
protected $propertyAccessor;
33+
34+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null)
35+
{
36+
parent::__construct($classMetadataFactory, $nameConverter);
37+
38+
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function supportsNormalization($data, $format = null)
45+
{
46+
return is_object($data);
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*
52+
* @throws CircularReferenceException
53+
*/
54+
public function normalize($object, $format = null, array $context = array())
55+
{
56+
if ($this->isCircularReference($object, $context)) {
57+
return $this->handleCircularReference($object);
58+
}
59+
60+
$data = array();
61+
$attributes = $this->getAllowedAttributes($object, $context, true);
62+
63+
// If not using groups, detect manually
64+
if (false === $attributes) {
65+
$attributes = array();
66+
67+
// methods
68+
$reflClass = new \ReflectionClass($object);
69+
foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
70+
if (
71+
!$reflMethod->isConstructor() &&
72+
!$reflMethod->isDestructor() &&
73+
0 === $reflMethod->getNumberOfRequiredParameters()
74+
) {
75+
$name = $reflMethod->getName();
76+
77+
if (strpos($name, 'get') === 0 || strpos($name, 'has') === 0) {
78+
// getters and hassers
79+
$attributes[lcfirst(substr($name, 3))] = true;
80+
} elseif (strpos($name, 'is') === 0) {
81+
// issers
82+
$attributes[lcfirst(substr($name, 2))] = true;
83+
}
84+
}
85+
}
86+
87+
// properties
88+
foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
89+
$attributes[$reflProperty->getName()] = true;
90+
}
91+
92+
$attributes = array_keys($attributes);
93+
}
94+
95+
foreach ($attributes as $attribute) {
96+
if (in_array($attribute, $this->ignoredAttributes)) {
97+
continue;
98+
}
99+
100+
$attributeValue = $this->propertyAccessor->getValue($object, $attribute);
101+
102+
if (isset($this->callbacks[$attribute])) {
103+
$attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
104+
}
105+
106+
if (null !== $attributeValue && !is_scalar($attributeValue)) {
107+
if (!$this->serializer instanceof NormalizerInterface) {
108+
throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute));
109+
}
110+
111+
$attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
112+
}
113+
114+
if ($this->nameConverter) {
115+
$attribute = $this->nameConverter->normalize($attribute);
116+
}
117+
118+
$data[$attribute] = $attributeValue;
119+
}
120+
121+
return $data;
122+
}
123+
124+
/**
125+
* {@inheritdoc}
126+
*/
127+
public function supportsDenormalization($data, $type, $format = null)
128+
{
129+
return class_exists($type);
130+
}
131+
132+
/**
133+
* {@inheritdoc}
134+
*/
135+
public function denormalize($data, $class, $format = null, array $context = array())
136+
{
137+
$allowedAttributes = $this->getAllowedAttributes($class, $context, true);
138+
$normalizedData = $this->prepareForDenormalization($data);
139+
140+
$reflectionClass = new \ReflectionClass($class);
141+
$object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
142+
143+
foreach ($normalizedData as $attribute => $value) {
144+
$allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
145+
$ignored = in_array($attribute, $this->ignoredAttributes);
146+
147+
if ($allowed && !$ignored) {
148+
if ($this->nameConverter) {
149+
$attribute = $this->nameConverter->normalize($attribute);
150+
}
151+
152+
try {
153+
$this->propertyAccessor->setValue($object, $attribute, $value);
154+
} catch (NoSuchPropertyException $exception) {
155+
// Properties not found are ignored
156+
}
157+
}
158+
}
159+
160+
return $object;
161+
}
162+
}

0 commit comments

Comments
 (0)