diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 2a8018dc0b..9ff231e7a4 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -248,8 +248,18 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer return; } - $builtinType = $type->getBuiltinType(); - $class = $type->getClassName(); + if ($type->isCollection() && null !== ($collectionValueType = $type->getCollectionValueType()) && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { + $builtinType = Type::BUILTIN_TYPE_OBJECT; + $class = $collectionValueType->getClassName().'[]'; + + if (null !== $collectionKeyType = $type->getCollectionKeyType()) { + $context['key_type'] = $collectionKeyType; + } + } else { + $builtinType = $type->getBuiltinType(); + $class = $type->getClassName(); + } + $expectedTypes[Type::BUILTIN_TYPE_OBJECT === $builtinType && $class ? $class : $builtinType] = true; if (Type::BUILTIN_TYPE_OBJECT === $builtinType) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php index 921e312bd0..7d3d87c510 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\Serializer\Exception\BadMethodCallException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\SerializerAwareInterface; use Symfony\Component\Serializer\SerializerInterface; @@ -30,6 +31,8 @@ class ArrayDenormalizer implements DenormalizerInterface, SerializerAwareInterfa /** * {@inheritdoc} + * + * @throws UnexpectedValueException */ public function denormalize($data, $class, $format = null, array $context = array()) { @@ -46,12 +49,16 @@ class ArrayDenormalizer implements DenormalizerInterface, SerializerAwareInterfa $serializer = $this->serializer; $class = substr($class, 0, -2); - return array_map( - function ($data) use ($serializer, $class, $format, $context) { - return $serializer->denormalize($data, $class, $format, $context); - }, - $data - ); + $builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null; + foreach ($data as $key => $value) { + if (null !== $builtinType && !call_user_func('is_'.$builtinType, $key)) { + throw new UnexpectedValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, gettype($key))); + } + + $data[$key] = $serializer->denormalize($value, $class, $format, $context); + } + + return $data; } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 8a09e516cd..ca1c4e0f59 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -12,9 +12,12 @@ namespace Symfony\Component\Serializer\Tests\Normalizer; use Doctrine\Common\Annotations\AnnotationReader; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; @@ -525,13 +528,21 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase public function testDenomalizeRecursive() { - $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); - $serializer = new Serializer(array(new DateTimeNormalizer(), $normalizer)); + $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor())); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer)); + + $obj = $serializer->denormalize(array( + 'inner' => array('foo' => 'foo', 'bar' => 'bar'), + 'date' => '1988/01/21', + 'inners' => array(array('foo' => 1), array('foo' => 2)), + ), ObjectOuter::class); - $obj = $serializer->denormalize(array('inner' => array('foo' => 'foo', 'bar' => 'bar'), 'date' => '1988/01/21'), ObjectOuter::class); $this->assertEquals('foo', $obj->getInner()->foo); $this->assertEquals('bar', $obj->getInner()->bar); $this->assertEquals('1988-01-21', $obj->getDate()->format('Y-m-d')); + $this->assertEquals(1, $obj->getInners()[0]->foo); + $this->assertEquals(2, $obj->getInners()[1]->foo); } /** @@ -546,6 +557,19 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase $serializer->denormalize(array('date' => 'foo'), ObjectOuter::class); } + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage The type of the key "a" must be "int" ("string" given). + */ + public function testRejectInvalidKey() + { + $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor())); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer)); + + $serializer->denormalize(array('inners' => array('a' => array('foo' => 1))), ObjectOuter::class); + } + public function testExtractAttributesRespectsFormat() { $normalizer = new FormatAndContextAwareNormalizer(); @@ -740,6 +764,11 @@ class ObjectOuter private $inner; private $date; + /** + * @var ObjectInner[] + */ + private $inners; + public function getInner() { return $this->inner; @@ -759,6 +788,16 @@ class ObjectOuter { return $this->date; } + + public function setInners(array $inners) + { + $this->inners = $inners; + } + + public function getInners() + { + return $this->inners; + } } class ObjectInner diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 29d40aab02..01fc76bab5 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -24,9 +24,10 @@ "symfony/property-access": "~2.8|~3.0", "symfony/http-foundation": "~2.8|~3.0", "symfony/cache": "~3.1", - "symfony/property-info": "~2.8|~3.0", + "symfony/property-info": "~3.1", "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0" + "doctrine/cache": "~1.0", + "phpdocumentor/reflection-docblock": "~3.0" }, "conflict": { "symfony/property-access": ">=3.0,<3.0.4|>=2.8,<2.8.4"