* * 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\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy; use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy; use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder; use Symfony\Component\Serializer\Tests\Normalizer\Features\AttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksObject; use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummy; use Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectToPopulateTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\SkipNullValuesTestTrait; use Symfony\Component\Serializer\Tests\Normalizer\Features\TypeEnforcementTestTrait; /** * @author Kévin Dunglas */ class ObjectNormalizerTest extends TestCase { use AttributesTestTrait; use CallbacksTestTrait; use CircularReferenceTestTrait; use ConstructorArgumentsTestTrait; use GroupsTestTrait; use IgnoredAttributesTestTrait; use MaxDepthTestTrait; use ObjectToPopulateTestTrait; use SkipNullValuesTestTrait; use TypeEnforcementTestTrait; /** * @var ObjectNormalizer */ private $normalizer; /** * @var SerializerInterface */ private $serializer; protected function setUp() { $this->createNormalizer(); } private function createNormalizer(array $defaultContext = [], ClassMetadataFactoryInterface $classMetadataFactory = null) { $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerNormalizer')->getMock(); $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); $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') ->willReturn('string_object') ; $this->assertEquals( [ '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( ['foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'], ObjectDummy::class, '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, ObjectDummy::class, 'any'); $this->assertEquals('foo', $obj->getFoo()); $this->assertEquals('bar', $obj->bar); } public function testDenormalizeNull() { $this->assertEquals(new ObjectDummy(), $this->normalizer->denormalize(null, ObjectDummy::class)); } public function testConstructorDenormalize() { $obj = $this->normalizer->denormalize( ['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( ['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( ['foo' => 'test', 'baz' => [1, 2, 3]], __NAMESPACE__.'\ObjectConstructorOptionalArgsDummy', 'any'); $this->assertEquals('test', $obj->getFoo()); $this->assertEquals([], $obj->bar); $this->assertEquals([1, 2, 3], $obj->getBaz()); } public function testConstructorDenormalizeWithOptionalDefaultArgument() { $obj = $this->normalizer->denormalize( ['bar' => 'test'], __NAMESPACE__.'\ObjectConstructorArgsWithDefaultValueDummy', 'any'); $this->assertEquals([], $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 testConstructorWithObjectTypeHintDenormalize() { $data = [ 'id' => 10, 'inner' => [ 'foo' => 'oof', 'bar' => 'rab', ], ]; $normalizer = new ObjectNormalizer(); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); $obj = $normalizer->denormalize($data, DummyWithConstructorObject::class); $this->assertInstanceOf(DummyWithConstructorObject::class, $obj); $this->assertEquals(10, $obj->getId()); $this->assertInstanceOf(ObjectInner::class, $obj->getInner()); $this->assertEquals('oof', $obj->getInner()->foo); $this->assertEquals('rab', $obj->getInner()->bar); } public function testConstructorWithUnconstructableNullableObjectTypeHintDenormalize() { $data = [ 'id' => 10, 'inner' => null, ]; $normalizer = new ObjectNormalizer(); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); $obj = $normalizer->denormalize($data, DummyWithNullableConstructorObject::class); $this->assertInstanceOf(DummyWithNullableConstructorObject::class, $obj); $this->assertEquals(10, $obj->getId()); $this->assertNull($obj->getInner()); } public function testConstructorWithUnknownObjectTypeHintDenormalize() { $this->expectException('Symfony\Component\Serializer\Exception\RuntimeException'); $this->expectExceptionMessage('Could not determine the class of the parameter "unknown".'); $data = [ 'id' => 10, 'unknown' => [ 'foo' => 'oof', 'bar' => 'rab', ], ]; $normalizer = new ObjectNormalizer(); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); $normalizer->denormalize($data, DummyWithConstructorInexistingObject::class); } // attributes protected function getNormalizerForAttributes(): ObjectNormalizer { $normalizer = new ObjectNormalizer(); // instantiate a serializer with the normalizer to handle normalizing recursive structures new Serializer([$normalizer]); return $normalizer; } protected function getDenormalizerForAttributes(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); new Serializer([$normalizer]); return $normalizer; } public function testAttributesContextDenormalizeConstructor() { $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor()); $serializer = new Serializer([$normalizer]); $objectInner = new ObjectInner(); $objectInner->bar = 'bar'; $obj = new DummyWithConstructorObjectAndDefaultValue('a', $objectInner); $context = ['attributes' => ['inner' => ['bar']]]; $this->assertEquals($obj, $serializer->denormalize([ 'foo' => 'b', 'inner' => ['foo' => 'foo', 'bar' => 'bar'], ], DummyWithConstructorObjectAndDefaultValue::class, null, $context)); } public function testNormalizeSameObjectWithDifferentAttributes() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $this->normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($serializer); $dummy = new ObjectOuter(); $dummy->foo = new ObjectInner(); $dummy->foo->foo = 'foo.foo'; $dummy->foo->bar = 'foo.bar'; $dummy->bar = new ObjectInner(); $dummy->bar->foo = 'bar.foo'; $dummy->bar->bar = 'bar.bar'; $this->assertEquals([ 'foo' => [ 'bar' => 'foo.bar', ], 'bar' => [ 'foo' => 'bar.foo', ], ], $this->normalizer->normalize($dummy, 'json', [ 'attributes' => [ 'foo' => ['bar'], 'bar' => ['foo'], ], ])); } // callbacks protected function getNormalizerForCallbacks(): ObjectNormalizer { return new ObjectNormalizer(); } /** * @dataProvider provideCallbacks */ public function testLegacyCallbacks($callbacks, $value, $result) { $this->normalizer->setCallbacks($callbacks); $obj = new CallbacksObject($value); $this->assertEquals( $result, $this->normalizer->normalize($obj, 'any') ); } /** * @dataProvider provideInvalidCallbacks */ public function testLegacyUncallableCallbacks($callbacks) { $this->expectException(\InvalidArgumentException::class); $this->normalizer->setCallbacks($callbacks); } // circular reference protected function getNormalizerForCircularReference(): ObjectNormalizer { $normalizer = new ObjectNormalizer(); new Serializer([$normalizer]); return $normalizer; } protected function getSelfReferencingModel() { return new CircularReferenceDummy(); } public function testLegacyUnableToNormalizeCircularReference() { $this->normalizer->setCircularReferenceLimit(2); $serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($serializer); $obj = new CircularReferenceDummy(); $this->expectException(CircularReferenceException::class); $this->normalizer->normalize($obj); } public function testSiblingReference() { $serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($serializer); $siblingHolder = new SiblingHolder(); $expected = [ 'sibling0' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 'sibling1' => ['coopTilleuls' => 'Les-Tilleuls.coop'], 'sibling2' => ['coopTilleuls' => 'Les-Tilleuls.coop'], ]; $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder)); } public function testLegacyCircularReferenceHandler() { new Serializer([$this->normalizer]); $obj = new CircularReferenceDummy(); $expected = ['me' => CircularReferenceDummy::class]; $this->normalizer->setCircularReferenceHandler(function ($obj, string $format, array $context) { $this->assertInstanceOf(CircularReferenceDummy::class, $obj); $this->assertSame('test', $format); $this->assertArrayHasKey('foo', $context); return \get_class($obj); }); $this->assertEquals($expected, $this->normalizer->normalize($obj, 'test', ['foo' => 'bar'])); } // constructor arguments protected function getDenormalizerForConstructArguments(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $denormalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); $serializer = new Serializer([$denormalizer]); $denormalizer->setSerializer($serializer); return $denormalizer; } // groups protected function getNormalizerForGroups(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory); // instantiate a serializer with the normalizer to handle normalizing recursive structures new Serializer([$normalizer]); return $normalizer; } protected function getDenormalizerForGroups(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); return new ObjectNormalizer($classMetadataFactory); } 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( [ 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', ], $this->normalizer->normalize($obj, null, [ObjectNormalizer::GROUPS => ['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([ 'bar' => null, 'foo_bar' => '@dunglas', 'symfony' => '@coopTilleuls', 'coop_tilleuls' => 'les-tilleuls.coop', ], 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', null, [ObjectNormalizer::GROUPS => ['name_converter']]) ); } // ignored attributes protected function getNormalizerForIgnoredAttributes(): ObjectNormalizer { $normalizer = new ObjectNormalizer(); // instantiate a serializer with the normalizer to handle normalizing recursive structures new Serializer([$normalizer]); return $normalizer; } protected function getDenormalizerForIgnoredAttributes(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor()); new Serializer([$normalizer]); return $normalizer; } public function testLegacyIgnoredAttributes() { $ignoredAttributes = ['foo', 'bar', 'baz', 'camelCase', 'object']; $this->normalizer->setIgnoredAttributes($ignoredAttributes); $obj = new ObjectDummy(); $obj->setFoo('foo'); $obj->bar = 'bar'; $obj->setBaz(true); $this->assertEquals( ['fooBar' => 'foobar'], $this->normalizer->normalize($obj, 'any') ); $ignoredAttributes = ['foo', 'baz', 'camelCase', 'object']; $this->normalizer->setIgnoredAttributes($ignoredAttributes); $this->assertEquals( [ 'fooBar' => 'foobar', 'bar' => 'bar', ], $this->normalizer->normalize($obj, 'any') ); } public function testLegacyIgnoredAttributesDenormalize() { $ignoredAttributes = ['fooBar', 'bar', 'baz']; $this->normalizer->setIgnoredAttributes($ignoredAttributes); $obj = new ObjectDummy(); $obj->setFoo('foo'); $this->assertEquals( $obj, $this->normalizer->denormalize(['fooBar' => 'fooBar', 'foo' => 'foo', 'baz' => 'baz'], ObjectDummy::class) ); } // max depth protected function getNormalizerForMaxDepth(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); return $normalizer; } public function testLegacyMaxDepth() { $level1 = new MaxDepthDummy(); $level1->foo = 'level1'; $level2 = new MaxDepthDummy(); $level2->foo = 'level2'; $level1->child = $level2; $level3 = new MaxDepthDummy(); $level3->foo = 'level3'; $level2->child = $level3; $this->createNormalizerWithMaxDepthHandler(null); $result = $this->serializer->normalize($level1, null, [ObjectNormalizer::ENABLE_MAX_DEPTH => true]); $expected = [ 'bar' => null, 'foo' => 'level1', 'child' => [ 'bar' => null, 'foo' => 'level2', 'child' => [ 'bar' => null, 'child' => null, ], ], ]; $this->assertEquals($expected, $result); $expected = [ 'bar' => null, 'foo' => 'level1', 'child' => [ 'bar' => null, 'foo' => 'level2', 'child' => [ 'bar' => null, 'child' => null, 'foo' => 'handler', ], ], ]; $this->createNormalizerWithMaxDepthHandler(function () { return 'handler'; }); $result = $this->serializer->normalize($level1, null, [ObjectNormalizer::ENABLE_MAX_DEPTH => true]); $this->assertEquals($expected, $result); $this->createNormalizerWithMaxDepthHandler(function ($object, $parentObject, $attributeName, $format, $context) { $this->assertSame('level3', $object); $this->assertInstanceOf(MaxDepthDummy::class, $parentObject); $this->assertSame('foo', $attributeName); $this->assertSame('test', $format); $this->assertArrayHasKey(ObjectNormalizer::ENABLE_MAX_DEPTH, $context); return 'handler'; }); $this->serializer->normalize($level1, 'test', [ObjectNormalizer::ENABLE_MAX_DEPTH => true]); } private function createNormalizerWithMaxDepthHandler(callable $handler = null) { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $this->createNormalizer([], $classMetadataFactory); if (null !== $handler) { $this->normalizer->setMaxDepthHandler($handler); } $this->serializer = new Serializer([$this->normalizer]); $this->normalizer->setSerializer($this->serializer); } // object to populate protected function getDenormalizerForObjectToPopulate(): ObjectNormalizer { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, new PhpDocExtractor()); new Serializer([$normalizer]); return $normalizer; } // skip null protected function getNormalizerForSkipNullValues(): ObjectNormalizer { return new ObjectNormalizer(); } // type enforcement protected function getDenormalizerForTypeEnforcement(): ObjectNormalizer { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); new Serializer([new ArrayDenormalizer(), $normalizer]); return $normalizer; } public function testUnableToNormalizeObjectAttribute() { $this->expectException('Symfony\Component\Serializer\Exception\LogicException'); $this->expectExceptionMessage('Cannot normalize attribute "object" because the injected serializer is not a normalizer'); $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'); } public function testDenormalizeNonExistingAttribute() { $this->assertEquals( new ObjectDummy(), $this->normalizer->denormalize(['non_existing' => true], ObjectDummy::class) ); } public function testNoTraversableSupport() { $this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject())); } public function testNormalizeStatic() { $this->assertEquals(['foo' => 'K'], $this->normalizer->normalize(new ObjectWithStaticPropertiesAndMethods())); } public function testNormalizeUpperCaseAttributes() { $this->assertEquals(['Foo' => 'Foo', 'Bar' => 'BarBar'], $this->normalizer->normalize(new ObjectWithUpperCaseAttributeNames())); } public function testNormalizeNotSerializableContext() { $objectDummy = new ObjectDummy(); $expected = [ 'foo' => null, 'baz' => null, 'fooBar' => '', 'camelCase' => null, 'object' => null, 'bar' => null, ]; $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, ['not_serializable' => function () { }])); } public function testThrowUnexpectedValueException() { $this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException'); $this->normalizer->denormalize(['foo' => 'bar'], ObjectTypeHinted::class); } public function testDenomalizeRecursive() { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); $obj = $serializer->denormalize([ 'inner' => ['foo' => 'foo', 'bar' => 'bar'], 'date' => '1988/01/21', 'inners' => [['foo' => 1], ['foo' => 2]], ], ObjectOuter::class); $this->assertSame('foo', $obj->getInner()->foo); $this->assertSame('bar', $obj->getInner()->bar); $this->assertSame('1988-01-21', $obj->getDate()->format('Y-m-d')); $this->assertSame(1, $obj->getInners()[0]->foo); $this->assertSame(2, $obj->getInners()[1]->foo); } public function testAcceptJsonNumber() { $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); $normalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); $this->assertSame(10.0, $serializer->denormalize(['number' => 10], JsonNumber::class, 'json')->number); $this->assertSame(10.0, $serializer->denormalize(['number' => 10], JsonNumber::class, 'jsonld')->number); } public function testExtractAttributesRespectsFormat() { $normalizer = new FormatAndContextAwareNormalizer(); $data = new ObjectDummy(); $data->setFoo('bar'); $data->bar = 'foo'; $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $normalizer->normalize($data, 'foo_and_bar_included')); } public function testExtractAttributesRespectsContext() { $normalizer = new FormatAndContextAwareNormalizer(); $data = new ObjectDummy(); $data->setFoo('bar'); $data->bar = 'foo'; $this->assertSame(['foo' => 'bar', 'bar' => 'foo'], $normalizer->normalize($data, null, ['include_foo_and_bar' => true])); } public function testAdvancedNameConverter() { $nameConverter = new class() implements AdvancedNameConverterInterface { public function normalize($propertyName, string $class = null, string $format = null, array $context = []) { return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } public function denormalize($propertyName, string $class = null, string $format = null, array $context = []) { return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } }; $normalizer = new ObjectNormalizer(null, $nameConverter); $this->assertArrayHasKey('foo-Symfony\Component\Serializer\Tests\Normalizer\Features\ObjectDummy-json-bar', $normalizer->normalize(new ObjectDummy(), 'json', ['foo' => 'bar'])); } public function testDefaultObjectClassResolver() { $normalizer = new ObjectNormalizer(); $obj = new ObjectDummy(); $obj->setFoo('foo'); $obj->bar = 'bar'; $obj->setBaz(true); $obj->setCamelCase('camelcase'); $obj->unwantedProperty = 'notwanted'; $this->assertEquals( [ 'foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar', 'camelCase' => 'camelcase', 'object' => null, ], $normalizer->normalize($obj, 'any') ); } public function testObjectClassResolver() { $classResolver = function ($object) { return ObjectDummy::class; }; $normalizer = new ObjectNormalizer(null, null, null, null, null, $classResolver); $obj = new ProxyObjectDummy(); $obj->setFoo('foo'); $obj->bar = 'bar'; $obj->setBaz(true); $obj->setCamelCase('camelcase'); $obj->unwantedProperty = 'notwanted'; $this->assertEquals( [ 'foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar', 'camelCase' => 'camelcase', 'object' => null, ], $normalizer->normalize($obj, 'any') ); } } class ProxyObjectDummy extends ObjectDummy { public $unwantedProperty; } 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 = [], $baz = []) { $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 = [], $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'; } } class ObjectTypeHinted { public function setFoo(array $f) { } } class ObjectOuter { public $foo; public $bar; /** * @var ObjectInner */ private $inner; private $date; /** * @var ObjectInner[] */ private $inners; public function getInner() { return $this->inner; } public function setInner(ObjectInner $inner) { $this->inner = $inner; } public function setDate(\DateTimeInterface $date) { $this->date = $date; } public function getDate() { return $this->date; } public function setInners(array $inners) { $this->inners = $inners; } public function getInners() { return $this->inners; } } class ObjectInner { public $foo; public $bar; } class FormatAndContextAwareNormalizer extends ObjectNormalizer { protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = []) { if (\in_array($attribute, ['foo', 'bar']) && 'foo_and_bar_included' === $format) { return true; } if (\in_array($attribute, ['foo', 'bar']) && isset($context['include_foo_and_bar']) && true === $context['include_foo_and_bar']) { return true; } return false; } } class DummyWithConstructorObject { private $id; private $inner; public function __construct($id, ObjectInner $inner) { $this->id = $id; $this->inner = $inner; } public function getId() { return $this->id; } public function getInner() { return $this->inner; } } class DummyWithConstructorInexistingObject { public function __construct($id, Unknown $unknown) { } } class JsonNumber { /** * @var float */ public $number; } class DummyWithConstructorObjectAndDefaultValue { private $foo; private $inner; public function __construct($foo = 'a', ObjectInner $inner) { $this->foo = $foo; $this->inner = $inner; } public function getFoo() { return $this->foo; } public function getInner() { return $this->inner; } } class ObjectWithUpperCaseAttributeNames { private $Foo = 'Foo'; public $Bar = 'BarBar'; public function getFoo() { return $this->Foo; } } class DummyWithNullableConstructorObject { private $id; private $inner; public function __construct($id, ?ObjectConstructorDummy $inner) { $this->id = $id; $this->inner = $inner; } public function getId() { return $this->id; } public function getInner() { return $this->inner; } }