4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Serializer\Normalizer;
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;
23 * Converts between objects and arrays using the PropertyAccess component.
25 * @author Kévin Dunglas <dunglas@gmail.com>
27 class ObjectNormalizer extends AbstractNormalizer
29 private $attributesCache = array();
32 * @var PropertyAccessorInterface
34 protected $propertyAccessor;
36 public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null)
38 parent::__construct($classMetadataFactory, $nameConverter);
40 $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
46 public function supportsNormalization($data, $format = null)
48 return is_object($data) && !$data instanceof \Traversable;
54 * @throws CircularReferenceException
56 public function normalize($object, $format = null, array $context = array())
58 if (!isset($context['cache_key'])) {
59 $context['cache_key'] = $this->getCacheKey($context);
61 if ($this->isCircularReference($object, $context)) {
62 return $this->handleCircularReference($object);
66 $attributes = $this->getAttributes($object, $context);
68 foreach ($attributes as $attribute) {
69 if (in_array($attribute, $this->ignoredAttributes)) {
73 $attributeValue = $this->propertyAccessor->getValue($object, $attribute);
75 if (isset($this->callbacks[$attribute])) {
76 $attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
79 if (null !== $attributeValue && !is_scalar($attributeValue)) {
80 if (!$this->serializer instanceof NormalizerInterface) {
81 throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute));
84 $attributeValue = $this->serializer->normalize($attributeValue, $format, $context);
87 if ($this->nameConverter) {
88 $attribute = $this->nameConverter->normalize($attribute);
91 $data[$attribute] = $attributeValue;
100 public function supportsDenormalization($data, $type, $format = null)
102 return class_exists($type);
108 public function denormalize($data, $class, $format = null, array $context = array())
110 if (!isset($context['cache_key'])) {
111 $context['cache_key'] = $this->getCacheKey($context);
113 $allowedAttributes = $this->getAllowedAttributes($class, $context, true);
114 $normalizedData = $this->prepareForDenormalization($data);
116 $reflectionClass = new \ReflectionClass($class);
117 $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes);
119 foreach ($normalizedData as $attribute => $value) {
120 if ($this->nameConverter) {
121 $attribute = $this->nameConverter->denormalize($attribute);
124 $allowed = $allowedAttributes === false || in_array($attribute, $allowedAttributes);
125 $ignored = in_array($attribute, $this->ignoredAttributes);
127 if ($allowed && !$ignored) {
129 $this->propertyAccessor->setValue($object, $attribute, $value);
130 } catch (NoSuchPropertyException $exception) {
131 // Properties not found are ignored
139 private function getCacheKey(array $context)
142 return md5(serialize($context));
143 } catch (\Exception $exception) {
144 // The context cannot be serialized, skip the cache
150 * Gets and caches attributes for this class and context.
152 * @param object $object
153 * @param array $context
157 private function getAttributes($object, array $context)
159 $class = get_class($object);
160 $key = $class.'-'.$context['cache_key'];
162 if (isset($this->attributesCache[$key])) {
163 return $this->attributesCache[$key];
166 $allowedAttributes = $this->getAllowedAttributes($object, $context, true);
168 if (false !== $allowedAttributes) {
169 if ($context['cache_key']) {
170 $this->attributesCache[$key] = $allowedAttributes;
173 return $allowedAttributes;
176 if (isset($this->attributesCache[$class])) {
177 return $this->attributesCache[$class];
180 return $this->attributesCache[$class] = $this->extractAttributes($object);
184 * Extracts attributes for this class and context.
186 * @param object $object
190 private function extractAttributes($object)
192 // If not using groups, detect manually
193 $attributes = array();
196 $reflClass = new \ReflectionClass($object);
197 foreach ($reflClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflMethod) {
199 $reflMethod->getNumberOfRequiredParameters() !== 0 ||
200 $reflMethod->isStatic() ||
201 $reflMethod->isConstructor() ||
202 $reflMethod->isDestructor()
207 $name = $reflMethod->name;
209 if (0 === strpos($name, 'get') || 0 === strpos($name, 'has')) {
210 // getters and hassers
211 $propertyName = substr($name, 3);
213 if (!$reflClass->hasProperty($propertyName)) {
214 $propertyName = lcfirst($propertyName);
217 $attributes[$propertyName] = true;
218 } elseif (strpos($name, 'is') === 0) {
220 $propertyName = substr($name, 2);
222 if (!$reflClass->hasProperty($propertyName)) {
223 $propertyName = lcfirst($propertyName);
226 $attributes[$propertyName] = true;
231 foreach ($reflClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflProperty) {
232 if ($reflProperty->isStatic()) {
236 $attributes[$reflProperty->name] = true;
239 return array_keys($attributes);