f2d389a0df07efaf64e16c4bc4180196bbb2f001
[yaffs-website] / vendor / symfony / serializer / Tests / Normalizer / ObjectNormalizerTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Serializer\Tests\Normalizer;
13
14 use Doctrine\Common\Annotations\AnnotationReader;
15 use PHPUnit\Framework\TestCase;
16 use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
17 use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
18 use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
19 use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
20 use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
21 use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
22 use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
23 use Symfony\Component\Serializer\Serializer;
24 use Symfony\Component\Serializer\SerializerInterface;
25 use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
26 use Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy;
27 use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
28 use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
29 use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
30 use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
31 use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
32
33 /**
34  * @author Kévin Dunglas <dunglas@gmail.com>
35  */
36 class ObjectNormalizerTest extends TestCase
37 {
38     /**
39      * @var ObjectNormalizer
40      */
41     private $normalizer;
42     /**
43      * @var SerializerInterface
44      */
45     private $serializer;
46
47     protected function setUp()
48     {
49         $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerNormalizer')->getMock();
50         $this->normalizer = new ObjectNormalizer();
51         $this->normalizer->setSerializer($this->serializer);
52     }
53
54     public function testNormalize()
55     {
56         $obj = new ObjectDummy();
57         $object = new \stdClass();
58         $obj->setFoo('foo');
59         $obj->bar = 'bar';
60         $obj->setBaz(true);
61         $obj->setCamelCase('camelcase');
62         $obj->setObject($object);
63
64         $this->serializer
65             ->expects($this->once())
66             ->method('normalize')
67             ->with($object, 'any')
68             ->will($this->returnValue('string_object'))
69         ;
70
71         $this->assertEquals(
72             array(
73                 'foo' => 'foo',
74                 'bar' => 'bar',
75                 'baz' => true,
76                 'fooBar' => 'foobar',
77                 'camelCase' => 'camelcase',
78                 'object' => 'string_object',
79             ),
80             $this->normalizer->normalize($obj, 'any')
81         );
82     }
83
84     public function testDenormalize()
85     {
86         $obj = $this->normalizer->denormalize(
87             array('foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'),
88             __NAMESPACE__.'\ObjectDummy',
89             'any'
90         );
91         $this->assertEquals('foo', $obj->getFoo());
92         $this->assertEquals('bar', $obj->bar);
93         $this->assertTrue($obj->isBaz());
94     }
95
96     public function testDenormalizeWithObject()
97     {
98         $data = new \stdClass();
99         $data->foo = 'foo';
100         $data->bar = 'bar';
101         $data->fooBar = 'foobar';
102         $obj = $this->normalizer->denormalize($data, __NAMESPACE__.'\ObjectDummy', 'any');
103         $this->assertEquals('foo', $obj->getFoo());
104         $this->assertEquals('bar', $obj->bar);
105     }
106
107     public function testDenormalizeNull()
108     {
109         $this->assertEquals(new ObjectDummy(), $this->normalizer->denormalize(null, __NAMESPACE__.'\ObjectDummy'));
110     }
111
112     public function testConstructorDenormalize()
113     {
114         $obj = $this->normalizer->denormalize(
115             array('foo' => 'foo', 'bar' => 'bar', 'baz' => true, 'fooBar' => 'foobar'),
116             __NAMESPACE__.'\ObjectConstructorDummy', 'any');
117         $this->assertEquals('foo', $obj->getFoo());
118         $this->assertEquals('bar', $obj->bar);
119         $this->assertTrue($obj->isBaz());
120     }
121
122     public function testConstructorDenormalizeWithNullArgument()
123     {
124         $obj = $this->normalizer->denormalize(
125             array('foo' => 'foo', 'bar' => null, 'baz' => true),
126             __NAMESPACE__.'\ObjectConstructorDummy', 'any');
127         $this->assertEquals('foo', $obj->getFoo());
128         $this->assertNull($obj->bar);
129         $this->assertTrue($obj->isBaz());
130     }
131
132     public function testConstructorDenormalizeWithMissingOptionalArgument()
133     {
134         $obj = $this->normalizer->denormalize(
135             array('foo' => 'test', 'baz' => array(1, 2, 3)),
136             __NAMESPACE__.'\ObjectConstructorOptionalArgsDummy', 'any');
137         $this->assertEquals('test', $obj->getFoo());
138         $this->assertEquals(array(), $obj->bar);
139         $this->assertEquals(array(1, 2, 3), $obj->getBaz());
140     }
141
142     public function testConstructorDenormalizeWithOptionalDefaultArgument()
143     {
144         $obj = $this->normalizer->denormalize(
145             array('bar' => 'test'),
146             __NAMESPACE__.'\ObjectConstructorArgsWithDefaultValueDummy', 'any');
147         $this->assertEquals(array(), $obj->getFoo());
148         $this->assertEquals('test', $obj->getBar());
149     }
150
151     public function testConstructorWithObjectDenormalize()
152     {
153         $data = new \stdClass();
154         $data->foo = 'foo';
155         $data->bar = 'bar';
156         $data->baz = true;
157         $data->fooBar = 'foobar';
158         $obj = $this->normalizer->denormalize($data, __NAMESPACE__.'\ObjectConstructorDummy', 'any');
159         $this->assertEquals('foo', $obj->getFoo());
160         $this->assertEquals('bar', $obj->bar);
161     }
162
163     public function testConstructorWithObjectTypeHintDenormalize()
164     {
165         $data = array(
166             'id' => 10,
167             'inner' => array(
168                 'foo' => 'oof',
169                 'bar' => 'rab',
170             ),
171         );
172
173         $normalizer = new ObjectNormalizer();
174         $serializer = new Serializer(array($normalizer));
175         $normalizer->setSerializer($serializer);
176
177         $obj = $normalizer->denormalize($data, DummyWithConstructorObject::class);
178         $this->assertInstanceOf(DummyWithConstructorObject::class, $obj);
179         $this->assertEquals(10, $obj->getId());
180         $this->assertInstanceOf(ObjectInner::class, $obj->getInner());
181         $this->assertEquals('oof', $obj->getInner()->foo);
182         $this->assertEquals('rab', $obj->getInner()->bar);
183     }
184
185     /**
186      * @expectedException \Symfony\Component\Serializer\Exception\RuntimeException
187      * @expectedExceptionMessage Could not determine the class of the parameter "unknown".
188      */
189     public function testConstructorWithUnknownObjectTypeHintDenormalize()
190     {
191         $data = array(
192             'id' => 10,
193             'unknown' => array(
194                 'foo' => 'oof',
195                 'bar' => 'rab',
196             ),
197         );
198
199         $normalizer = new ObjectNormalizer();
200         $serializer = new Serializer(array($normalizer));
201         $normalizer->setSerializer($serializer);
202
203         $normalizer->denormalize($data, DummyWithConstructorInexistingObject::class);
204     }
205
206     public function testGroupsNormalize()
207     {
208         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
209         $this->normalizer = new ObjectNormalizer($classMetadataFactory);
210         $this->normalizer->setSerializer($this->serializer);
211
212         $obj = new GroupDummy();
213         $obj->setFoo('foo');
214         $obj->setBar('bar');
215         $obj->setFooBar('fooBar');
216         $obj->setSymfony('symfony');
217         $obj->setKevin('kevin');
218         $obj->setCoopTilleuls('coopTilleuls');
219
220         $this->assertEquals(array(
221             'bar' => 'bar',
222         ), $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('c'))));
223
224         $this->assertEquals(array(
225             'symfony' => 'symfony',
226             'foo' => 'foo',
227             'fooBar' => 'fooBar',
228             'bar' => 'bar',
229             'kevin' => 'kevin',
230             'coopTilleuls' => 'coopTilleuls',
231         ), $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('a', 'c'))));
232     }
233
234     public function testGroupsDenormalize()
235     {
236         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
237         $this->normalizer = new ObjectNormalizer($classMetadataFactory);
238         $this->normalizer->setSerializer($this->serializer);
239
240         $obj = new GroupDummy();
241         $obj->setFoo('foo');
242
243         $toNormalize = array('foo' => 'foo', 'bar' => 'bar');
244
245         $normalized = $this->normalizer->denormalize(
246             $toNormalize,
247             'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
248             null,
249             array(ObjectNormalizer::GROUPS => array('a'))
250         );
251         $this->assertEquals($obj, $normalized);
252
253         $obj->setBar('bar');
254
255         $normalized = $this->normalizer->denormalize(
256             $toNormalize,
257             'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy',
258             null,
259             array(ObjectNormalizer::GROUPS => array('a', 'b'))
260         );
261         $this->assertEquals($obj, $normalized);
262     }
263
264     public function testNormalizeNoPropertyInGroup()
265     {
266         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
267         $this->normalizer = new ObjectNormalizer($classMetadataFactory);
268         $this->normalizer->setSerializer($this->serializer);
269
270         $obj = new GroupDummy();
271         $obj->setFoo('foo');
272
273         $this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist'))));
274     }
275
276     public function testGroupsNormalizeWithNameConverter()
277     {
278         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
279         $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
280         $this->normalizer->setSerializer($this->serializer);
281
282         $obj = new GroupDummy();
283         $obj->setFooBar('@dunglas');
284         $obj->setSymfony('@coopTilleuls');
285         $obj->setCoopTilleuls('les-tilleuls.coop');
286
287         $this->assertEquals(
288             array(
289                 'bar' => null,
290                 'foo_bar' => '@dunglas',
291                 'symfony' => '@coopTilleuls',
292             ),
293             $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('name_converter')))
294         );
295     }
296
297     public function testGroupsDenormalizeWithNameConverter()
298     {
299         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
300         $this->normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
301         $this->normalizer->setSerializer($this->serializer);
302
303         $obj = new GroupDummy();
304         $obj->setFooBar('@dunglas');
305         $obj->setSymfony('@coopTilleuls');
306
307         $this->assertEquals(
308             $obj,
309             $this->normalizer->denormalize(array(
310                 'bar' => null,
311                 'foo_bar' => '@dunglas',
312                 'symfony' => '@coopTilleuls',
313                 'coop_tilleuls' => 'les-tilleuls.coop',
314             ), 'Symfony\Component\Serializer\Tests\Fixtures\GroupDummy', null, array(ObjectNormalizer::GROUPS => array('name_converter')))
315         );
316     }
317
318     /**
319      * @dataProvider provideCallbacks
320      */
321     public function testCallbacks($callbacks, $value, $result, $message)
322     {
323         $this->normalizer->setCallbacks($callbacks);
324
325         $obj = new ObjectConstructorDummy('', $value, true);
326
327         $this->assertEquals(
328             $result,
329             $this->normalizer->normalize($obj, 'any'),
330             $message
331         );
332     }
333
334     /**
335      * @expectedException \InvalidArgumentException
336      */
337     public function testUncallableCallbacks()
338     {
339         $this->normalizer->setCallbacks(array('bar' => null));
340
341         $obj = new ObjectConstructorDummy('baz', 'quux', true);
342
343         $this->normalizer->normalize($obj, 'any');
344     }
345
346     public function testIgnoredAttributes()
347     {
348         $this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'baz', 'camelCase', 'object'));
349
350         $obj = new ObjectDummy();
351         $obj->setFoo('foo');
352         $obj->bar = 'bar';
353         $obj->setBaz(true);
354
355         $this->assertEquals(
356             array('fooBar' => 'foobar'),
357             $this->normalizer->normalize($obj, 'any')
358         );
359     }
360
361     public function testIgnoredAttributesDenormalize()
362     {
363         $this->normalizer->setIgnoredAttributes(array('fooBar', 'bar', 'baz'));
364
365         $obj = new ObjectDummy();
366         $obj->setFoo('foo');
367
368         $this->assertEquals(
369             $obj,
370             $this->normalizer->denormalize(array('fooBar' => 'fooBar', 'foo' => 'foo', 'baz' => 'baz'), __NAMESPACE__.'\ObjectDummy')
371         );
372     }
373
374     public function provideCallbacks()
375     {
376         return array(
377             array(
378                 array(
379                     'bar' => function ($bar) {
380                         return 'baz';
381                     },
382                 ),
383                 'baz',
384                 array('foo' => '', 'bar' => 'baz', 'baz' => true),
385                 'Change a string',
386             ),
387             array(
388                 array(
389                     'bar' => function ($bar) {
390                         return;
391                     },
392                 ),
393                 'baz',
394                 array('foo' => '', 'bar' => null, 'baz' => true),
395                 'Null an item',
396             ),
397             array(
398                 array(
399                     'bar' => function ($bar) {
400                         return $bar->format('d-m-Y H:i:s');
401                     },
402                 ),
403                 new \DateTime('2011-09-10 06:30:00'),
404                 array('foo' => '', 'bar' => '10-09-2011 06:30:00', 'baz' => true),
405                 'Format a date',
406             ),
407             array(
408                 array(
409                     'bar' => function ($bars) {
410                         $foos = '';
411                         foreach ($bars as $bar) {
412                             $foos .= $bar->getFoo();
413                         }
414
415                         return $foos;
416                     },
417                 ),
418                 array(new ObjectConstructorDummy('baz', '', false), new ObjectConstructorDummy('quux', '', false)),
419                 array('foo' => '', 'bar' => 'bazquux', 'baz' => true),
420                 'Collect a property',
421             ),
422             array(
423                 array(
424                     'bar' => function ($bars) {
425                         return count($bars);
426                     },
427                 ),
428                 array(new ObjectConstructorDummy('baz', '', false), new ObjectConstructorDummy('quux', '', false)),
429                 array('foo' => '', 'bar' => 2, 'baz' => true),
430                 'Count a property',
431             ),
432         );
433     }
434
435     /**
436      * @expectedException \Symfony\Component\Serializer\Exception\LogicException
437      * @expectedExceptionMessage Cannot normalize attribute "object" because the injected serializer is not a normalizer
438      */
439     public function testUnableToNormalizeObjectAttribute()
440     {
441         $serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock();
442         $this->normalizer->setSerializer($serializer);
443
444         $obj = new ObjectDummy();
445         $object = new \stdClass();
446         $obj->setObject($object);
447
448         $this->normalizer->normalize($obj, 'any');
449     }
450
451     /**
452      * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
453      */
454     public function testUnableToNormalizeCircularReference()
455     {
456         $serializer = new Serializer(array($this->normalizer));
457         $this->normalizer->setSerializer($serializer);
458         $this->normalizer->setCircularReferenceLimit(2);
459
460         $obj = new CircularReferenceDummy();
461
462         $this->normalizer->normalize($obj);
463     }
464
465     public function testSiblingReference()
466     {
467         $serializer = new Serializer(array($this->normalizer));
468         $this->normalizer->setSerializer($serializer);
469
470         $siblingHolder = new SiblingHolder();
471
472         $expected = array(
473             'sibling0' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
474             'sibling1' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
475             'sibling2' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
476         );
477         $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder));
478     }
479
480     public function testCircularReferenceHandler()
481     {
482         $serializer = new Serializer(array($this->normalizer));
483         $this->normalizer->setSerializer($serializer);
484         $this->normalizer->setCircularReferenceHandler(function ($obj) {
485             return get_class($obj);
486         });
487
488         $obj = new CircularReferenceDummy();
489
490         $expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy');
491         $this->assertEquals($expected, $this->normalizer->normalize($obj));
492     }
493
494     public function testDenormalizeNonExistingAttribute()
495     {
496         $this->assertEquals(
497             new ObjectDummy(),
498             $this->normalizer->denormalize(array('non_existing' => true), __NAMESPACE__.'\ObjectDummy')
499         );
500     }
501
502     public function testNoTraversableSupport()
503     {
504         $this->assertFalse($this->normalizer->supportsNormalization(new \ArrayObject()));
505     }
506
507     public function testNormalizeStatic()
508     {
509         $this->assertEquals(array('foo' => 'K'), $this->normalizer->normalize(new ObjectWithStaticPropertiesAndMethods()));
510     }
511
512     public function testNormalizeUpperCaseAttributes()
513     {
514         $this->assertEquals(array('Foo' => 'Foo', 'Bar' => 'BarBar'), $this->normalizer->normalize(new ObjectWithUpperCaseAttributeNames()));
515     }
516
517     public function testNormalizeNotSerializableContext()
518     {
519         $objectDummy = new ObjectDummy();
520         $expected = array(
521             'foo' => null,
522             'baz' => null,
523             'fooBar' => '',
524             'camelCase' => null,
525             'object' => null,
526             'bar' => null,
527         );
528
529         $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, array('not_serializable' => function () {
530         })));
531     }
532
533     public function testMaxDepth()
534     {
535         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
536         $this->normalizer = new ObjectNormalizer($classMetadataFactory);
537         $serializer = new Serializer(array($this->normalizer));
538         $this->normalizer->setSerializer($serializer);
539
540         $level1 = new MaxDepthDummy();
541         $level1->foo = 'level1';
542
543         $level2 = new MaxDepthDummy();
544         $level2->foo = 'level2';
545         $level1->child = $level2;
546
547         $level3 = new MaxDepthDummy();
548         $level3->foo = 'level3';
549         $level2->child = $level3;
550
551         $result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
552
553         $expected = array(
554             'bar' => null,
555             'foo' => 'level1',
556             'child' => array(
557                     'bar' => null,
558                     'foo' => 'level2',
559                     'child' => array(
560                             'bar' => null,
561                             'child' => null,
562                         ),
563                 ),
564         );
565
566         $this->assertEquals($expected, $result);
567     }
568
569     /**
570      * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
571      */
572     public function testThrowUnexpectedValueException()
573     {
574         $this->normalizer->denormalize(array('foo' => 'bar'), ObjectTypeHinted::class);
575     }
576
577     public function testDenomalizeRecursive()
578     {
579         $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor()));
580         $normalizer = new ObjectNormalizer(null, null, null, $extractor);
581         $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer));
582
583         $obj = $serializer->denormalize(array(
584             'inner' => array('foo' => 'foo', 'bar' => 'bar'),
585             'date' => '1988/01/21',
586             'inners' => array(array('foo' => 1), array('foo' => 2)),
587         ), ObjectOuter::class);
588
589         $this->assertSame('foo', $obj->getInner()->foo);
590         $this->assertSame('bar', $obj->getInner()->bar);
591         $this->assertSame('1988-01-21', $obj->getDate()->format('Y-m-d'));
592         $this->assertSame(1, $obj->getInners()[0]->foo);
593         $this->assertSame(2, $obj->getInners()[1]->foo);
594     }
595
596     public function testAcceptJsonNumber()
597     {
598         $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor()));
599         $normalizer = new ObjectNormalizer(null, null, null, $extractor);
600         $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer));
601
602         $this->assertSame(10.0, $serializer->denormalize(array('number' => 10), JsonNumber::class, 'json')->number);
603         $this->assertSame(10.0, $serializer->denormalize(array('number' => 10), JsonNumber::class, 'jsonld')->number);
604     }
605
606     /**
607      * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
608      * @expectedExceptionMessage The type of the "date" attribute for class "Symfony\Component\Serializer\Tests\Normalizer\ObjectOuter" must be one of "DateTimeInterface" ("string" given).
609      */
610     public function testRejectInvalidType()
611     {
612         $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
613         $serializer = new Serializer(array($normalizer));
614
615         $serializer->denormalize(array('date' => 'foo'), ObjectOuter::class);
616     }
617
618     /**
619      * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
620      * @expectedExceptionMessage The type of the key "a" must be "int" ("string" given).
621      */
622     public function testRejectInvalidKey()
623     {
624         $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor()));
625         $normalizer = new ObjectNormalizer(null, null, null, $extractor);
626         $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer));
627
628         $serializer->denormalize(array('inners' => array('a' => array('foo' => 1))), ObjectOuter::class);
629     }
630
631     public function testDoNotRejectInvalidTypeOnDisableTypeEnforcementContextOption()
632     {
633         $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor()));
634         $normalizer = new ObjectNormalizer(null, null, null, $extractor);
635         $serializer = new Serializer(array($normalizer));
636         $context = array(ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true);
637
638         $this->assertSame('foo', $serializer->denormalize(array('number' => 'foo'), JsonNumber::class, null, $context)->number);
639     }
640
641     public function testExtractAttributesRespectsFormat()
642     {
643         $normalizer = new FormatAndContextAwareNormalizer();
644
645         $data = new ObjectDummy();
646         $data->setFoo('bar');
647         $data->bar = 'foo';
648
649         $this->assertSame(array('foo' => 'bar', 'bar' => 'foo'), $normalizer->normalize($data, 'foo_and_bar_included'));
650     }
651
652     public function testExtractAttributesRespectsContext()
653     {
654         $normalizer = new FormatAndContextAwareNormalizer();
655
656         $data = new ObjectDummy();
657         $data->setFoo('bar');
658         $data->bar = 'foo';
659
660         $this->assertSame(array('foo' => 'bar', 'bar' => 'foo'), $normalizer->normalize($data, null, array('include_foo_and_bar' => true)));
661     }
662
663     public function testAttributesContextNormalize()
664     {
665         $normalizer = new ObjectNormalizer();
666         $serializer = new Serializer(array($normalizer));
667
668         $objectInner = new ObjectInner();
669         $objectInner->foo = 'innerFoo';
670         $objectInner->bar = 'innerBar';
671
672         $objectDummy = new ObjectDummy();
673         $objectDummy->setFoo('foo');
674         $objectDummy->setBaz(true);
675         $objectDummy->setObject($objectInner);
676
677         $context = array('attributes' => array('foo', 'baz', 'object' => array('foo')));
678         $this->assertEquals(
679             array(
680                 'foo' => 'foo',
681                 'baz' => true,
682                 'object' => array('foo' => 'innerFoo'),
683             ),
684             $serializer->normalize($objectDummy, null, $context)
685         );
686
687         $context = array('attributes' => array('foo', 'baz', 'object'));
688         $this->assertEquals(
689             array(
690                 'foo' => 'foo',
691                 'baz' => true,
692                 'object' => array('foo' => 'innerFoo', 'bar' => 'innerBar'),
693             ),
694             $serializer->normalize($objectDummy, null, $context)
695         );
696     }
697
698     public function testAttributesContextDenormalize()
699     {
700         $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
701         $serializer = new Serializer(array($normalizer));
702
703         $objectInner = new ObjectInner();
704         $objectInner->foo = 'innerFoo';
705
706         $objectOuter = new ObjectOuter();
707         $objectOuter->bar = 'bar';
708         $objectOuter->setInner($objectInner);
709
710         $context = array('attributes' => array('bar', 'inner' => array('foo')));
711         $this->assertEquals($objectOuter, $serializer->denormalize(
712             array(
713                 'foo' => 'foo',
714                 'bar' => 'bar',
715                 'date' => '2017-02-03',
716                 'inner' => array('foo' => 'innerFoo', 'bar' => 'innerBar'),
717             ), ObjectOuter::class, null, $context));
718     }
719
720     public function testAttributesContextDenormalizeConstructor()
721     {
722         $normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());
723         $serializer = new Serializer(array($normalizer));
724
725         $objectInner = new ObjectInner();
726         $objectInner->bar = 'bar';
727
728         $obj = new DummyWithConstructorObjectAndDefaultValue('a', $objectInner);
729
730         $context = array('attributes' => array('inner' => array('bar')));
731         $this->assertEquals($obj, $serializer->denormalize(array(
732             'foo' => 'b',
733             'inner' => array('foo' => 'foo', 'bar' => 'bar'),
734         ), DummyWithConstructorObjectAndDefaultValue::class, null, $context));
735     }
736
737     public function testNormalizeSameObjectWithDifferentAttributes()
738     {
739         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
740         $this->normalizer = new ObjectNormalizer($classMetadataFactory);
741         $serializer = new Serializer(array($this->normalizer));
742         $this->normalizer->setSerializer($serializer);
743
744         $dummy = new ObjectOuter();
745         $dummy->foo = new ObjectInner();
746         $dummy->foo->foo = 'foo.foo';
747         $dummy->foo->bar = 'foo.bar';
748
749         $dummy->bar = new ObjectInner();
750         $dummy->bar->foo = 'bar.foo';
751         $dummy->bar->bar = 'bar.bar';
752
753         $this->assertEquals(array(
754             'foo' => array(
755                 'bar' => 'foo.bar',
756             ),
757             'bar' => array(
758                 'foo' => 'bar.foo',
759             ),
760         ), $this->normalizer->normalize($dummy, 'json', array(
761             'attributes' => array(
762                 'foo' => array('bar'),
763                 'bar' => array('foo'),
764             ),
765         )));
766     }
767 }
768
769 class ObjectDummy
770 {
771     protected $foo;
772     public $bar;
773     private $baz;
774     protected $camelCase;
775     protected $object;
776
777     public function getFoo()
778     {
779         return $this->foo;
780     }
781
782     public function setFoo($foo)
783     {
784         $this->foo = $foo;
785     }
786
787     public function isBaz()
788     {
789         return $this->baz;
790     }
791
792     public function setBaz($baz)
793     {
794         $this->baz = $baz;
795     }
796
797     public function getFooBar()
798     {
799         return $this->foo.$this->bar;
800     }
801
802     public function getCamelCase()
803     {
804         return $this->camelCase;
805     }
806
807     public function setCamelCase($camelCase)
808     {
809         $this->camelCase = $camelCase;
810     }
811
812     public function otherMethod()
813     {
814         throw new \RuntimeException('Dummy::otherMethod() should not be called');
815     }
816
817     public function setObject($object)
818     {
819         $this->object = $object;
820     }
821
822     public function getObject()
823     {
824         return $this->object;
825     }
826 }
827
828 class ObjectConstructorDummy
829 {
830     protected $foo;
831     public $bar;
832     private $baz;
833
834     public function __construct($foo, $bar, $baz)
835     {
836         $this->foo = $foo;
837         $this->bar = $bar;
838         $this->baz = $baz;
839     }
840
841     public function getFoo()
842     {
843         return $this->foo;
844     }
845
846     public function isBaz()
847     {
848         return $this->baz;
849     }
850
851     public function otherMethod()
852     {
853         throw new \RuntimeException('Dummy::otherMethod() should not be called');
854     }
855 }
856
857 abstract class ObjectSerializerNormalizer implements SerializerInterface, NormalizerInterface
858 {
859 }
860
861 class ObjectConstructorOptionalArgsDummy
862 {
863     protected $foo;
864     public $bar;
865     private $baz;
866
867     public function __construct($foo, $bar = array(), $baz = array())
868     {
869         $this->foo = $foo;
870         $this->bar = $bar;
871         $this->baz = $baz;
872     }
873
874     public function getFoo()
875     {
876         return $this->foo;
877     }
878
879     public function getBaz()
880     {
881         return $this->baz;
882     }
883
884     public function otherMethod()
885     {
886         throw new \RuntimeException('Dummy::otherMethod() should not be called');
887     }
888 }
889
890 class ObjectConstructorArgsWithDefaultValueDummy
891 {
892     protected $foo;
893     protected $bar;
894
895     public function __construct($foo = array(), $bar)
896     {
897         $this->foo = $foo;
898         $this->bar = $bar;
899     }
900
901     public function getFoo()
902     {
903         return $this->foo;
904     }
905
906     public function getBar()
907     {
908         return $this->bar;
909     }
910
911     public function otherMethod()
912     {
913         throw new \RuntimeException('Dummy::otherMethod() should not be called');
914     }
915 }
916
917 class ObjectWithStaticPropertiesAndMethods
918 {
919     public $foo = 'K';
920     public static $bar = 'A';
921
922     public static function getBaz()
923     {
924         return 'L';
925     }
926 }
927
928 class ObjectTypeHinted
929 {
930     public function setFoo(array $f)
931     {
932     }
933 }
934
935 class ObjectOuter
936 {
937     public $foo;
938     public $bar;
939     private $inner;
940     private $date;
941
942     /**
943      * @var ObjectInner[]
944      */
945     private $inners;
946
947     public function getInner()
948     {
949         return $this->inner;
950     }
951
952     public function setInner(ObjectInner $inner)
953     {
954         $this->inner = $inner;
955     }
956
957     public function setDate(\DateTimeInterface $date)
958     {
959         $this->date = $date;
960     }
961
962     public function getDate()
963     {
964         return $this->date;
965     }
966
967     public function setInners(array $inners)
968     {
969         $this->inners = $inners;
970     }
971
972     public function getInners()
973     {
974         return $this->inners;
975     }
976 }
977
978 class ObjectInner
979 {
980     public $foo;
981     public $bar;
982 }
983
984 class FormatAndContextAwareNormalizer extends ObjectNormalizer
985 {
986     protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array())
987     {
988         if (in_array($attribute, array('foo', 'bar')) && 'foo_and_bar_included' === $format) {
989             return true;
990         }
991
992         if (in_array($attribute, array('foo', 'bar')) && isset($context['include_foo_and_bar']) && true === $context['include_foo_and_bar']) {
993             return true;
994         }
995
996         return false;
997     }
998 }
999
1000 class DummyWithConstructorObject
1001 {
1002     private $id;
1003     private $inner;
1004
1005     public function __construct($id, ObjectInner $inner)
1006     {
1007         $this->id = $id;
1008         $this->inner = $inner;
1009     }
1010
1011     public function getId()
1012     {
1013         return $this->id;
1014     }
1015
1016     public function getInner()
1017     {
1018         return $this->inner;
1019     }
1020 }
1021
1022 class DummyWithConstructorInexistingObject
1023 {
1024     public function __construct($id, Unknown $unknown)
1025     {
1026     }
1027 }
1028
1029 class JsonNumber
1030 {
1031     /**
1032      * @var float
1033      */
1034     public $number;
1035 }
1036
1037 class DummyWithConstructorObjectAndDefaultValue
1038 {
1039     private $foo;
1040     private $inner;
1041
1042     public function __construct($foo = 'a', ObjectInner $inner)
1043     {
1044         $this->foo = $foo;
1045         $this->inner = $inner;
1046     }
1047
1048     public function getFoo()
1049     {
1050         return $this->foo;
1051     }
1052
1053     public function getInner()
1054     {
1055         return $this->inner;
1056     }
1057 }
1058
1059 class ObjectWithUpperCaseAttributeNames
1060 {
1061     private $Foo = 'Foo';
1062     public $Bar = 'BarBar';
1063
1064     public function getFoo()
1065     {
1066         return $this->Foo;
1067     }
1068 }