diff --git a/src/Symfony/Component/Serializer/Annotation/Groups.php b/src/Symfony/Component/Serializer/Annotation/Groups.php index 519837a55b73e..a3d9451246687 100644 --- a/src/Symfony/Component/Serializer/Annotation/Groups.php +++ b/src/Symfony/Component/Serializer/Annotation/Groups.php @@ -35,7 +35,7 @@ class Groups */ public function __construct(array $data) { - if (!isset($data['value']) || !$data['value']) { + if (empty($data['value'])) { throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', get_class($this))); } diff --git a/src/Symfony/Component/Serializer/Annotation/MaxDepth.php b/src/Symfony/Component/Serializer/Annotation/MaxDepth.php new file mode 100644 index 0000000000000..639d44cb57571 --- /dev/null +++ b/src/Symfony/Component/Serializer/Annotation/MaxDepth.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Annotation; + +use Symfony\Component\Serializer\Exception\InvalidArgumentException; + +/** + * Annotation class for @MaxDepth(). + * + * @Annotation + * @Target({"PROPERTY", "METHOD"}) + * + * @author Kévin Dunglas + */ +class MaxDepth +{ + /** + * @var int + */ + private $maxDepth; + + public function __construct(array $data) + { + if (empty($data['value'])) { + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', get_class($this))); + } + + if (!is_int($data['value']) || $data['value'] <= 0) { + throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be a positive integer.', get_class($this))); + } + + $this->maxDepth = $data['value']; + } + + public function getMaxDepth() + { + return $this->maxDepth; + } +} diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php index 7a1d3db94a809..b9daf5d25b829 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadata.php @@ -36,6 +36,15 @@ class AttributeMetadata implements AttributeMetadataInterface */ public $groups = array(); + /** + * @var int|null + * + * @internal This property is public in order to reduce the size of the + * class' serialized representation. Do not access it. Use + * {@link getMaxDepth()} instead. + */ + public $maxDepth; + /** * Constructs a metadata for the given attribute. * @@ -72,6 +81,22 @@ public function getGroups() return $this->groups; } + /** + * {@inheritdoc} + */ + public function setMaxDepth($maxDepth) + { + $this->maxDepth = $maxDepth; + } + + /** + * {@inheritdoc} + */ + public function getMaxDepth() + { + return $this->maxDepth; + } + /** * {@inheritdoc} */ @@ -80,6 +105,11 @@ public function merge(AttributeMetadataInterface $attributeMetadata) foreach ($attributeMetadata->getGroups() as $group) { $this->addGroup($group); } + + // Overwrite only if not defined + if (null === $this->maxDepth) { + $this->maxDepth = $attributeMetadata->getMaxDepth(); + } } /** @@ -89,6 +119,6 @@ public function merge(AttributeMetadataInterface $attributeMetadata) */ public function __sleep() { - return array('name', 'groups'); + return array('name', 'groups', 'maxDepth'); } } diff --git a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php index 0701a58b56257..035cd788bc9a3 100644 --- a/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/AttributeMetadataInterface.php @@ -41,6 +41,20 @@ public function addGroup($group); */ public function getGroups(); + /** + * Sets the serialization max depth for this attribute. + * + * @param int|null $maxDepth + */ + public function setMaxDepth($maxDepth); + + /** + * Gets the serialization max depth for this attribute. + * + * @return int|null + */ + public function getMaxDepth(); + /** * Merges an {@see AttributeMetadataInterface} with in the current one. * diff --git a/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php b/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php index 55ddf52a64bb5..871f2c73052ee 100644 --- a/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php +++ b/src/Symfony/Component/Serializer/Mapping/ClassMetadata.php @@ -28,7 +28,7 @@ class ClassMetadata implements ClassMetadataInterface public $name; /** - * @var AttributeMetadataInterface[] + * @var array * * @internal This property is public in order to reduce the size of the * class' serialized representation. Do not access it. Use diff --git a/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php b/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php index 095587d8961b3..52df1f9c7a299 100644 --- a/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php +++ b/src/Symfony/Component/Serializer/Mapping/ClassMetadataInterface.php @@ -39,7 +39,7 @@ public function addAttributeMetadata(AttributeMetadataInterface $attributeMetada /** * Gets the list of {@link AttributeMetadataInterface}. * - * @return AttributeMetadataInterface[] + * @return array An array containing the attribute name as key and the related instance of AttributeMetadataInterface as value. */ public function getAttributesMetadata(); diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php index 6c563b44e9f33..4495f0d56c3bf 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/AnnotationLoader.php @@ -13,6 +13,7 @@ use Doctrine\Common\Annotations\Reader; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\MaxDepth; use Symfony\Component\Serializer\Exception\MappingException; use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; @@ -55,11 +56,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) } if ($property->getDeclaringClass()->name === $className) { - foreach ($this->reader->getPropertyAnnotations($property) as $groups) { - if ($groups instanceof Groups) { - foreach ($groups->getGroups() as $group) { + foreach ($this->reader->getPropertyAnnotations($property) as $annotation) { + if ($annotation instanceof Groups) { + foreach ($annotation->getGroups() as $group) { $attributesMetadata[$property->name]->addGroup($group); } + } elseif ($annotation instanceof MaxDepth) { + $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); } $loaded = true; @@ -68,29 +71,40 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) } foreach ($reflectionClass->getMethods() as $method) { - if ($method->getDeclaringClass()->name === $className) { - foreach ($this->reader->getMethodAnnotations($method) as $groups) { - if ($groups instanceof Groups) { - if (preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches)) { - $attributeName = lcfirst($matches[2]); - - if (isset($attributesMetadata[$attributeName])) { - $attributeMetadata = $attributesMetadata[$attributeName]; - } else { - $attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName); - $classMetadata->addAttributeMetadata($attributeMetadata); - } - - foreach ($groups->getGroups() as $group) { - $attributeMetadata->addGroup($group); - } - } else { - throw new MappingException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); - } + if ($method->getDeclaringClass()->name !== $className) { + continue; + } + + $accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches); + if ($accessorOrMutator) { + $attributeName = lcfirst($matches[2]); + + if (isset($attributesMetadata[$attributeName])) { + $attributeMetadata = $attributesMetadata[$attributeName]; + } else { + $attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName); + $classMetadata->addAttributeMetadata($attributeMetadata); + } + } + + foreach ($this->reader->getMethodAnnotations($method) as $annotation) { + if ($annotation instanceof Groups) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('Groups on "%s::%s" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $loaded = true; + foreach ($annotation->getGroups() as $group) { + $attributeMetadata->addGroup($group); + } + } elseif ($annotation instanceof MaxDepth) { + if (!$accessorOrMutator) { + throw new MappingException(sprintf('MaxDepth on "%s::%s" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + } + + $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); } + + $loaded = true; } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php index 0da2f7d690ff6..f20fba37a214a 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php @@ -62,6 +62,10 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) foreach ($attribute->group as $group) { $attributeMetadata->addGroup((string) $group); } + + if (isset($attribute['max-depth'])) { + $attributeMetadata->setMaxDepth((int) $attribute['max-depth']); + } } return true; diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php index 1c84698594e5b..2ecff526a8d65 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Serializer/Mapping/Loader/YamlFileLoader.php @@ -65,6 +65,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) if (isset($yaml['attributes']) && is_array($yaml['attributes'])) { $attributesMetadata = $classMetadata->getAttributesMetadata(); + foreach ($yaml['attributes'] as $attribute => $data) { if (isset($attributesMetadata[$attribute])) { $attributeMetadata = $attributesMetadata[$attribute]; @@ -75,9 +76,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata) if (isset($data['groups'])) { foreach ($data['groups'] as $group) { - $attributeMetadata->addGroup($group); + $attributeMetadata->addGroup((string) $group); } } + + if (isset($data['max_depth'])) { + $attributeMetadata->setMaxDepth((int) $data['max_depth']); + } } } diff --git a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd index cd5a9a9f0df82..e7b9967986f00 100644 --- a/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd +++ b/src/Symfony/Component/Serializer/Mapping/Loader/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd @@ -47,10 +47,11 @@ Contains serialization groups for a attributes. The name of the attribute should be given in the "name" option. ]]> - + + diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index d555fbadd5208..cc1602eb33a0a 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Serializer\Normalizer; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface; @@ -25,6 +27,12 @@ */ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements NormalizerInterface, DenormalizerInterface { + const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit'; + const OBJECT_TO_POPULATE = 'object_to_populate'; + const GROUPS = 'groups'; + const ENABLE_MAX_DEPTH = 'enable_max_depth'; + const DEPTH_KEY_PATTERN = 'depth_%s::%s'; + /** * @var int */ @@ -54,16 +62,22 @@ abstract class AbstractNormalizer extends SerializerAwareNormalizer implements N */ protected $camelizedAttributes = array(); + /** + * @var PropertyInfoExtractorInterface + */ + protected $propertyInfoExtractor; + /** * Sets the {@link ClassMetadataFactoryInterface} to use. * * @param ClassMetadataFactoryInterface|null $classMetadataFactory * @param NameConverterInterface|null $nameConverter */ - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null) + public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyInfoExtractorInterface $propertyInfoExtractor = null) { $this->classMetadataFactory = $classMetadataFactory; $this->nameConverter = $nameConverter; + $this->propertyInfoExtractor = $propertyInfoExtractor; } /** @@ -146,16 +160,16 @@ protected function isCircularReference($object, &$context) { $objectHash = spl_object_hash($object); - if (isset($context['circular_reference_limit'][$objectHash])) { - if ($context['circular_reference_limit'][$objectHash] >= $this->circularReferenceLimit) { - unset($context['circular_reference_limit'][$objectHash]); + if (isset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash])) { + if ($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] >= $this->circularReferenceLimit) { + unset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]); return true; } - ++$context['circular_reference_limit'][$objectHash]; + ++$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]; } else { - $context['circular_reference_limit'][$objectHash] = 1; + $context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] = 1; } return false; @@ -193,13 +207,13 @@ protected function handleCircularReference($object) */ protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) { - if (!$this->classMetadataFactory || !isset($context['groups']) || !is_array($context['groups'])) { + if (!$this->classMetadataFactory || !isset($context[static::GROUPS]) || !is_array($context[static::GROUPS])) { return false; } $allowedAttributes = array(); foreach ($this->classMetadataFactory->getMetadataFor($classOrObject)->getAttributesMetadata() as $attributeMetadata) { - if (count(array_intersect($attributeMetadata->getGroups(), $context['groups']))) { + if (count(array_intersect($attributeMetadata->getGroups(), $context[static::GROUPS]))) { $allowedAttributes[] = $attributesAsString ? $attributeMetadata->getName() : $attributeMetadata; } } @@ -239,52 +253,209 @@ protected function prepareForDenormalization($data) protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes) { if ( - isset($context['object_to_populate']) && - is_object($context['object_to_populate']) && - $class === get_class($context['object_to_populate']) + isset($context[static::OBJECT_TO_POPULATE]) && + is_object($context[static::OBJECT_TO_POPULATE]) && + $class === get_class($context[static::OBJECT_TO_POPULATE]) ) { - return $context['object_to_populate']; + return $context[static::OBJECT_TO_POPULATE]; + } + + $format = null; + if (isset($context['format'])) { + $format = $context['format']; } $constructor = $reflectionClass->getConstructor(); - if ($constructor) { - $constructorParameters = $constructor->getParameters(); - - $params = array(); - foreach ($constructorParameters as $constructorParameter) { - $paramName = $constructorParameter->name; - $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName; - - $allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes); - $ignored = in_array($paramName, $this->ignoredAttributes); - if (method_exists($constructorParameter, 'isVariadic') && $constructorParameter->isVariadic()) { - if ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) { - if (!is_array($data[$paramName])) { - throw new RuntimeException(sprintf('Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.', $class, $constructorParameter->name)); - } - - $params = array_merge($params, $data[$paramName]); - } - } elseif ($allowed && !$ignored && (isset($data[$key]) || array_key_exists($key, $data))) { - $params[] = $data[$key]; - // don't run set for a parameter passed to the constructor - unset($data[$key]); - } elseif ($constructorParameter->isDefaultValueAvailable()) { - $params[] = $constructorParameter->getDefaultValue(); - } else { - throw new RuntimeException( - sprintf( - 'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.', - $class, - $constructorParameter->name - ) - ); - } + if (!$constructor) { + return new $class(); + } + + $constructorParameters = $constructor->getParameters(); + + $params = array(); + foreach ($constructorParameters as $constructorParameter) { + $paramName = $constructorParameter->name; + $key = $this->nameConverter ? $this->nameConverter->normalize($paramName) : $paramName; + + $allowed = $allowedAttributes === false || in_array($paramName, $allowedAttributes); + $ignored = in_array($paramName, $this->ignoredAttributes); + + if (!$allowed || $ignored) { + continue; + } + + $missing = !isset($data[$key]) && !array_key_exists($key, $data); + $variadic = method_exists($constructorParameter, 'isVariadic') && $constructorParameter->isVariadic(); + + if ($variadic && !$missing && !is_array($data[$paramName])) { + $message = 'Cannot create an instance of %s from serialized data because the variadic parameter %s can only accept an array.'; + throw new RuntimeException(sprintf($message, $class, $constructorParameter->name)); + } + + if ($variadic && !$missing) { + $params = array_merge($params, $data[$paramName]); + + continue; + } + + if ($missing && !$variadic && !$constructorParameter->isDefaultValueAvailable()) { + $message = 'Cannot create an instance of %s from serialized data because its constructor requires parameter "%s" to be present.'; + throw new RuntimeException(sprintf($message, $class, $constructorParameter->name)); + } + + if ($missing && $constructorParameter->isDefaultValueAvailable()) { + $params[] = $constructorParameter->getDefaultValue(); + + continue; + } + + if (!$missing) { + $params[] = $this->denormalizeProperty($data[$key], $class, $key, $format, $context); + + unset($data[$key]); + } + } + + return $reflectionClass->newInstanceArgs($params); + } + + /** + * @param mixed $data + * @param string $class + * @param string $name + * @param string $format + * @param array $context + * + * @return mixed|object + */ + protected function denormalizeProperty($data, $class, $name, $format = null, array $context = array()) + { + if (!$this->propertyInfoExtractor) { + return $data; + } + + $types = $this->propertyInfoExtractor->getTypes($class, $name); + + if (empty($types)) { + return $data; + } + + foreach ($types as $type) { + if ($data === null && $type->isNullable()) { + return $data; + } + + if (!$this->serializer instanceof DenormalizerInterface) { + $message = 'Cannot denormalize attribute "%s" because injected serializer is not a denormalizer'; + throw new RuntimeException(sprintf($message, $name)); + } + + return $this->serializer->denormalize($data, $type->getClassName(), $format, $context); + } + + } + + /** + * Should this attribute be normalized? + * + * @param mixed $object + * @param string $attributeName + * @param array $context + * + * @return bool + */ + protected function isAttributeToNormalize($object, $attributeName, &$context) + { + return !in_array($attributeName, $this->ignoredAttributes) && !$this->isMaxDepthReached(get_class($object), $attributeName, $context); + } + + /** + * Sets an attribute and apply the name converter if necessary. + * + * @param array $data + * @param string $attribute + * @param mixed $attributeValue + * + * @return array + */ + protected function setAttribute(array $data, $attribute, $attributeValue) + { + if ($this->nameConverter) { + $attribute = $this->nameConverter->normalize($attribute); + } + + $data[$attribute] = $attributeValue; + + return $data; + } + + /** + * Normalizes complex types at the end of the process (recursive call). + * + * @param array $data + * @param array $stack + * @param string|null $format + * @param array $context + * + * @return array + * + * @throws LogicException + */ + protected function normalizeComplexTypes(array $data, array $stack, $format, array &$context) + { + foreach ($stack as $attribute => $attributeValue) { + if (!$this->serializer instanceof NormalizerInterface) { + throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute)); } - return $reflectionClass->newInstanceArgs($params); + $attributeValue = $this->serializer->normalize($attributeValue, $format, $context); + + $data = $this->setAttribute($data, $attribute, $attributeValue); } - return new $class(); + return $data; + } + + /** + * Is the max depth reached for the given attribute? + * + * @param string $class + * @param string $attribute + * @param array $context + * + * @return bool + */ + private function isMaxDepthReached($class, $attribute, array &$context) + { + if (!$this->classMetadataFactory || !isset($context[static::ENABLE_MAX_DEPTH])) { + return false; + } + + $classMetadata = $this->classMetadataFactory->getMetadataFor($class); + $attributesMetadata = $classMetadata->getAttributesMetadata(); + + if (!isset($attributesMetadata[$attribute])) { + return false; + } + + $maxDepth = $attributesMetadata[$attribute]->getMaxDepth(); + if (null === $maxDepth) { + return false; + } + + $key = sprintf(static::DEPTH_KEY_PATTERN, $class, $attribute); + $keyExist = isset($context[$key]); + + if ($keyExist && $context[$key] === $maxDepth) { + return true; + } + + if ($keyExist) { + ++$context[$key]; + } else { + $context[$key] = 1; + } + + return false; } } diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index fc7ac9f4631de..e924e5e6680d0 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -55,38 +55,37 @@ public function normalize($object, $format = null, array $context = array()) $allowedAttributes = $this->getAllowedAttributes($object, $context, true); $attributes = array(); + $stack = array(); + foreach ($reflectionMethods as $method) { - if ($this->isGetMethod($method)) { - $attributeName = lcfirst(substr($method->name, 0 === strpos($method->name, 'is') ? 2 : 3)); - if (in_array($attributeName, $this->ignoredAttributes)) { - continue; - } - - if (false !== $allowedAttributes && !in_array($attributeName, $allowedAttributes)) { - continue; - } - - $attributeValue = $method->invoke($object); - if (isset($this->callbacks[$attributeName])) { - $attributeValue = call_user_func($this->callbacks[$attributeName], $attributeValue); - } - if (null !== $attributeValue && !is_scalar($attributeValue)) { - if (!$this->serializer instanceof NormalizerInterface) { - throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attributeName)); - } + if (!$this->isGetMethod($method)) { + continue; + } - $attributeValue = $this->serializer->normalize($attributeValue, $format, $context); - } + $attributeName = lcfirst(substr($method->name, 0 === strpos($method->name, 'is') ? 2 : 3)); + if (!$this->isAttributeToNormalize($object, $attributeName, $context)) { + continue; + } + + if (false !== $allowedAttributes && !in_array($attributeName, $allowedAttributes)) { + continue; + } - if ($this->nameConverter) { - $attributeName = $this->nameConverter->normalize($attributeName); - } + $attributeValue = $method->invoke($object); + if (isset($this->callbacks[$attributeName])) { + $attributeValue = call_user_func($this->callbacks[$attributeName], $attributeValue); + } - $attributes[$attributeName] = $attributeValue; + // Recursive calls are done at the end of the process to allow @MaxDepth to work + if (null !== $attributeValue && !is_scalar($attributeValue)) { + $stack[$attributeName] = $attributeValue; + continue; } + + $attributes = $this->setAttribute($attributes, $attributeName, $attributeValue); } - return $attributes; + return $this->normalizeComplexTypes($attributes, $stack, $format, $context); } /** @@ -100,7 +99,8 @@ public function denormalize($data, $class, $format = null, array $context = arra $normalizedData = $this->prepareForDenormalization($data); $reflectionClass = new \ReflectionClass($class); - $object = $this->instantiateObject($normalizedData, $class, $context, $reflectionClass, $allowedAttributes); + $subcontext = array_merge($context, array('format' => $format)); + $object = $this->instantiateObject($normalizedData, $class, $subcontext, $reflectionClass, $allowedAttributes); $classMethods = get_class_methods($object); foreach ($normalizedData as $attribute => $value) { @@ -113,11 +113,45 @@ public function denormalize($data, $class, $format = null, array $context = arra if ($allowed && !$ignored) { $setter = 'set'.ucfirst($attribute); - if (in_array($setter, $classMethods) && !$reflectionClass->getMethod($setter)->isStatic()) { - $object->$setter($value); - } + if ($this->propertyInfoExtractor) { + $types = (array) $this->propertyInfoExtractor->getTypes($class, $attribute); + + foreach ($types as $type) { + if ($type && (!empty($value) || !$type->isNullable())) { + if (!$this->serializer instanceof DenormalizerInterface) { + throw new RuntimeException( + sprintf( + 'Cannot denormalize attribute "%s" because injected serializer is not a denormalizer', + $attribute + ) + ); + } + + $value = $this->serializer->denormalize( + $value, + $type->getClassName(), + $format, + $context + ); + + break; + } + } + } + + if (!$allowed || $ignored) { + continue; } + + $setter = 'set'.ucfirst($attribute); + if (!in_array($setter, $classMethods)) { + continue; + } + + $value = $this->denormalizeProperty($value, $class, $attribute, $format, $context); + + $object->$setter($value); } return $object; diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index c731dd7a97f17..8913b3ff5aa16 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -15,7 +15,6 @@ use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Serializer\Exception\CircularReferenceException; -use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -60,10 +59,11 @@ public function normalize($object, $format = null, array $context = array()) } $data = array(); + $stack = array(); $attributes = $this->getAttributes($object, $context); foreach ($attributes as $attribute) { - if (in_array($attribute, $this->ignoredAttributes)) { + if (!$this->isAttributeToNormalize($object, $attribute, $context)) { continue; } @@ -73,22 +73,16 @@ public function normalize($object, $format = null, array $context = array()) $attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue); } + // Recursive calls are done at the end of the process to allow @MaxDepth to work if (null !== $attributeValue && !is_scalar($attributeValue)) { - if (!$this->serializer instanceof NormalizerInterface) { - throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $attribute)); - } - - $attributeValue = $this->serializer->normalize($attributeValue, $format, $context); - } - - if ($this->nameConverter) { - $attribute = $this->nameConverter->normalize($attribute); + $stack[$attribute] = $attributeValue; + continue; } - $data[$attribute] = $attributeValue; + $data = $this->setAttribute($data, $attribute, $attributeValue); } - return $data; + return $this->normalizeComplexTypes($data, $stack, $format, $context); } /** diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index 993046f3ac872..da7af2ac08b7e 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\Serializer\Exception\CircularReferenceException; -use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\RuntimeException; /** @@ -47,10 +46,11 @@ public function normalize($object, $format = null, array $context = array()) $reflectionObject = new \ReflectionObject($object); $attributes = array(); + $stack = array(); $allowedAttributes = $this->getAllowedAttributes($object, $context, true); foreach ($reflectionObject->getProperties() as $property) { - if (in_array($property->name, $this->ignoredAttributes) || $property->isStatic()) { + if ($property->isStatic() || !$this->isAttributeToNormalize($object, $property->name, $context)) { continue; } @@ -68,23 +68,17 @@ public function normalize($object, $format = null, array $context = array()) if (isset($this->callbacks[$property->name])) { $attributeValue = call_user_func($this->callbacks[$property->name], $attributeValue); } - if (null !== $attributeValue && !is_scalar($attributeValue)) { - if (!$this->serializer instanceof NormalizerInterface) { - throw new LogicException(sprintf('Cannot normalize attribute "%s" because injected serializer is not a normalizer', $property->name)); - } - - $attributeValue = $this->serializer->normalize($attributeValue, $format, $context); - } - $propertyName = $property->name; - if ($this->nameConverter) { - $propertyName = $this->nameConverter->normalize($propertyName); + // Recursive calls are done at the end of the process to allow @MaxDepth to work + if (null !== $attributeValue && !is_scalar($attributeValue)) { + $stack[$property->name] = $attributeValue; + continue; } - $attributes[$propertyName] = $attributeValue; + $attributes = $this->setAttribute($attributes, $property->name, $attributeValue); } - return $attributes; + return $this->normalizeComplexTypes($attributes, $stack, $format, $context); } /** diff --git a/src/Symfony/Component/Serializer/Tests/Annotation/MaxDepthTest.php b/src/Symfony/Component/Serializer/Tests/Annotation/MaxDepthTest.php new file mode 100644 index 0000000000000..2a51ffbe5d1ca --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Annotation/MaxDepthTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Annotation; + +use Symfony\Component\Serializer\Annotation\MaxDepth; + +/** + * @author Kévin Dunglas + */ +class MaxDepthTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException + */ + public function testNotAnIntMaxDepthParameter() + { + new MaxDepth(array('value' => 'foo')); + } + + public function testMaxDepthParameters() + { + $validData = 3; + + $groups = new MaxDepth(array('value' => 3)); + $this->assertEquals($validData, $groups->getMaxDepth()); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/MaxDepthDummy.php b/src/Symfony/Component/Serializer/Tests/Fixtures/MaxDepthDummy.php new file mode 100644 index 0000000000000..aef6dda2966eb --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/MaxDepthDummy.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Fixtures; + +use Symfony\Component\Serializer\Annotation\MaxDepth; + +/** + * @author Kévin Dunglas + */ +class MaxDepthDummy +{ + /** + * @MaxDepth(2) + */ + public $foo; + + public $bar; + + /** + * @var self + */ + public $child; + + /** + * @MaxDepth(3) + */ + public function getBar() + { + return $this->bar; + } + + public function getChild() + { + return $this->child; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml index 6e95aaf72118b..9ba51cbfdf6d4 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.xml @@ -15,4 +15,9 @@ + + + + + diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml index e855ea472b3d6..c4038704a50de 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/serialization.yml @@ -1,6 +1,12 @@ -Symfony\Component\Serializer\Tests\Fixtures\GroupDummy: +'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy': attributes: foo: groups: ['group1', 'group2'] bar: groups: ['group2'] +'Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy': + attributes: + foo: + max_depth: 2 + bar: + max_depth: 3 diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php index 4a32831cb698d..d24baa5c4e88d 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/AttributeMetadataTest.php @@ -40,6 +40,14 @@ public function testGroups() $this->assertEquals(array('a', 'b'), $attributeMetadata->getGroups()); } + public function testMaxDepth() + { + $attributeMetadata = new AttributeMetadata('name'); + $attributeMetadata->setMaxDepth(69); + + $this->assertEquals(69, $attributeMetadata->getMaxDepth()); + } + public function testMerge() { $attributeMetadata1 = new AttributeMetadata('a1'); @@ -49,10 +57,12 @@ public function testMerge() $attributeMetadata2 = new AttributeMetadata('a2'); $attributeMetadata2->addGroup('a'); $attributeMetadata2->addGroup('c'); + $attributeMetadata2->setMaxDepth(2); $attributeMetadata1->merge($attributeMetadata2); $this->assertEquals(array('a', 'b', 'c'), $attributeMetadata1->getGroups()); + $this->assertEquals(2, $attributeMetadata1->getMaxDepth()); } public function testSerialize() @@ -60,6 +70,7 @@ public function testSerialize() $attributeMetadata = new AttributeMetadata('attribute'); $attributeMetadata->addGroup('a'); $attributeMetadata->addGroup('b'); + $attributeMetadata->setMaxDepth(3); $serialized = serialize($attributeMetadata); $this->assertEquals($attributeMetadata, unserialize($serialized)); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php index 484d062f22375..6dc4009b51824 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -43,7 +43,7 @@ public function testLoadClassMetadataReturnsTrueIfSuccessful() $this->assertTrue($this->loader->loadClassMetadata($classMetadata)); } - public function testLoadClassMetadata() + public function testLoadGroups() { $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy'); $this->loader->loadClassMetadata($classMetadata); @@ -51,6 +51,16 @@ public function testLoadClassMetadata() $this->assertEquals(TestClassMetadataFactory::createClassMetadata(), $classMetadata); } + public function testLoadMaxDepth() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(2, $attributesMetadata['foo']->getMaxDepth()); + $this->assertEquals(3, $attributesMetadata['bar']->getMaxDepth()); + } + public function testLoadClassMetadataAndMerge() { $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\GroupDummy'); diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php index 6b468ff18189c..2b3beef61ebc2 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -51,4 +51,14 @@ public function testLoadClassMetadata() $this->assertEquals(TestClassMetadataFactory::createXmlCLassMetadata(), $this->metadata); } + + public function testMaxDepth() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(2, $attributesMetadata['foo']->getMaxDepth()); + $this->assertEquals(3, $attributesMetadata['bar']->getMaxDepth()); + } } diff --git a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php index 72d146f9f5224..2dd1dfb3bc52c 100644 --- a/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -66,4 +66,14 @@ public function testLoadClassMetadata() $this->assertEquals(TestClassMetadataFactory::createXmlCLassMetadata(), $this->metadata); } + + public function testMaxDepth() + { + $classMetadata = new ClassMetadata('Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy'); + $this->loader->loadClassMetadata($classMetadata); + + $attributesMetadata = $classMetadata->getAttributesMetadata(); + $this->assertEquals(2, $attributesMetadata['foo']->getMaxDepth()); + $this->assertEquals(3, $attributesMetadata['bar']->getMaxDepth()); + } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index 3e98e3ed1841e..4b4c4acca568a 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -12,12 +12,15 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; +use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -490,11 +493,69 @@ public function testNoStaticGetSetSupport() $this->assertFalse($this->normalizer->supportsNormalization(new ObjectWithJustStaticSetterDummy())); } + public function testDenormalizeWithTypehint() + { + /* need a serializer that can recurse denormalization $normalizer */ + $normalizer = new GetSetMethodNormalizer(null, null, new PropertyInfoExtractor(array(), array(new ReflectionExtractor()))); + $serializer = new Serializer(array($normalizer)); + $normalizer->setSerializer($serializer); + + $obj = $normalizer->denormalize( + array( + 'object' => array('foo' => 'foo', 'bar' => 'bar'), + ), + __NAMESPACE__.'\GetTypehintedDummy', + 'any' + ); + $this->assertEquals('foo', $obj->getObject()->getFoo()); + $this->assertEquals('bar', $obj->getObject()->getBar()); + } + public function testPrivateSetter() { $obj = $this->normalizer->denormalize(array('foo' => 'foobar'), __NAMESPACE__.'\ObjectWithPrivateSetterDummy'); $this->assertEquals('bar', $obj->getFoo()); } + + public function testMaxDepth() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new GetSetMethodNormalizer($classMetadataFactory); + $serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($serializer); + + $level1 = new MaxDepthDummy(); + $level1->bar = 'level1'; + + $level2 = new MaxDepthDummy(); + $level2->bar = 'level2'; + $level1->child = $level2; + + $level3 = new MaxDepthDummy(); + $level3->bar = 'level3'; + $level2->child = $level3; + + $level4 = new MaxDepthDummy(); + $level4->bar = 'level4'; + $level3->child = $level4; + + $result = $serializer->normalize($level1, null, array(GetSetMethodNormalizer::ENABLE_MAX_DEPTH => true)); + + $expected = array( + 'bar' => 'level1', + 'child' => array( + 'bar' => 'level2', + 'child' => array( + 'bar' => 'level3', + 'child' => array( + 'child' => null, + ), + ), + ), + ); + + $this->assertEquals($expected, $result); + } } class GetSetDummy @@ -713,6 +774,59 @@ public function getBar_foo() } } +class GetTypehintedDummy +{ + protected $object; + + public function getObject() + { + return $this->object; + } + + public function setObject(GetTypehintDummy $object) + { + $this->object = $object; + } +} + +class GetTypehintDummy +{ + protected $foo; + protected $bar; + + /** + * @return mixed + */ + public function getFoo() + { + return $this->foo; + } + + /** + * @param mixed $foo + */ + public function setFoo($foo) + { + $this->foo = $foo; + } + + /** + * @return mixed + */ + public function getBar() + { + return $this->bar; + } + + /** + * @param mixed $bar + */ + public function setBar($bar) + { + $this->bar = $bar; + } +} + class ObjectConstructorArgsWithPrivateMutatorDummy { private $foo; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 2a16d69f906ba..a95acdd9f9bbf 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; +use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -445,6 +446,42 @@ public function testNormalizeStatic() { $this->assertEquals(array('foo' => 'K'), $this->normalizer->normalize(new ObjectWithStaticPropertiesAndMethods())); } + + public function testMaxDepth() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory); + $serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($serializer); + + $level1 = new MaxDepthDummy(); + $level1->foo = 'level1'; + + $level2 = new MaxDepthDummy(); + $level2->foo = 'level2'; + $level1->child = $level2; + + $level3 = new MaxDepthDummy(); + $level3->foo = 'level3'; + $level2->child = $level3; + + $result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); + + $expected = array( + 'bar' => null, + 'foo' => 'level1', + 'child' => array( + 'bar' => null, + 'foo' => 'level2', + 'child' => array( + 'bar' => null, + 'child' => null, + ), + ), + ); + + $this->assertEquals($expected, $result); + } } class ObjectDummy diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index 36814629288aa..b9d1af9aea700 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy; +use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\PropertySiblingHolder; @@ -371,6 +372,42 @@ public function testNoStaticPropertySupport() { $this->assertFalse($this->normalizer->supportsNormalization(new StaticPropertyDummy())); } + + public function testMaxDepth() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new PropertyNormalizer($classMetadataFactory); + $serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($serializer); + + $level1 = new MaxDepthDummy(); + $level1->foo = 'level1'; + + $level2 = new MaxDepthDummy(); + $level2->foo = 'level2'; + $level1->child = $level2; + + $level3 = new MaxDepthDummy(); + $level3->foo = 'level3'; + $level2->child = $level3; + + $result = $serializer->normalize($level1, null, array(PropertyNormalizer::ENABLE_MAX_DEPTH => true)); + + $expected = array( + 'foo' => 'level1', + 'child' => array( + 'foo' => 'level2', + 'child' => array( + 'child' => null, + 'bar' => null, + ), + 'bar' => null, + ), + 'bar' => null, + ); + + $this->assertEquals($expected, $result); + } } class PropertyDummy @@ -439,4 +476,3 @@ class StaticPropertyDummy { private static $property = 'value'; } - diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 4978ebace4cd5..c57e321abad23 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -19,9 +19,11 @@ "php": ">=5.5.9" }, "require-dev": { - "symfony/yaml": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0", - "symfony/property-access": "~2.8|~3.0", + "symfony/phpunit-bridge": "~2.7|~3.0.0", + "symfony/yaml": "~2.0,>=2.0.5|~3.0.0", + "symfony/config": "~2.2|~3.0.0", + "symfony/property-access": "~2.3|~3.0.0", + "symfony/property-info": "~2.8|~3.0", "doctrine/annotations": "~1.0", "doctrine/cache": "~1.0" }, @@ -30,7 +32,8 @@ "doctrine/cache": "For using the default cached annotation reader and metadata cache.", "symfony/yaml": "For using the default YAML mapping loader.", "symfony/config": "For using the XML mapping loader.", - "symfony/property-access": "For using the ObjectNormalizer." + "symfony/property-access": "For using the ObjectNormalizer.", + "symfony/property-info": "For using recursive denormalizations" }, "autoload": { "psr-4": { "Symfony\\Component\\Serializer\\": "" }, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy