From 0573f28d9ba7d3ea934ec70a268f9010998e29fd Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 13 Apr 2015 23:07:23 +0200 Subject: [PATCH] Support for array denormalization. --- .../Exception/BadMethodCallException.php | 16 +++ .../Normalizer/ArrayDenormalizer.php | 77 +++++++++++ .../Normalizer/CustomNormalizer.php | 4 + .../Normalizer/GetSetMethodNormalizer.php | 2 +- .../Normalizer/PropertyNormalizer.php | 2 +- .../Normalizer/ArrayDenormalizerTest.php | 121 ++++++++++++++++++ .../Serializer/Tests/SerializerTest.php | 48 +++++++ 7 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Serializer/Exception/BadMethodCallException.php create mode 100644 src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php create mode 100644 src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php diff --git a/src/Symfony/Component/Serializer/Exception/BadMethodCallException.php b/src/Symfony/Component/Serializer/Exception/BadMethodCallException.php new file mode 100644 index 0000000000..b2f3d61a8c --- /dev/null +++ b/src/Symfony/Component/Serializer/Exception/BadMethodCallException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Exception; + +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php new file mode 100644 index 0000000000..6e746f29fe --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Serializer\Exception\BadMethodCallException; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Denormalizes arrays of objects. + * + * @author Alexander M. Turek + */ +class ArrayDenormalizer implements DenormalizerInterface, SerializerAwareInterface +{ + /** + * @var SerializerInterface|DenormalizerInterface + */ + private $serializer; + + /** + * {@inheritdoc} + */ + public function denormalize($data, $class, $format = null, array $context = array()) + { + if ($this->serializer === null) { + throw new BadMethodCallException('Please set a serializer before calling denormalize()!'); + } + if (!is_array($data)) { + throw new InvalidArgumentException('Data expected to be an array, '.gettype($data).' given.'); + } + if (substr($class, -2) !== '[]') { + throw new InvalidArgumentException('Unsupported class: '.$class); + } + + $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 + ); + } + + /** + * {@inheritdoc} + */ + public function supportsDenormalization($data, $type, $format = null) + { + return substr($type, -2) === '[]' + && $this->serializer->supportsDenormalization($data, substr($type, 0, -2), $format); + } + + /** + * {@inheritdoc} + */ + public function setSerializer(SerializerInterface $serializer) + { + if (!$serializer instanceof DenormalizerInterface) { + throw new InvalidArgumentException('Expected a serializer that also implements DenormalizerInterface.'); + } + + $this->serializer = $serializer; + } +} diff --git a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php index 83c3c14384..7df266a7a9 100644 --- a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php @@ -59,6 +59,10 @@ class CustomNormalizer extends SerializerAwareNormalizer implements NormalizerIn */ public function supportsDenormalization($data, $type, $format = null) { + if (!class_exists($type)) { + return false; + } + $class = new \ReflectionClass($type); return $class->isSubclassOf('Symfony\Component\Serializer\Normalizer\DenormalizableInterface'); diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index de4c3ece1d..9e9eda150f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -135,7 +135,7 @@ class GetSetMethodNormalizer extends AbstractNormalizer */ public function supportsDenormalization($data, $type, $format = null) { - return $this->supports($type); + return class_exists($type) && $this->supports($type); } /** diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index 4253d3dd0e..d022aec7a1 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -135,7 +135,7 @@ class PropertyNormalizer extends AbstractNormalizer */ public function supportsDenormalization($data, $type, $format = null) { - return $this->supports($type); + return class_exists($type) && $this->supports($type); } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php new file mode 100644 index 0000000000..23014f300e --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ArrayDenormalizerTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\SerializerInterface; + +class ArrayDenormalizerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ArrayDenormalizer + */ + private $denormalizer; + + /** + * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $serializer; + + protected function setUp() + { + $this->serializer = $this->getMock('Symfony\Component\Serializer\Serializer'); + $this->denormalizer = new ArrayDenormalizer(); + $this->denormalizer->setSerializer($this->serializer); + } + + public function testDenormalize() + { + $this->serializer->expects($this->at(0)) + ->method('denormalize') + ->with(array('foo' => 'one', 'bar' => 'two')) + ->will($this->returnValue(new ArrayDummy('one', 'two'))); + + $this->serializer->expects($this->at(1)) + ->method('denormalize') + ->with(array('foo' => 'three', 'bar' => 'four')) + ->will($this->returnValue(new ArrayDummy('three', 'four'))); + + $result = $this->denormalizer->denormalize( + array( + array('foo' => 'one', 'bar' => 'two'), + array('foo' => 'three', 'bar' => 'four'), + ), + __NAMESPACE__.'\ArrayDummy[]' + ); + + $this->assertEquals( + array( + new ArrayDummy('one', 'two'), + new ArrayDummy('three', 'four'), + ), + $result + ); + } + + public function testSupportsValidArray() + { + $this->serializer->expects($this->once()) + ->method('supportsDenormalization') + ->with($this->anything(), __NAMESPACE__.'\ArrayDummy', $this->anything()) + ->will($this->returnValue(true)); + + $this->assertTrue( + $this->denormalizer->supportsDenormalization( + array( + array('foo' => 'one', 'bar' => 'two'), + array('foo' => 'three', 'bar' => 'four'), + ), + __NAMESPACE__.'\ArrayDummy[]' + ) + ); + } + + public function testSupportsInvalidArray() + { + $this->serializer->expects($this->any()) + ->method('supportsDenormalization') + ->will($this->returnValue(false)); + + $this->assertFalse( + $this->denormalizer->supportsDenormalization( + array( + array('foo' => 'one', 'bar' => 'two'), + array('foo' => 'three', 'bar' => 'four'), + ), + __NAMESPACE__.'\InvalidClass[]' + ) + ); + } + + public function testSupportsNoArray() + { + $this->assertFalse( + $this->denormalizer->supportsDenormalization( + array('foo' => 'one', 'bar' => 'two'), + __NAMESPACE__.'\ArrayDummy' + ) + ); + } +} + +class ArrayDummy +{ + public $foo; + public $bar; + + public function __construct($foo, $bar) + { + $this->foo = $foo; + $this->bar = $bar; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 68f70fcfe5..43dfe29e00 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Serializer\Tests; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; @@ -220,6 +223,51 @@ class SerializerTest extends \PHPUnit_Framework_TestCase $result = $this->serializer->decode(json_encode($data), 'json'); $this->assertEquals($data, $result); } + + public function testSupportsArrayDeserialization() + { + $serializer = new Serializer( + array( + new GetSetMethodNormalizer(), + new PropertyNormalizer(), + new ObjectNormalizer(), + new CustomNormalizer(), + new ArrayDenormalizer(), + ), + array( + 'json' => new JsonEncoder(), + ) + ); + + $this->assertTrue( + $serializer->supportsDenormalization(array(), __NAMESPACE__.'\Model[]', 'json') + ); + } + + public function testDeserializeArray() + { + $jsonData = '[{"title":"foo","numbers":[5,3]},{"title":"bar","numbers":[2,8]}]'; + + $expectedData = array( + Model::fromArray(array('title' => 'foo', 'numbers' => array(5, 3))), + Model::fromArray(array('title' => 'bar', 'numbers' => array(2, 8))), + ); + + $serializer = new Serializer( + array( + new GetSetMethodNormalizer(), + new ArrayDenormalizer(), + ), + array( + 'json' => new JsonEncoder(), + ) + ); + + $this->assertEquals( + $expectedData, + $serializer->deserialize($jsonData, __NAMESPACE__.'\Model[]', 'json') + ); + } } class Model