--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 Doctrine\Common\Annotations\AnnotationReader;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+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\SiblingHolder;
+use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
+use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
+
+/**
+ * @author Kévin Dunglas <dunglas@gmail.com>
+ */
+class ObjectNormalizerTest extends TestCase
+{
+ /**
+ * @var ObjectNormalizer
+ */
+ private $normalizer;
+ /**
+ * @var SerializerInterface
+ */
+ private $serializer;
+
+ protected function setUp()
+ {
+ $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerNormalizer')->getMock();
+ $this->normalizer = new ObjectNormalizer();
+ $this->normalizer->setSerializer($this->serializer);
+ }
+
+ public function testNormalize()
+ {
+ $obj = new ObjectDummy();
+ $object = new \stdClass();
+ $obj->setFoo('foo');
+ $obj->bar = 'bar';
+ $obj->setBaz(true);
+ $obj->setCamelCase('camelcase');
+ $obj->setObject($object);
+
+ $this->serializer
+ ->expects($this->once())
+ ->method('normalize')
+ ->with($object, 'any')
+ ->will($this->returnValue('string_object'))
+ ;
+
+ $this->assertEquals(
+ array(
+ 'foo' => 'foo',
+ 'bar' => 'bar',
+ 'baz' => true,
+ 'fooBar' => 'foobar',
+ 'camelCase' => 'camelcase',
+ 'object' => 'string_object',
+ ),
+ $this->normalizer->normalize($obj, 'any')
+ );
+ }
+
+ public function testDenormalize()
+ {
+ $obj = $this->normalizer->denormalize(
+ array('foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'),
+ __NAMESPACE__.'\ObjectDummy',
+ 'any'
+ );
+ $this->assertEquals('foo', $obj->getFoo());
+ $this->assertEquals('bar', $obj->bar);
+ $this->assertTrue($obj->isBaz());
+ }
+
+ public function testDenormalizeWithObject()
+ {
+ $data = new \stdClass();
+ $data->foo = 'foo';
+ $data->bar = 'bar';
+ $data->fooBar = 'foobar';
+ $obj = $this->normalizer->denormalize($data, __NAMESPACE__.'\ObjectDummy', 'any');
+ $this->assertEquals('foo', $obj->getFoo());
+ $this->assertEquals('bar', $obj->bar);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyDenormalizeOnCamelCaseFormat()
+ {
+ $this->normalizer->setCamelizedAttributes(array('camel_case'));
+ $obj = $this->normalizer->denormalize(
+ array('camel_case' => 'camelCase'),
+ __NAMESPACE__.'\ObjectDummy'
+ );
+ $this->assertEquals('camelCase', $obj->getCamelCase());
+ }
+
+ public function testNameConverterSupport()
+ {
+ $this->normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());
+ $obj = $this->normalizer->denormalize(
+ array('camel_case' => 'camelCase'),
+ __NAMESPACE__.'\ObjectDummy'
+ );
+ $this->assertEquals('camelCase', $obj->getCamelCase());
+ }
+
+ public function testDenormalizeNull()
+ {
+ $this->assertEquals(new ObjectDummy(), $this->normalizer->denormalize(null, __NAMESPACE__.'\ObjectDummy'));
+ }
+
+ public function testConstructorDenormalize()
+ {
+ $obj = $this->normalizer->denormalize(
+ array('foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'),
+ __NAMESPACE__.'\ObjectConstructorDummy', 'any');
+ $this->assertEquals('foo', $obj->getFoo());
+ $this->assertEquals('bar', $obj->bar);
+ $this->assertTrue($obj->isBaz());
+ }
+
+ public function testConstructorDenormalizeWithNullArgument()
+ {
+ $obj = $this->normalizer->denormalize(
+ array('foo' => 'foo', 'bar' => null, 'baz' => true),
+ __NAMESPACE__.'\ObjectConstructorDummy', 'any');
+ $this->assertEquals('foo', $obj->getFoo());
+ $this->assertNull($obj->bar);
+ $this->assertTrue($obj->isBaz());
+ }
+
+ public function testConstructorDenormalizeWithMissingOptionalArgument()
+ {
+ $obj = $this->normalizer->denormalize(
+ array('foo' => 'test', 'baz' => array(1, 2, 3)),
+ __NAMESPACE__.'\ObjectConstructorOptionalArgsDummy', 'any');
+ $this->assertEquals('test', $obj->getFoo());
+ $this->assertEquals(array(), $obj->bar);
+ $this->assertEquals(array(1, 2, 3), $obj->getBaz());
+ }
+
+ /**
+ * @see https://bugs.php.net/62715
+ *
+ * @requires PHP 5.3.17
+ */
+ public function testConstructorDenormalizeWithOptionalDefaultArgument()
+ {
+ $obj = $this->normalizer->denormalize(
+ array('bar' => 'test'),
+ __NAMESPACE__.'\ObjectConstructorArgsWithDefaultValueDummy', 'any');
+ $this->assertEquals(array(), $obj->getFoo());
+ $this->assertEquals('test', $obj->getBar());
+ }
+
+ public function testConstructorWithObjectDenormalize()
+ {
+ $data = new \stdClass();
+ $data->foo = 'foo';
+ $data->bar = 'bar';
+ $data->baz = true;
+ $data->fooBar = 'foobar';
+ $obj = $this->normalizer->denormalize($data, __NAMESPACE__.'\ObjectConstructorDummy', 'any');
+ $this->assertEquals('foo', $obj->getFoo());
+ $this->assertEquals('bar', $obj->bar);
+ }
+
+ public function testGroupsNormalize()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory);
+ $this->normalizer->setSerializer($this->serializer);
+
+ $obj = new GroupDummy();
+ $obj->setFoo('foo');
+ $obj->setBar('bar');
+ $obj->setFooBar('fooBar');
+ $obj->setSymfony('symfony');
+ $obj->setKevin('kevin');
+ $obj->setCoopTilleuls('coopTilleuls');
+
+ $this->assertEquals(array(
+ 'bar' => 'bar',
+ ), $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('c'))));
+
+ $this->assertEquals(array(
+ 'symfony' => 'symfony',
+ 'foo' => 'foo',
+ 'fooBar' => 'fooBar',
+ 'bar' => 'bar',
+ 'kevin' => 'kevin',
+ 'coopTilleuls' => 'coopTilleuls',
+ ), $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('a', 'c'))));
+ }
+
+ public function testGroupsDenormalize()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory);
+ $this->normalizer->setSerializer($this->serializer);
+
+ $obj = new GroupDummy();
+ $obj->setFoo('foo');
+
+ $toNormalize = array('foo' => 'foo', 'bar' => 'bar');
+
+ $normalized = $this->normalizer->denormalize(
+ $toNormalize,
+ 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
+ null,
+ array(ObjectNormalizer::GROUPS => array('a'))
+ );
+ $this->assertEquals($obj, $normalized);
+
+ $obj->setBar('bar');
+
+ $normalized = $this->normalizer->denormalize(
+ $toNormalize,
+ 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
+ null,
+ array(ObjectNormalizer::GROUPS => array('a', 'b'))
+ );
+ $this->assertEquals($obj, $normalized);
+ }
+
+ public function testNormalizeNoPropertyInGroup()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory);
+ $this->normalizer->setSerializer($this->serializer);
+
+ $obj = new GroupDummy();
+ $obj->setFoo('foo');
+
+ $this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist'))));
+ }
+
+ public function testGroupsNormalizeWithNameConverter()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
+ $this->normalizer->setSerializer($this->serializer);
+
+ $obj = new GroupDummy();
+ $obj->setFooBar('@dunglas');
+ $obj->setSymfony('@coopTilleuls');
+ $obj->setCoopTilleuls('les-tilleuls.coop');
+
+ $this->assertEquals(
+ array(
+ 'bar' => null,
+ 'foo_bar' => '@dunglas',
+ 'symfony' => '@coopTilleuls',
+ ),
+ $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('name_converter')))
+ );
+ }
+
+ public function testGroupsDenormalizeWithNameConverter()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
+ $this->normalizer->setSerializer($this->serializer);
+
+ $obj = new GroupDummy();
+ $obj->setFooBar('@dunglas');
+ $obj->setSymfony('@coopTilleuls');
+
+ $this->assertEquals(
+ $obj,
+ $this->normalizer->denormalize(array(
+ 'bar' => null,
+ 'foo_bar' => '@dunglas',
+ 'symfony' => '@coopTilleuls',
+ 'coop_tilleuls' => 'les-tilleuls.coop',
+ ), 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', null, array(ObjectNormalizer::GROUPS => array('name_converter')))
+ );
+ }
+
+ /**
+ * @dataProvider provideCallbacks
+ */
+ public function testCallbacks($callbacks, $value, $result, $message)
+ {
+ $this->normalizer->setCallbacks($callbacks);
+
+ $obj = new ObjectConstructorDummy('', $value, true);
+
+ $this->assertEquals(
+ $result,
+ $this->normalizer->normalize($obj, 'any'),
+ $message
+ );
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testUncallableCallbacks()
+ {
+ $this->normalizer->setCallbacks(array('bar' => null));
+
+ $obj = new ObjectConstructorDummy('baz', 'quux', true);
+
+ $this->normalizer->normalize($obj, 'any');
+ }
+
+ public function testIgnoredAttributes()
+ {
+ $this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'baz', 'camelCase', 'object'));
+
+ $obj = new ObjectDummy();
+ $obj->setFoo('foo');
+ $obj->bar = 'bar';
+ $obj->setBaz(true);
+
+ $this->assertEquals(
+ array('fooBar' => 'foobar'),
+ $this->normalizer->normalize($obj, 'any')
+ );
+ }
+
+ public function testIgnoredAttributesDenormalize()
+ {
+ $this->normalizer->setIgnoredAttributes(array('fooBar', 'bar', 'baz'));
+
+ $obj = new ObjectDummy();
+ $obj->setFoo('foo');
+
+ $this->assertEquals(
+ $obj,
+ $this->normalizer->denormalize(array('fooBar' => 'fooBar', 'foo' => 'foo', 'baz' => 'baz'), __NAMESPACE__.'\ObjectDummy')
+ );
+ }
+
+ public function provideCallbacks()
+ {
+ return array(
+ array(
+ array(
+ 'bar' => function ($bar) {
+ return 'baz';
+ },
+ ),
+ 'baz',
+ array('foo' => '', 'bar' => 'baz', 'baz' => true),
+ 'Change a string',
+ ),
+ array(
+ array(
+ 'bar' => function ($bar) {
+ return;
+ },
+ ),
+ 'baz',
+ array('foo' => '', 'bar' => null, 'baz' => true),
+ 'Null an item',
+ ),
+ array(
+ array(
+ 'bar' => function ($bar) {
+ return $bar->format('d-m-Y H:i:s');
+ },
+ ),
+ new \DateTime('2011-09-10 06:30:00'),
+ array('foo' => '', 'bar' => '10-09-2011 06:30:00', 'baz' => true),
+ 'Format a date',
+ ),
+ array(
+ array(
+ 'bar' => function ($bars) {
+ $foos = '';
+ foreach ($bars as $bar) {
+ $foos .= $bar->getFoo();
+ }
+
+ return $foos;
+ },
+ ),
+ array(new ObjectConstructorDummy('baz', '', false), new ObjectConstructorDummy('quux', '', false)),
+ array('foo' => '', 'bar' => 'bazquux', 'baz' => true),
+ 'Collect a property',
+ ),
+ array(
+ array(
+ 'bar' => function ($bars) {
+ return count($bars);
+ },
+ ),
+ array(new ObjectConstructorDummy('baz', '', false), new ObjectConstructorDummy('quux', '', false)),
+ array('foo' => '', 'bar' => 2, 'baz' => true),
+ 'Count a property',
+ ),
+ );
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Serializer\Exception\LogicException
+ * @expectedExceptionMessage Cannot normalize attribute "object" because injected serializer is not a normalizer
+ */
+ public function testUnableToNormalizeObjectAttribute()
+ {
+ $serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock();
+ $this->normalizer->setSerializer($serializer);
+
+ $obj = new ObjectDummy();
+ $object = new \stdClass();
+ $obj->setObject($object);
+
+ $this->normalizer->normalize($obj, 'any');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
+ */
+ public function testUnableToNormalizeCircularReference()
+ {
+ $serializer = new Serializer(array($this->normalizer));
+ $this->normalizer->setSerializer($serializer);
+ $this->normalizer->setCircularReferenceLimit(2);
+
+ $obj = new CircularReferenceDummy();
+
+ $this->normalizer->normalize($obj);
+ }
+
+ public function testSiblingReference()
+ {
+ $serializer = new Serializer(array($this->normalizer));
+ $this->normalizer->setSerializer($serializer);
+
+ $siblingHolder = new SiblingHolder();
+
+ $expected = array(
+ 'sibling0' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
+ 'sibling1' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
+ 'sibling2' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
+ );
+ $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder));
+ }
+
+ public function testCircularReferenceHandler()
+ {
+ $serializer = new Serializer(array($this->normalizer));
+ $this->normalizer->setSerializer($serializer);
+ $this->normalizer->setCircularReferenceHandler(function ($obj) {
+ return get_class($obj);
+ });
+
+ $obj = new CircularReferenceDummy();
+
+ $expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy');
+ $this->assertEquals($expected, $this->normalizer->normalize($obj));
+ }
+
+ public function testDenormalizeNonExistingAttribute()
+ {
+ $this->assertEquals(
+ new ObjectDummy(),
+ $this->normalizer->denormalize(array('non_existing' => true), __NAMESPACE__.'\ObjectDummy')
+ );
+ }
+
+ public function testNoTraversableSupport()
+ {
+ $this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
+ }
+
+ public function testNormalizeStatic()
+ {
+ $this->assertEquals(array('foo' => 'K'), $this->normalizer->normalize(new ObjectWithStaticPropertiesAndMethods()));
+ }
+
+ public function testNormalizeNotSerializableContext()
+ {
+ $objectDummy = new ObjectDummy();
+ $expected = array(
+ 'foo' => null,
+ 'baz' => null,
+ 'fooBar' => '',
+ 'camelCase' => null,
+ 'object' => null,
+ 'bar' => null,
+ );
+
+ $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, array('not_serializable' => function () {})));
+ }
+}
+
+class ObjectDummy
+{
+ protected $foo;
+ public $bar;
+ private $baz;
+ protected $camelCase;
+ protected $object;
+
+ public function getFoo()
+ {
+ return $this->foo;
+ }
+
+ public function setFoo($foo)
+ {
+ $this->foo = $foo;
+ }
+
+ public function isBaz()
+ {
+ return $this->baz;
+ }
+
+ public function setBaz($baz)
+ {
+ $this->baz = $baz;
+ }
+
+ public function getFooBar()
+ {
+ return $this->foo.$this->bar;
+ }
+
+ public function getCamelCase()
+ {
+ return $this->camelCase;
+ }
+
+ public function setCamelCase($camelCase)
+ {
+ $this->camelCase = $camelCase;
+ }
+
+ public function otherMethod()
+ {
+ throw new \RuntimeException('Dummy::otherMethod() should not be called');
+ }
+
+ public function setObject($object)
+ {
+ $this->object = $object;
+ }
+
+ public function getObject()
+ {
+ return $this->object;
+ }
+}
+
+class ObjectConstructorDummy
+{
+ protected $foo;
+ public $bar;
+ private $baz;
+
+ public function __construct($foo, $bar, $baz)
+ {
+ $this->foo = $foo;
+ $this->bar = $bar;
+ $this->baz = $baz;
+ }
+
+ public function getFoo()
+ {
+ return $this->foo;
+ }
+
+ public function isBaz()
+ {
+ return $this->baz;
+ }
+
+ public function otherMethod()
+ {
+ throw new \RuntimeException('Dummy::otherMethod() should not be called');
+ }
+}
+
+abstract class ObjectSerializerNormalizer implements SerializerInterface, NormalizerInterface
+{
+}
+
+class ObjectConstructorOptionalArgsDummy
+{
+ protected $foo;
+ public $bar;
+ private $baz;
+
+ public function __construct($foo, $bar = array(), $baz = array())
+ {
+ $this->foo = $foo;
+ $this->bar = $bar;
+ $this->baz = $baz;
+ }
+
+ public function getFoo()
+ {
+ return $this->foo;
+ }
+
+ public function getBaz()
+ {
+ return $this->baz;
+ }
+
+ public function otherMethod()
+ {
+ throw new \RuntimeException('Dummy::otherMethod() should not be called');
+ }
+}
+
+class ObjectConstructorArgsWithDefaultValueDummy
+{
+ protected $foo;
+ protected $bar;
+
+ public function __construct($foo = array(), $bar)
+ {
+ $this->foo = $foo;
+ $this->bar = $bar;
+ }
+
+ public function getFoo()
+ {
+ return $this->foo;
+ }
+
+ public function getBar()
+ {
+ return $this->bar;
+ }
+
+ public function otherMethod()
+ {
+ throw new \RuntimeException('Dummy::otherMethod() should not be called');
+ }
+}
+
+class ObjectWithStaticPropertiesAndMethods
+{
+ public $foo = 'K';
+ public static $bar = 'A';
+
+ public static function getBaz()
+ {
+ return 'L';
+ }
+}