1<?php
2
3declare(strict_types=1);
4
5namespace JMS\Serializer\Tests\Serializer;
6
7use Doctrine\Common\Collections\ArrayCollection;
8use JMS\Serializer\Construction\UnserializeObjectConstructor;
9use JMS\Serializer\Context;
10use JMS\Serializer\DeserializationContext;
11use JMS\Serializer\EventDispatcher\EventDispatcher;
12use JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber;
13use JMS\Serializer\Exclusion\DepthExclusionStrategy;
14use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
15use JMS\Serializer\Expression\ExpressionEvaluator;
16use JMS\Serializer\GraphNavigatorInterface;
17use JMS\Serializer\Handler\ArrayCollectionHandler;
18use JMS\Serializer\Handler\ConstraintViolationHandler;
19use JMS\Serializer\Handler\DateHandler;
20use JMS\Serializer\Handler\FormErrorHandler;
21use JMS\Serializer\Handler\HandlerRegistry;
22use JMS\Serializer\Handler\HandlerRegistryInterface;
23use JMS\Serializer\Handler\IteratorHandler;
24use JMS\Serializer\Handler\StdClassHandler;
25use JMS\Serializer\SerializationContext;
26use JMS\Serializer\Serializer;
27use JMS\Serializer\SerializerBuilder;
28use JMS\Serializer\Tests\Fixtures\AccessorOrderChild;
29use JMS\Serializer\Tests\Fixtures\AccessorOrderMethod;
30use JMS\Serializer\Tests\Fixtures\AccessorOrderParent;
31use JMS\Serializer\Tests\Fixtures\Author;
32use JMS\Serializer\Tests\Fixtures\AuthorExpressionAccess;
33use JMS\Serializer\Tests\Fixtures\AuthorExpressionAccessContext;
34use JMS\Serializer\Tests\Fixtures\AuthorList;
35use JMS\Serializer\Tests\Fixtures\AuthorReadOnly;
36use JMS\Serializer\Tests\Fixtures\AuthorReadOnlyPerClass;
37use JMS\Serializer\Tests\Fixtures\AuthorsInline;
38use JMS\Serializer\Tests\Fixtures\BlogPost;
39use JMS\Serializer\Tests\Fixtures\CircularReferenceCollection;
40use JMS\Serializer\Tests\Fixtures\CircularReferenceParent;
41use JMS\Serializer\Tests\Fixtures\Comment;
42use JMS\Serializer\Tests\Fixtures\CurrencyAwareOrder;
43use JMS\Serializer\Tests\Fixtures\CurrencyAwarePrice;
44use JMS\Serializer\Tests\Fixtures\CustomDeserializationObject;
45use JMS\Serializer\Tests\Fixtures\DateTimeArraysObject;
46use JMS\Serializer\Tests\Fixtures\Discriminator\Car;
47use JMS\Serializer\Tests\Fixtures\Discriminator\ImagePost;
48use JMS\Serializer\Tests\Fixtures\Discriminator\Moped;
49use JMS\Serializer\Tests\Fixtures\Discriminator\Post;
50use JMS\Serializer\Tests\Fixtures\DiscriminatorGroup\Car as DiscriminatorGroupCar;
51use JMS\Serializer\Tests\Fixtures\ExclusionStrategy\AlwaysExcludeExclusionStrategy;
52use JMS\Serializer\Tests\Fixtures\FirstClassListCollection;
53use JMS\Serializer\Tests\Fixtures\Garage;
54use JMS\Serializer\Tests\Fixtures\GetSetObject;
55use JMS\Serializer\Tests\Fixtures\GroupsObject;
56use JMS\Serializer\Tests\Fixtures\GroupsUser;
57use JMS\Serializer\Tests\Fixtures\IndexedCommentsBlogPost;
58use JMS\Serializer\Tests\Fixtures\InitializedBlogPostConstructor;
59use JMS\Serializer\Tests\Fixtures\InitializedObjectConstructor;
60use JMS\Serializer\Tests\Fixtures\InlineChild;
61use JMS\Serializer\Tests\Fixtures\InlineChildEmpty;
62use JMS\Serializer\Tests\Fixtures\InlineChildWithGroups;
63use JMS\Serializer\Tests\Fixtures\InlineParent;
64use JMS\Serializer\Tests\Fixtures\InlineParentWithEmptyChild;
65use JMS\Serializer\Tests\Fixtures\Input;
66use JMS\Serializer\Tests\Fixtures\InvalidGroupsObject;
67use JMS\Serializer\Tests\Fixtures\Log;
68use JMS\Serializer\Tests\Fixtures\MaxDepth\Gh236Foo;
69use JMS\Serializer\Tests\Fixtures\NamedDateTimeArraysObject;
70use JMS\Serializer\Tests\Fixtures\NamedDateTimeImmutableArraysObject;
71use JMS\Serializer\Tests\Fixtures\Node;
72use JMS\Serializer\Tests\Fixtures\ObjectUsingTypeCasting;
73use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyHash;
74use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyNullableAndEmptyArrays;
75use JMS\Serializer\Tests\Fixtures\ObjectWithGenerator;
76use JMS\Serializer\Tests\Fixtures\ObjectWithIntListAndIntMap;
77use JMS\Serializer\Tests\Fixtures\ObjectWithIterator;
78use JMS\Serializer\Tests\Fixtures\ObjectWithLifecycleCallbacks;
79use JMS\Serializer\Tests\Fixtures\ObjectWithNullProperty;
80use JMS\Serializer\Tests\Fixtures\ObjectWithToString;
81use JMS\Serializer\Tests\Fixtures\ObjectWithTypedArraySetter;
82use JMS\Serializer\Tests\Fixtures\ObjectWithVersionedVirtualProperties;
83use JMS\Serializer\Tests\Fixtures\ObjectWithVirtualProperties;
84use JMS\Serializer\Tests\Fixtures\Order;
85use JMS\Serializer\Tests\Fixtures\ParentDoNotSkipWithEmptyChild;
86use JMS\Serializer\Tests\Fixtures\ParentSkipWithEmptyChild;
87use JMS\Serializer\Tests\Fixtures\PersonSecret;
88use JMS\Serializer\Tests\Fixtures\PersonSecretMore;
89use JMS\Serializer\Tests\Fixtures\PersonSecretMoreVirtual;
90use JMS\Serializer\Tests\Fixtures\PersonSecretVirtual;
91use JMS\Serializer\Tests\Fixtures\Price;
92use JMS\Serializer\Tests\Fixtures\Publisher;
93use JMS\Serializer\Tests\Fixtures\SimpleInternalObject;
94use JMS\Serializer\Tests\Fixtures\SimpleObject;
95use JMS\Serializer\Tests\Fixtures\SimpleObjectProxy;
96use JMS\Serializer\Tests\Fixtures\SimpleObjectWithStaticProp;
97use JMS\Serializer\Tests\Fixtures\Tag;
98use JMS\Serializer\Tests\Fixtures\Timestamp;
99use JMS\Serializer\Tests\Fixtures\Tree;
100use JMS\Serializer\Tests\Fixtures\VehicleInterfaceGarage;
101use JMS\Serializer\Visitor\DeserializationVisitorInterface;
102use JMS\Serializer\Visitor\SerializationVisitorInterface;
103use PHPUnit\Framework\TestCase;
104use Symfony\Component\ExpressionLanguage\ExpressionFunction;
105use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
106use Symfony\Component\Form\Extension\Core\Type\ButtonType;
107use Symfony\Component\Form\Extension\Core\Type\SubmitType;
108use Symfony\Component\Form\Form;
109use Symfony\Component\Form\FormConfigBuilder;
110use Symfony\Component\Form\FormError;
111use Symfony\Component\Form\FormFactoryBuilder;
112use Symfony\Component\Translation\IdentityTranslator;
113use Symfony\Component\Translation\MessageSelector;
114use Symfony\Component\Validator\ConstraintViolation;
115use Symfony\Component\Validator\ConstraintViolationList;
116
117abstract class BaseSerializationTest extends TestCase
118{
119    protected $factory;
120
121    /**
122     * @var EventDispatcher
123     */
124    protected $dispatcher;
125
126    /** @var Serializer */
127    protected $serializer;
128
129    /**
130     * @var HandlerRegistryInterface
131     */
132    protected $handlerRegistry;
133    protected $serializationVisitors;
134    protected $deserializationVisitors;
135    protected $objectConstructor;
136    protected $accessorStrategy;
137
138    public function testSerializeNullArray()
139    {
140        $arr = ['foo' => 'bar', 'baz' => null, null];
141
142        self::assertEquals(
143            $this->getContent('nullable'),
144            $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true))
145        );
146    }
147
148    public function testDeserializeObjectWithMissingTypedArrayProp()
149    {
150        /** @var ObjectWithTypedArraySetter $dObj */
151        $dObj = $this->serializer->deserialize(
152            $this->getContent('empty_object'),
153            ObjectWithTypedArraySetter::class,
154            $this->getFormat()
155        );
156
157        self::assertInstanceOf(ObjectWithTypedArraySetter::class, $dObj);
158
159        self::assertSame([], $dObj->getEmpty());
160    }
161
162    public function testSerializeNullArrayExcludingNulls()
163    {
164        $arr = ['foo' => 'bar', 'baz' => null, null];
165
166        self::assertEquals(
167            $this->getContent('nullable_skip'),
168            $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(false))
169        );
170    }
171
172    public function testObjectUsingTypeCasting()
173    {
174        $typeAliasing = new ObjectUsingTypeCasting();
175        $typeAliasing->asString = new ObjectWithToString('8');
176
177        self::assertEquals(
178            $this->getContent('type_casting'),
179            $this->serialize($typeAliasing)
180        );
181    }
182
183    public function testSerializeNullObject()
184    {
185        $obj = new ObjectWithNullProperty('foo', 'bar');
186
187        self::assertEquals(
188            $this->getContent('simple_object_nullable'),
189            $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true))
190        );
191    }
192
193    public function testDeserializeNullObject()
194    {
195        if (!$this->hasDeserializer()) {
196            $this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat()));
197        }
198
199        $obj = new ObjectWithNullProperty('foo', 'bar');
200
201        /** @var ObjectWithNullProperty $dObj */
202        $dObj = $this->serializer->deserialize(
203            $this->getContent('simple_object_nullable'),
204            ObjectWithNullProperty::class,
205            $this->getFormat()
206        );
207
208        self::assertEquals($obj, $dObj);
209        self::assertNull($dObj->getNullProperty());
210    }
211
212    /**
213     * @expectedException \JMS\Serializer\Exception\NotAcceptableException
214     * @dataProvider getTypes
215     */
216    public function testNull($type)
217    {
218        if ($this->hasDeserializer()) {
219            self::assertEquals(null, $this->deserialize($this->getContent('null'), $type));
220        }
221
222        // this is the default, but we want to be explicit here
223        $context = SerializationContext::create()->setSerializeNull(false);
224        $this->serialize(null, $context);
225    }
226
227    /**
228     * @dataProvider getTypes
229     */
230    public function testNullAllowed($type)
231    {
232        $context = SerializationContext::create()->setSerializeNull(true);
233        self::assertEquals($this->getContent('null'), $this->serialize(null, $context), $type);
234
235        if ($this->hasDeserializer()) {
236            self::assertEquals(null, $this->deserialize($this->getContent('null'), $type));
237        }
238    }
239
240    public function getTypes()
241    {
242        return [
243            ['NULL'],
244            ['integer'],
245            ['double'],
246            ['float'],
247            ['string'],
248            ['DateTime'],
249        ];
250    }
251
252    public function testString()
253    {
254        self::assertEquals($this->getContent('string'), $this->serialize('foo'));
255
256        if ($this->hasDeserializer()) {
257            self::assertEquals('foo', $this->deserialize($this->getContent('string'), 'string'));
258        }
259    }
260
261    /**
262     * @expectedException \JMS\Serializer\Exception\ExpressionLanguageRequiredException
263     * @expectedExceptionMessage To use conditional exclude/expose in JMS\Serializer\Tests\Fixtures\PersonSecret you must configure the expression language.
264     */
265    public function testExpressionExclusionNotConfigured()
266    {
267        $person = new PersonSecret();
268        $person->gender = 'f';
269        $person->name = 'mike';
270        $this->serialize($person);
271    }
272
273    public function testExpressionExclusionConfiguredWithDisjunctStrategy()
274    {
275        $person = new PersonSecret();
276        $person->gender = 'f';
277        $person->name = 'mike';
278
279        $language = new ExpressionLanguage();
280        $language->addFunction(new ExpressionFunction('show_data', static function () {
281            return 'true';
282        }, static function () {
283            return true;
284        }));
285
286        $builder = SerializerBuilder::create();
287        $builder->setExpressionEvaluator(new ExpressionEvaluator($language));
288        $serializer = $builder->build();
289
290        self::assertEquals($this->getContent('person_secret_hide'), $serializer->serialize($person, $this->getFormat()));
291    }
292
293    public function expressionFunctionProvider()
294    {
295        $person = new PersonSecret();
296        $person->gender = 'f';
297        $person->name = 'mike';
298
299        $personMoreSecret = new PersonSecretMore();
300        $personMoreSecret->gender = 'f';
301        $personMoreSecret->name = 'mike';
302
303        $personVirtual = new PersonSecretVirtual();
304        $personVirtual->gender = 'f';
305        $personVirtual->name = 'mike';
306
307        $personMoreSecretVirtual = new PersonSecretMoreVirtual();
308        $personMoreSecretVirtual->gender = 'f';
309        $personMoreSecretVirtual->name = 'mike';
310
311        $showGender = new ExpressionFunction('show_data', static function () {
312            return 'true';
313        }, static function () {
314            return true;
315        });
316
317        $hideGender = new ExpressionFunction('show_data', static function () {
318            return 'false';
319        }, static function () {
320            return false;
321        });
322
323        return [
324            [
325                $person,
326                $showGender,
327                'person_secret_hide',
328            ],
329            [
330                $person,
331                $hideGender,
332                'person_secret_show',
333            ],
334            [
335                $personMoreSecret,
336                $showGender,
337                'person_secret_show',
338            ],
339            [
340                $personMoreSecret,
341                $hideGender,
342                'person_secret_hide',
343            ],
344            [
345                $personVirtual,
346                $showGender,
347                'person_secret_hide',
348            ],
349            [
350                $personVirtual,
351                $hideGender,
352                'person_secret_show',
353            ],
354            [
355                $personMoreSecretVirtual,
356                $showGender,
357                'person_secret_show',
358            ],
359            [
360                $personMoreSecretVirtual,
361                $hideGender,
362                'person_secret_hide',
363            ],
364        ];
365    }
366
367    /**
368     * @param PersonSecret|PersonSecretMore $person
369     * @param ExpressionFunction $function
370     * @param string $json
371     *
372     * @dataProvider expressionFunctionProvider
373     */
374    public function testExpressionExclusion($person, ExpressionFunction $function, $json)
375    {
376        $language = new ExpressionLanguage();
377        $language->addFunction($function);
378
379        $builder = SerializerBuilder::create();
380        $builder->setExpressionEvaluator(new ExpressionEvaluator($language));
381        $serializer = $builder->build();
382
383        self::assertEquals($this->getContent($json), $serializer->serialize($person, $this->getFormat()));
384    }
385
386    /**
387     * @dataProvider getBooleans
388     */
389    public function testBooleans($strBoolean, $boolean)
390    {
391        self::assertEquals($this->getContent('boolean_' . $strBoolean), $this->serialize($boolean));
392
393        if ($this->hasDeserializer()) {
394            self::assertSame($boolean, $this->deserialize($this->getContent('boolean_' . $strBoolean), 'boolean'));
395        }
396    }
397
398    public function getBooleans()
399    {
400        return [['true', true], ['false', false]];
401    }
402
403    /**
404     * @dataProvider getNumerics
405     */
406    public function testNumerics($key, $value, $type)
407    {
408        self::assertSame($this->getContent($key), $this->serialize($value));
409
410        if ($this->hasDeserializer()) {
411            self::assertEquals($value, $this->deserialize($this->getContent($key), $type));
412        }
413    }
414
415    public function getNumerics()
416    {
417        return [
418            ['integer', 1, 'integer'],
419            ['float', 4.533, 'double'],
420            ['float', 4.533, 'float'],
421            ['float_trailing_zero', 1.0, 'double'],
422            ['float_trailing_zero', 1.0, 'float'],
423        ];
424    }
425
426    public function testSimpleInternalObject()
427    {
428        $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher);
429        $builder->setMetadataDirs([
430            'JMS\Serializer\Tests\Fixtures' => __DIR__ . '/metadata/SimpleInternalObject',
431            '' => __DIR__ . '/metadata/SimpleInternalObject',
432        ]);
433
434        $this->serializer = $builder->build();
435
436        $obj = new SimpleInternalObject('foo', 'bar');
437
438        $this->assertEquals($this->getContent('simple_object'), $this->serialize($obj));
439
440        if ($this->hasDeserializer()) {
441            $this->assertEquals($obj, $this->deserialize($this->getContent('simple_object'), get_class($obj)));
442        }
443    }
444
445    public function testSimpleObject()
446    {
447        self::assertEquals($this->getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar')));
448
449        if ($this->hasDeserializer()) {
450            self::assertEquals($obj, $this->deserialize($this->getContent('simple_object'), get_class($obj)));
451        }
452    }
453
454    public function testSimpleObjectStaticProp()
455    {
456        $this->assertEquals($this->getContent('simple_object'), $this->serialize($obj = new SimpleObjectWithStaticProp('foo', 'bar')));
457
458        if ($this->hasDeserializer()) {
459            $this->assertEquals($obj, $this->deserialize($this->getContent('simple_object'), get_class($obj)));
460        }
461    }
462
463    public function testArrayStrings()
464    {
465        $data = ['foo', 'bar'];
466        self::assertEquals($this->getContent('array_strings'), $this->serialize($data));
467
468        if ($this->hasDeserializer()) {
469            self::assertEquals($data, $this->deserialize($this->getContent('array_strings'), 'array<string>'));
470        }
471    }
472
473    public function testArrayBooleans()
474    {
475        $data = [true, false];
476        self::assertEquals($this->getContent('array_booleans'), $this->serialize($data));
477
478        if ($this->hasDeserializer()) {
479            self::assertEquals($data, $this->deserialize($this->getContent('array_booleans'), 'array<boolean>'));
480        }
481    }
482
483    public function testArrayIntegers()
484    {
485        $data = [1, 3, 4];
486        self::assertEquals($this->getContent('array_integers'), $this->serialize($data));
487
488        if ($this->hasDeserializer()) {
489            self::assertEquals($data, $this->deserialize($this->getContent('array_integers'), 'array<integer>'));
490        }
491    }
492
493    public function testArrayEmpty()
494    {
495        if ('xml' === $this->getFormat()) {
496            $this->markTestSkipped('XML can\'t be tested for empty array');
497        }
498
499        $data = ['array' => []];
500        self::assertEquals($this->getContent('array_empty'), $this->serialize($data));
501
502        if ($this->hasDeserializer()) {
503            self::assertEquals($data, $this->deserialize($this->getContent('array_empty'), 'array'));
504        }
505    }
506
507    public function testArrayFloats()
508    {
509        $data = [1.34, 3.0, 6.42];
510        self::assertEquals($this->getContent('array_floats'), $this->serialize($data));
511
512        if ($this->hasDeserializer()) {
513            self::assertEquals($data, $this->deserialize($this->getContent('array_floats'), 'array<double>'));
514        }
515    }
516
517    public function testArrayObjects()
518    {
519        $data = [new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')];
520        self::assertEquals($this->getContent('array_objects'), $this->serialize($data));
521
522        if ($this->hasDeserializer()) {
523            self::assertEquals($data, $this->deserialize($this->getContent('array_objects'), 'array<JMS\Serializer\Tests\Fixtures\SimpleObject>'));
524        }
525    }
526
527    public function testArrayListAndMapDifference()
528    {
529        $arrayData = [0 => 1, 2 => 2, 3 => 3]; // Misses key 1
530        $data = new ObjectWithIntListAndIntMap($arrayData, $arrayData);
531
532        self::assertEquals($this->getContent('array_list_and_map_difference'), $this->serialize($data));
533    }
534
535    public function testDateTimeArrays()
536    {
537        $data = [
538            new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')),
539            new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')),
540        ];
541
542        $object = new DateTimeArraysObject($data, $data);
543        $serializedObject = $this->serialize($object);
544
545        self::assertEquals($this->getContent('array_datetimes_object'), $serializedObject);
546
547        if ($this->hasDeserializer()) {
548            /** @var DateTimeArraysObject $deserializedObject */
549            $deserializedObject = $this->deserialize($this->getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject');
550
551            /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */
552            foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) {
553                $dateTime->setTimezone(new \DateTimeZone('UTC'));
554            }
555
556            foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) {
557                $dateTime->setTimezone(new \DateTimeZone('UTC'));
558            }
559
560            self::assertEquals($object, $deserializedObject);
561        }
562    }
563
564    public function testNamedDateTimeArrays()
565    {
566        $data = [
567            new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')),
568            new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')),
569        ];
570
571        $object = new NamedDateTimeArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]);
572        $serializedObject = $this->serialize($object);
573
574        self::assertEquals($this->getContent('array_named_datetimes_object'), $serializedObject);
575
576        if ($this->hasDeserializer()) {
577            // skip XML deserialization
578            if ('xml' === $this->getFormat()) {
579                return;
580            }
581
582            /** @var NamedDateTimeArraysObject $deserializedObject */
583            $deserializedObject = $this->deserialize($this->getContent('array_named_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\NamedDateTimeArraysObject');
584
585            /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */
586            foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) {
587                $dateTime->setTimezone(new \DateTimeZone('UTC'));
588            }
589
590            self::assertEquals($object, $deserializedObject);
591        }
592    }
593
594    /**
595     * @group datetime
596     */
597    public function testNamedDateTimeImmutableArrays()
598    {
599        $data = [
600            new \DateTimeImmutable('2047-01-01 12:47:47', new \DateTimeZone('UTC')),
601            new \DateTimeImmutable('2016-12-05 00:00:00', new \DateTimeZone('UTC')),
602        ];
603
604        $object = new NamedDateTimeImmutableArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]);
605        $serializedObject = $this->serialize($object);
606
607        self::assertEquals($this->getContent('array_named_datetimeimmutables_object'), $serializedObject);
608
609        if ($this->hasDeserializer()) {
610            if ('xml' === $this->getFormat()) {
611                $this->markTestSkipped('XML deserialization does not support key-val pairs mode');
612            }
613            /** @var NamedDateTimeArraysObject $deserializedObject */
614            $deserializedObject = $this->deserialize($this->getContent('array_named_datetimeimmutables_object'), 'Jms\Serializer\Tests\Fixtures\NamedDateTimeImmutableArraysObject');
615
616            /** deserialized object has a default timezone set depending on user's timezone settings. That's why we manually set the UTC timezone on the DateTime objects. */
617            foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) {
618                $dateTime->setTimezone(new \DateTimeZone('UTC'));
619            }
620
621            self::assertEquals($object, $deserializedObject);
622        }
623    }
624
625    public function testArrayMixed()
626    {
627        self::assertEquals($this->getContent('array_mixed'), $this->serialize(['foo', 1, true, new SimpleObject('foo', 'bar'), [1, 3, true]]));
628    }
629
630    /**
631     * @dataProvider getDateTime
632     * @group datetime
633     */
634    public function testDateTime($key, $value, $type)
635    {
636        self::assertEquals($this->getContent($key), $this->serialize($value));
637
638        if ($this->hasDeserializer()) {
639            $deserialized = $this->deserialize($this->getContent($key), $type);
640
641            self::assertInternalType('object', $deserialized);
642            self::assertInstanceOf(get_class($value), $deserialized);
643            self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp());
644        }
645    }
646
647    public function getDateTime()
648    {
649        return [
650            ['date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'],
651        ];
652    }
653
654    /**
655     * @dataProvider getDateTimeImmutable
656     * @group datetime
657     */
658    public function testDateTimeImmutable($key, $value, $type)
659    {
660        self::assertEquals($this->getContent($key), $this->serialize($value));
661
662        if ($this->hasDeserializer()) {
663            $deserialized = $this->deserialize($this->getContent($key), $type);
664
665            self::assertInternalType('object', $deserialized);
666            self::assertInstanceOf(get_class($value), $deserialized);
667            self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp());
668        }
669    }
670
671    public function getDateTimeImmutable()
672    {
673        return [
674            ['date_time_immutable', new \DateTimeImmutable('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTimeImmutable'],
675        ];
676    }
677
678    public function testTimestamp()
679    {
680        $value = new Timestamp(new \DateTime('2016-02-11 00:00:00', new \DateTimeZone('UTC')));
681        self::assertEquals($this->getContent('timestamp'), $this->serialize($value));
682
683        if ($this->hasDeserializer()) {
684            $deserialized = $this->deserialize($this->getContent('timestamp'), Timestamp::class);
685            self::assertEquals($value, $deserialized);
686            self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp());
687
688            $deserialized = $this->deserialize($this->getContent('timestamp_prev'), Timestamp::class);
689            self::assertEquals($value, $deserialized);
690            self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp());
691        }
692    }
693
694    public function testDateInterval()
695    {
696        $duration = new \DateInterval('PT45M');
697
698        self::assertEquals($this->getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat()));
699
700        if ($this->hasDeserializer()) {
701            $deserialized = $this->deserialize($this->getContent('date_interval'), \DateInterval::class);
702            self::assertEquals($duration, $deserialized);
703            self::assertEquals($duration->i, $deserialized->i);
704        }
705    }
706
707    public function testBlogPost()
708    {
709        $post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo'));
710        $post->addComment($comment = new Comment($author, 'foo'));
711
712        $post->addTag($tag1 = new Tag('tag1'));
713        $post->addTag($tag2 = new Tag('tag2'));
714
715        self::assertEquals($this->getContent('blog_post'), $this->serialize($post));
716
717        if ($this->hasDeserializer()) {
718            $deserialized = $this->deserialize($this->getContent('blog_post'), get_class($post));
719            self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM));
720            self::assertAttributeEquals('This is a nice title.', 'title', $deserialized);
721            self::assertAttributeSame(false, 'published', $deserialized);
722            self::assertAttributeSame(false, 'reviewed', $deserialized);
723            self::assertAttributeSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', 'etag', $deserialized);
724            self::assertAttributeEquals(new ArrayCollection([$comment]), 'comments', $deserialized);
725            self::assertAttributeEquals([$comment], 'comments2', $deserialized);
726            self::assertAttributeEquals($author, 'author', $deserialized);
727            self::assertAttributeEquals([$tag1, $tag2], 'tag', $deserialized);
728        }
729    }
730
731    public function testDeserializingNull()
732    {
733        $objectConstructor = new InitializedBlogPostConstructor();
734
735        $builder = SerializerBuilder::create();
736        $builder->setObjectConstructor($objectConstructor);
737        $this->serializer = $builder->build();
738
739        $post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo'));
740
741        $this->setField($post, 'author', null);
742        $this->setField($post, 'publisher', null);
743
744        self::assertEquals($this->getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true)));
745
746        if ($this->hasDeserializer()) {
747            $deserialized = $this->deserialize($this->getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create());
748
749            self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM));
750            self::assertAttributeEquals('This is a nice title.', 'title', $deserialized);
751            self::assertAttributeSame(false, 'published', $deserialized);
752            self::assertAttributeSame(false, 'reviewed', $deserialized);
753            self::assertAttributeEquals(new ArrayCollection(), 'comments', $deserialized);
754            self::assertEquals(null, $this->getField($deserialized, 'author'));
755        }
756    }
757
758    public function testExpressionAuthor()
759    {
760        $evaluator = new ExpressionEvaluator(new ExpressionLanguage());
761
762        $builder = SerializerBuilder::create();
763        $builder->setExpressionEvaluator($evaluator);
764        $serializer = $builder->build();
765
766        $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis');
767        self::assertEquals($this->getContent('author_expression'), $serializer->serialize($author, $this->getFormat()));
768    }
769
770    public function testExpressionAuthorWithContextVars()
771    {
772        $evaluator = new ExpressionEvaluator(new ExpressionLanguage());
773
774        $builder = SerializerBuilder::create();
775        $builder->setExpressionEvaluator($evaluator);
776        $serializer = $builder->build();
777
778        $author = new AuthorExpressionAccessContext('Ruud');
779        self::assertEquals($this->getContent('author_expression_context'), $serializer->serialize($author, $this->getFormat()));
780    }
781
782
783    /**
784     * @expectedException \JMS\Serializer\Exception\ExpressionLanguageRequiredException
785     * @expectedExceptionMessage The property firstName on JMS\Serializer\Tests\Fixtures\AuthorExpressionAccess requires the expression accessor strategy to be enabled.
786     */
787    public function testExpressionAccessorStrategNotEnabled()
788    {
789        $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis');
790        self::assertEquals($this->getContent('author_expression'), $this->serialize($author));
791    }
792
793    public function testReadOnly()
794    {
795        $author = new AuthorReadOnly(123, 'Ruud Kamphuis');
796        self::assertEquals($this->getContent('readonly'), $this->serialize($author));
797
798        if ($this->hasDeserializer()) {
799            $deserialized = $this->deserialize($this->getContent('readonly'), get_class($author));
800            self::assertNull($this->getField($deserialized, 'id'));
801            self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name'));
802        }
803    }
804
805    public function testReadOnlyClass()
806    {
807        $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis');
808        self::assertEquals($this->getContent('readonly'), $this->serialize($author));
809
810        if ($this->hasDeserializer()) {
811            $deserialized = $this->deserialize($this->getContent('readonly'), get_class($author));
812            self::assertNull($this->getField($deserialized, 'id'));
813            self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name'));
814        }
815    }
816
817    public function testPrice()
818    {
819        $price = new Price(3);
820        self::assertEquals($this->getContent('price'), $this->serialize($price));
821
822        if ($this->hasDeserializer()) {
823            $deserialized = $this->deserialize($this->getContent('price'), get_class($price));
824            self::assertEquals(3, $this->getField($deserialized, 'price'));
825        }
826    }
827
828    public function testOrder()
829    {
830        $order = new Order(new Price(12.34));
831        self::assertEquals($this->getContent('order'), $this->serialize($order));
832
833        if ($this->hasDeserializer()) {
834            self::assertEquals($order, $this->deserialize($this->getContent('order'), get_class($order)));
835        }
836    }
837
838    public function testCurrencyAwarePrice()
839    {
840        $price = new CurrencyAwarePrice(2.34);
841        self::assertEquals($this->getContent('currency_aware_price'), $this->serialize($price));
842
843        if ($this->hasDeserializer()) {
844            self::assertEquals($price, $this->deserialize($this->getContent('currency_aware_price'), get_class($price)));
845        }
846    }
847
848    public function testOrderWithCurrencyAwarePrice()
849    {
850        $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23));
851        self::assertEquals($this->getContent('order_with_currency_aware_price'), $this->serialize($order));
852
853        if ($this->hasDeserializer()) {
854            self::assertEquals($order, $this->deserialize($this->getContent('order_with_currency_aware_price'), get_class($order)));
855        }
856    }
857
858    public function testInline()
859    {
860        $inline = new InlineParent();
861
862        $result = $this->serialize($inline);
863        self::assertEquals($this->getContent('inline'), $result);
864
865        if ($this->hasDeserializer()) {
866            self::assertEquals($inline, $this->deserialize($this->getContent('inline'), get_class($inline)));
867        }
868    }
869
870    public function testInlineEmptyChild()
871    {
872        $inline = new InlineParentWithEmptyChild(new InlineChildEmpty());
873        $result = $this->serialize($inline);
874        self::assertEquals($this->getContent('inline_child_empty'), $result);
875        if ($this->hasDeserializer()) {
876            self::assertEquals($inline, $this->deserialize($this->getContent('inline'), get_class($inline)));
877        }
878    }
879
880    public function testEmptyChild()
881    {
882        // by empty object
883        $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildEmpty());
884        self::assertEquals($this->getContent('empty_child'), $this->serialize($inline));
885
886        // by nulls
887        $inner = new InlineChild();
888        $inner->a = null;
889        $inner->b = null;
890        $inline = new ParentDoNotSkipWithEmptyChild($inner);
891        self::assertEquals($this->getContent('empty_child'), $this->serialize($inline));
892
893        // by exclusion strategy
894        $context = SerializationContext::create()->setGroups(['Default']);
895        $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildWithGroups());
896        self::assertEquals($this->getContent('empty_child'), $this->serialize($inline, $context));
897    }
898
899    public function testSkipEmptyChild()
900    {
901        // by empty object
902        $inline = new ParentSkipWithEmptyChild(new InlineChildEmpty());
903        self::assertEquals($this->getContent('empty_child_skip'), $this->serialize($inline));
904
905        // by nulls
906        $inner = new InlineChild();
907        $inner->a = null;
908        $inner->b = null;
909        $inline = new ParentSkipWithEmptyChild($inner);
910        self::assertEquals($this->getContent('empty_child_skip'), $this->serialize($inline));
911
912        // by exclusion strategy
913        $context = SerializationContext::create()->setGroups(['Default']);
914        $inline = new ParentSkipWithEmptyChild(new InlineChildWithGroups());
915        self::assertEquals($this->getContent('empty_child_skip'), $this->serialize($inline, $context));
916    }
917
918    public function testLog()
919    {
920        self::assertEquals($this->getContent('log'), $this->serialize($log = new Log()));
921
922        if ($this->hasDeserializer()) {
923            $deserialized = $this->deserialize($this->getContent('log'), get_class($log));
924            self::assertEquals($log, $deserialized);
925        }
926    }
927
928    public function testSelfCircularReferenceCollection()
929    {
930        $object = new CircularReferenceCollection();
931        $object->collection[] = $object;
932        self::assertEquals($this->getContent('circular_reference_collection'), $this->serialize($object));
933    }
934
935    public function testCircularReference()
936    {
937        $object = new CircularReferenceParent();
938        self::assertEquals($this->getContent('circular_reference'), $this->serialize($object));
939
940        if ($this->hasDeserializer()) {
941            $deserialized = $this->deserialize($this->getContent('circular_reference'), get_class($object));
942
943            $col = $this->getField($deserialized, 'collection');
944            self::assertCount(2, $col);
945            self::assertEquals('child1', $col[0]->getName());
946            self::assertEquals('child2', $col[1]->getName());
947            self::assertSame($deserialized, $col[0]->getParent());
948            self::assertSame($deserialized, $col[1]->getParent());
949
950            $col = $this->getField($deserialized, 'anotherCollection');
951            self::assertCount(2, $col);
952            self::assertEquals('child1', $col[0]->getName());
953            self::assertEquals('child2', $col[1]->getName());
954            self::assertSame($deserialized, $col[0]->getParent());
955            self::assertSame($deserialized, $col[1]->getParent());
956        }
957    }
958
959    public function testLifecycleCallbacks()
960    {
961        $object = new ObjectWithLifecycleCallbacks();
962        self::assertEquals($this->getContent('lifecycle_callbacks'), $this->serialize($object));
963        self::assertAttributeSame(null, 'name', $object);
964
965        if ($this->hasDeserializer()) {
966            $deserialized = $this->deserialize($this->getContent('lifecycle_callbacks'), get_class($object));
967            self::assertEquals($object, $deserialized);
968        }
969    }
970
971    public function testFormErrors()
972    {
973        $errors = [
974            new FormError('This is the form error'),
975            new FormError('Another error'),
976        ];
977
978        self::assertEquals($this->getContent('form_errors'), $this->serialize($errors));
979    }
980
981    public function testNestedFormErrors()
982    {
983        $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
984
985        $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher);
986        $formConfigBuilder->setCompound(true);
987        $formConfigBuilder->setDataMapper($this->getMockBuilder('Symfony\Component\Form\DataMapperInterface')->getMock());
988        $fooConfig = $formConfigBuilder->getFormConfig();
989
990        $form = new Form($fooConfig);
991        $form->addError(new FormError('This is the form error'));
992
993        $formConfigBuilder = new FormConfigBuilder('bar', null, $dispatcher);
994        $barConfig = $formConfigBuilder->getFormConfig();
995        $child = new Form($barConfig);
996        $child->addError(new FormError('Error of the child form'));
997        $form->add($child);
998
999        self::assertEquals($this->getContent('nested_form_errors'), $this->serialize($form));
1000    }
1001
1002    /**
1003     * @doesNotPerformAssertions
1004     */
1005    public function testFormErrorsWithNonFormComponents()
1006    {
1007        if (!class_exists('Symfony\Component\Form\Extension\Core\Type\SubmitType')) {
1008            $this->markTestSkipped('Not using Symfony Form >= 2.3 with submit type');
1009        }
1010
1011        $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
1012
1013        $factoryBuilder = new FormFactoryBuilder();
1014        $factoryBuilder->addType(new SubmitType());
1015        $factoryBuilder->addType(new ButtonType());
1016        $factory = $factoryBuilder->getFormFactory();
1017
1018        $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher);
1019        $formConfigBuilder->setFormFactory($factory);
1020        $formConfigBuilder->setCompound(true);
1021        $formConfigBuilder->setDataMapper($this->getMockBuilder('Symfony\Component\Form\DataMapperInterface')->getMock());
1022        $fooConfig = $formConfigBuilder->getFormConfig();
1023
1024        $form = new Form($fooConfig);
1025        $form->add('save', SubmitType::class);
1026
1027        try {
1028            $this->serialize($form);
1029        } catch (\Throwable $e) {
1030            self::assertTrue(false, 'Serialization should not throw an exception');
1031        }
1032    }
1033
1034    public function testConstraintViolation()
1035    {
1036        $violation = new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null);
1037
1038        self::assertEquals($this->getContent('constraint_violation'), $this->serialize($violation));
1039    }
1040
1041    public function testConstraintViolationList()
1042    {
1043        $violations = new ConstraintViolationList();
1044        $violations->add(new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null));
1045        $violations->add(new ConstraintViolation('Message of another violation', 'Message of another violation', [], null, 'bar', null));
1046
1047        self::assertEquals($this->getContent('constraint_violation_list'), $this->serialize($violations));
1048    }
1049
1050    public function testDoctrineProxy()
1051    {
1052        if (!class_exists('Doctrine\ORM\Version')) {
1053            $this->markTestSkipped('Doctrine is not available.');
1054        }
1055
1056        $object = new SimpleObjectProxy('foo', 'bar');
1057
1058        self::assertEquals($this->getContent('orm_proxy'), $this->serialize($object));
1059    }
1060
1061    public function testInitializedDoctrineProxy()
1062    {
1063        if (!class_exists('Doctrine\ORM\Version')) {
1064            $this->markTestSkipped('Doctrine is not available.');
1065        }
1066
1067        $object = new SimpleObjectProxy('foo', 'bar');
1068        $object->__load();
1069
1070        self::assertEquals($this->getContent('orm_proxy'), $this->serialize($object));
1071    }
1072
1073    public function testCustomAccessor()
1074    {
1075        $post = new IndexedCommentsBlogPost();
1076
1077        self::assertEquals($this->getContent('custom_accessor'), $this->serialize($post));
1078    }
1079
1080    public function testMixedAccessTypes()
1081    {
1082        $object = new GetSetObject();
1083
1084        self::assertEquals($this->getContent('mixed_access_types'), $this->serialize($object));
1085
1086        if ($this->hasDeserializer()) {
1087            $object = $this->deserialize($this->getContent('mixed_access_types'), 'JMS\Serializer\Tests\Fixtures\GetSetObject');
1088            self::assertAttributeEquals(1, 'id', $object);
1089            self::assertAttributeEquals('Johannes', 'name', $object);
1090            self::assertAttributeEquals(42, 'readOnlyProperty', $object);
1091        }
1092    }
1093
1094    public function testAccessorOrder()
1095    {
1096        self::assertEquals($this->getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild()));
1097        self::assertEquals($this->getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent()));
1098        self::assertEquals($this->getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod()));
1099    }
1100
1101    public function testGroups()
1102    {
1103        $groupsObject = new GroupsObject();
1104
1105        self::assertEquals($this->getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat()));
1106
1107        self::assertEquals(
1108            $this->getContent('groups_foo'),
1109            $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo']))
1110        );
1111
1112        self::assertEquals(
1113            $this->getContent('groups_foobar'),
1114            $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo', 'bar']))
1115        );
1116
1117        self::assertEquals(
1118            $this->getContent('groups_all'),
1119            $this->serializer->serialize($groupsObject, $this->getFormat())
1120        );
1121
1122        self::assertEquals(
1123            $this->getContent('groups_default'),
1124            $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP]))
1125        );
1126
1127        self::assertEquals(
1128            $this->getContent('groups_default'),
1129            $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP]))
1130        );
1131    }
1132
1133    public function testAdvancedGroups()
1134    {
1135        $adrien = new GroupsUser(
1136            'John',
1137            new GroupsUser(
1138                'John Manager',
1139                null,
1140                [
1141                    new GroupsUser(
1142                        'John Manager friend 1',
1143                        new GroupsUser('John Manager friend 1 manager')
1144                    ),
1145                    new GroupsUser('John Manager friend 2'),
1146                ]
1147            ),
1148            [
1149                new GroupsUser(
1150                    'John friend 1',
1151                    new GroupsUser('John friend 1 manager')
1152                ),
1153                new GroupsUser(
1154                    'John friend 2',
1155                    new GroupsUser('John friend 2 manager')
1156                ),
1157            ]
1158        );
1159
1160        self::assertEquals(
1161            $this->getContent('groups_advanced'),
1162            $this->serializer->serialize(
1163                $adrien,
1164                $this->getFormat(),
1165                SerializationContext::create()->setGroups([
1166                    GroupsExclusionStrategy::DEFAULT_GROUP,
1167                    'manager_group',
1168                    'friends_group',
1169
1170                    'manager' => [
1171                        GroupsExclusionStrategy::DEFAULT_GROUP,
1172                        'friends_group',
1173
1174                        'friends' => ['nickname_group'],
1175                    ],
1176                    'friends' => [
1177                        'manager_group',
1178                        'nickname_group',
1179                    ],
1180                ])
1181            )
1182        );
1183    }
1184
1185    /**
1186     * @expectedException \JMS\Serializer\Exception\InvalidMetadataException
1187     * @expectedExceptionMessage Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups?
1188     */
1189    public function testInvalidGroupName()
1190    {
1191        $groupsObject = new InvalidGroupsObject();
1192
1193        $this->serializer->serialize($groupsObject, $this->getFormat());
1194    }
1195
1196    public function testVirtualProperty()
1197    {
1198        self::assertEquals($this->getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties()));
1199    }
1200
1201    public function testVirtualVersions()
1202    {
1203        $evaluator = new ExpressionEvaluator(new ExpressionLanguage());
1204
1205        $builder = SerializerBuilder::create();
1206        $builder->setExpressionEvaluator($evaluator);
1207        $serializer = $builder->build();
1208
1209        self::assertEquals(
1210            $this->getContent('virtual_properties_low'),
1211            $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('2'))
1212        );
1213
1214        self::assertEquals(
1215            $this->getContent('virtual_properties_all'),
1216            $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('7'))
1217        );
1218
1219        self::assertEquals(
1220            $this->getContent('virtual_properties_high'),
1221            $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('9'))
1222        );
1223    }
1224
1225    public function testCustomHandler()
1226    {
1227        if (!$this->hasDeserializer()) {
1228            return;
1229        }
1230
1231        $handler = static function () {
1232            return new CustomDeserializationObject('customly_unserialized_value');
1233        };
1234
1235        $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler);
1236
1237        $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat());
1238        $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat());
1239        self::assertEquals('customly_unserialized_value', $object->someProperty);
1240    }
1241
1242    public function testInput()
1243    {
1244        self::assertEquals($this->getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat()));
1245    }
1246
1247    public function testObjectWithEmptyHash()
1248    {
1249        self::assertEquals($this->getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat()));
1250    }
1251
1252    /**
1253     * @group null
1254     */
1255    public function testSerializeObjectWhenNull()
1256    {
1257        self::assertEquals(
1258            $this->getContent('object_when_null'),
1259            $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false))
1260        );
1261
1262        self::assertEquals(
1263            $this->getContent('object_when_null_and_serialized'),
1264            $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true))
1265        );
1266    }
1267
1268    /**
1269     * @group polymorphic
1270     */
1271    public function testPolymorphicObjectsWithGroup()
1272    {
1273        $context = SerializationContext::create();
1274        $context->setGroups(['foo']);
1275
1276        self::assertEquals(
1277            $this->getContent('car'),
1278            $this->serialize(new DiscriminatorGroupCar(5), $context)
1279        );
1280    }
1281
1282    /**
1283     * @group polymorphic
1284     */
1285    public function testPolymorphicObjects()
1286    {
1287        self::assertEquals(
1288            $this->getContent('car'),
1289            $this->serialize(new Car(5))
1290        );
1291        self::assertEquals(
1292            $this->getContent('post'),
1293            $this->serialize(new Post('Post Title'))
1294        );
1295        self::assertEquals(
1296            $this->getContent('image_post'),
1297            $this->serialize(new ImagePost('Image Post Title'))
1298        );
1299
1300        if ($this->hasDeserializer()) {
1301            self::assertEquals(
1302                new Car(5),
1303                $this->deserialize(
1304                    $this->getContent('car'),
1305                    'JMS\Serializer\Tests\Fixtures\Discriminator\Car'
1306                ),
1307                'Class is resolved correctly when concrete sub-class is used.'
1308            );
1309
1310            self::assertEquals(
1311                new Car(5),
1312                $this->deserialize(
1313                    $this->getContent('car'),
1314                    'JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle'
1315                ),
1316                'Class is resolved correctly when least supertype is used.'
1317            );
1318
1319            self::assertEquals(
1320                new Car(5),
1321                $this->deserialize(
1322                    $this->getContent('car_without_type'),
1323                    'JMS\Serializer\Tests\Fixtures\Discriminator\Car'
1324                ),
1325                'Class is resolved correctly when concrete sub-class is used and no type is defined.'
1326            );
1327
1328            self::assertEquals(
1329                new Post('Post Title'),
1330                $this->deserialize(
1331                    $this->getContent('post'),
1332                    'JMS\Serializer\Tests\Fixtures\Discriminator\Post'
1333                ),
1334                'Class is resolved correctly when parent class is used and type is set.'
1335            );
1336
1337            self::assertEquals(
1338                new ImagePost('Image Post Title'),
1339                $this->deserialize(
1340                    $this->getContent('image_post'),
1341                    'JMS\Serializer\Tests\Fixtures\Discriminator\Post'
1342                ),
1343                'Class is resolved correctly when least supertype is used.'
1344            );
1345
1346            self::assertEquals(
1347                new ImagePost('Image Post Title'),
1348                $this->deserialize(
1349                    $this->getContent('image_post'),
1350                    'JMS\Serializer\Tests\Fixtures\Discriminator\ImagePost'
1351                ),
1352                'Class is resolved correctly when concrete sub-class is used and no type is defined.'
1353            );
1354        }
1355    }
1356
1357    /**
1358     * @group polymorphic
1359     */
1360    public function testNestedPolymorphicObjects()
1361    {
1362        $garage = new Garage([new Car(3), new Moped(1)]);
1363        self::assertEquals(
1364            $this->getContent('garage'),
1365            $this->serialize($garage)
1366        );
1367
1368        if ($this->hasDeserializer()) {
1369            self::assertEquals(
1370                $garage,
1371                $this->deserialize(
1372                    $this->getContent('garage'),
1373                    'JMS\Serializer\Tests\Fixtures\Garage'
1374                )
1375            );
1376        }
1377    }
1378
1379    /**
1380     * @group polymorphic
1381     */
1382    public function testNestedPolymorphicInterfaces()
1383    {
1384        $garage = new VehicleInterfaceGarage([new Car(3), new Moped(1)]);
1385        self::assertEquals(
1386            $this->getContent('garage'),
1387            $this->serialize($garage)
1388        );
1389
1390        if ($this->hasDeserializer()) {
1391            self::assertEquals(
1392                $garage,
1393                $this->deserialize(
1394                    $this->getContent('garage'),
1395                    'JMS\Serializer\Tests\Fixtures\VehicleInterfaceGarage'
1396                )
1397            );
1398        }
1399    }
1400
1401    /**
1402     * @group polymorphic
1403     * @expectedException LogicException
1404     */
1405    public function testPolymorphicObjectsInvalidDeserialization()
1406    {
1407        if (!$this->hasDeserializer()) {
1408            throw new \LogicException('No deserializer');
1409        }
1410
1411        $this->deserialize(
1412            $this->getContent('car_without_type'),
1413            'JMS\Serializer\Tests\Fixtures\Discriminator\Vehicle'
1414        );
1415    }
1416
1417    public function testDepthExclusionStrategy()
1418    {
1419        $context = SerializationContext::create()
1420            ->addExclusionStrategy(new DepthExclusionStrategy());
1421
1422        $data = new Tree(
1423            new Node([
1424                new Node([
1425                    new Node([
1426                        new Node([
1427                            new Node(),
1428                        ]),
1429                    ]),
1430                ]),
1431            ])
1432        );
1433
1434        self::assertEquals($this->getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context));
1435    }
1436
1437    public function testMaxDepthWithSkippableObject()
1438    {
1439        $data = new Gh236Foo();
1440
1441        $context = SerializationContext::create()->enableMaxDepthChecks();
1442        $serialized = $this->serialize($data, $context);
1443
1444        self::assertEquals($this->getContent('maxdepth_skippabe_object'), $serialized);
1445    }
1446
1447    public function testDeserializingIntoExistingObject()
1448    {
1449        if (!$this->hasDeserializer()) {
1450            return;
1451        }
1452        $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor());
1453
1454        $builder = SerializerBuilder::create();
1455        $builder->setObjectConstructor($objectConstructor);
1456        $serializer = $builder->build();
1457
1458        $order = new Order(new Price(12));
1459
1460        $context = new DeserializationContext();
1461        $context->setAttribute('target', $order);
1462
1463        $deseralizedOrder = $serializer->deserialize(
1464            $this->getContent('order'),
1465            get_class($order),
1466            $this->getFormat(),
1467            $context
1468        );
1469
1470        self::assertSame($order, $deseralizedOrder);
1471        self::assertEquals(new Order(new Price(12.34)), $deseralizedOrder);
1472        self::assertAttributeInstanceOf('JMS\Serializer\Tests\Fixtures\Price', 'cost', $deseralizedOrder);
1473    }
1474
1475    public function testObjectWithNullableArrays()
1476    {
1477        $object = new ObjectWithEmptyNullableAndEmptyArrays();
1478        self::assertEquals($this->getContent('nullable_arrays'), $this->serializer->serialize($object, $this->getFormat()));
1479    }
1480
1481    /**
1482     * @dataProvider getSerializeNullCases
1483     */
1484    public function testSerializeNullArrayObjectWithExclusionStrategy(bool $serializeNull)
1485    {
1486        $arr = [
1487            new SimpleObject('foo1', 'bar1'),
1488        ];
1489
1490        $serializationContext = SerializationContext::create();
1491        $serializationContext->setSerializeNull($serializeNull);
1492        $serializationContext->setInitialType('array<' . SimpleObject::class . '>');
1493        $serializationContext->addExclusionStrategy(new AlwaysExcludeExclusionStrategy());
1494        self::assertEquals(
1495            $this->getContent('array_objects_nullable'),
1496            $this->serializer->serialize($arr, $this->getFormat(), $serializationContext)
1497        );
1498    }
1499
1500    public function testHandlerInvokedOnPrimitives()
1501    {
1502        $invoked = false;
1503        $this->handlerRegistry->registerHandler(
1504            GraphNavigatorInterface::DIRECTION_SERIALIZATION,
1505            'Virtual',
1506            $this->getFormat(),
1507            function ($visitor, $data) use (&$invoked) {
1508                $invoked = true;
1509                $this->assertEquals('foo', $data);
1510                return null;
1511            }
1512        );
1513
1514        $this->serializer->serialize('foo', $this->getFormat(), null, 'Virtual');
1515        $this->assertTrue($invoked);
1516    }
1517
1518    public function getFirstClassListCollectionsValues()
1519    {
1520        $collection = new FirstClassListCollection([1, 2]);
1521
1522        return [
1523            [[1, 2, 3], $this->getContent('inline_list_collection')],
1524            [[], $this->getContent('inline_empty_list_collection')],
1525            [[1, 'a' => 2], $this->getContent('inline_deserialization_list_collection'), $collection],
1526        ];
1527    }
1528
1529    /**
1530     * @param array $items
1531     * @param array $expected
1532     *
1533     * @dataProvider getFirstClassListCollectionsValues
1534     */
1535    public function testFirstClassListCollections($items, $expected, ?FirstClassListCollection $expectedDeserializatrion = null)
1536    {
1537        $collection = new FirstClassListCollection($items);
1538
1539        self::assertSame($expected, $this->serialize($collection));
1540        self::assertEquals(
1541            $expectedDeserializatrion ?: $collection,
1542            $this->deserialize($expected, get_class($collection))
1543        );
1544    }
1545
1546    public function testInlineCollection()
1547    {
1548        $list = new AuthorsInline(new Author('foo'), new Author('bar'));
1549        self::assertEquals($this->getContent('authors_inline'), $this->serialize($list));
1550        self::assertEquals($list, $this->deserialize($this->getContent('authors_inline'), AuthorsInline::class));
1551    }
1552
1553    public function testGenerator(): void
1554    {
1555        $generator = static function (): \Generator {
1556            yield 'foo' => 'bar';
1557            yield 'bar' => 'foo';
1558        };
1559        $withGenerator = new ObjectWithGenerator($generator());
1560        self::assertEquals($this->getContent('generator'), $this->serialize($withGenerator));
1561
1562        if (!$this->hasDeserializer()) {
1563            return;
1564        }
1565        self::assertEquals(
1566            $withGenerator,
1567            $this->deserialize($this->getContent('generator'), get_class($withGenerator))
1568        );
1569    }
1570
1571    public function testIterator(): void
1572    {
1573        $iterator = new \ArrayIterator([
1574            'foo' => 'bar',
1575            'bar' => 'foo',
1576        ]);
1577        $withIterator = new ObjectWithIterator($iterator);
1578        self::assertEquals($this->getContent('iterator'), $this->serialize($withIterator));
1579
1580        if (!$this->hasDeserializer()) {
1581            return;
1582        }
1583        self::assertEquals(
1584            $withIterator,
1585            $this->deserialize($this->getContent('iterator'), get_class($withIterator))
1586        );
1587    }
1588
1589    public function getSerializeNullCases()
1590    {
1591        return [
1592            [true],
1593            [false],
1594        ];
1595    }
1596
1597    abstract protected function getContent($key);
1598
1599    abstract protected function getFormat();
1600
1601    protected function hasDeserializer()
1602    {
1603        return true;
1604    }
1605
1606    protected function serialize($data, ?Context $context = null)
1607    {
1608        return $this->serializer->serialize($data, $this->getFormat(), $context);
1609    }
1610
1611    protected function deserialize($content, $type, ?Context $context = null)
1612    {
1613        return $this->serializer->deserialize($content, $type, $this->getFormat(), $context);
1614    }
1615
1616    protected function setUp()
1617    {
1618        $this->handlerRegistry = new HandlerRegistry();
1619        $this->handlerRegistry->registerSubscribingHandler(new ConstraintViolationHandler());
1620        $this->handlerRegistry->registerSubscribingHandler(new StdClassHandler());
1621        $this->handlerRegistry->registerSubscribingHandler(new DateHandler());
1622        $this->handlerRegistry->registerSubscribingHandler(new FormErrorHandler(new IdentityTranslator(new MessageSelector())));
1623        $this->handlerRegistry->registerSubscribingHandler(new ArrayCollectionHandler());
1624        $this->handlerRegistry->registerSubscribingHandler(new IteratorHandler());
1625        $this->handlerRegistry->registerHandler(
1626            GraphNavigatorInterface::DIRECTION_SERIALIZATION,
1627            'AuthorList',
1628            $this->getFormat(),
1629            static function (SerializationVisitorInterface $visitor, $object, array $type, Context $context) {
1630                return $visitor->visitArray(iterator_to_array($object), $type);
1631            }
1632        );
1633        $this->handlerRegistry->registerHandler(
1634            GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
1635            'AuthorList',
1636            $this->getFormat(),
1637            static function (DeserializationVisitorInterface $visitor, $data, $type, Context $context) {
1638                $type = [
1639                    'name' => 'array',
1640                    'params' => [
1641                        ['name' => 'integer', 'params' => []],
1642                        ['name' => 'JMS\Serializer\Tests\Fixtures\Author', 'params' => []],
1643                    ],
1644                ];
1645
1646                $elements = $context->getNavigator()->accept($data, $type);
1647                $list = new AuthorList();
1648                foreach ($elements as $author) {
1649                    $list->add($author);
1650                }
1651
1652                return $list;
1653            }
1654        );
1655
1656        $this->dispatcher = new EventDispatcher();
1657        $this->dispatcher->addSubscriber(new DoctrineProxySubscriber());
1658
1659        $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher);
1660        $this->serializer = $builder->build();
1661    }
1662
1663    protected function getField($obj, $name)
1664    {
1665        $ref = new \ReflectionProperty($obj, $name);
1666        $ref->setAccessible(true);
1667
1668        return $ref->getValue($obj);
1669    }
1670
1671    private function setField($obj, $name, $value)
1672    {
1673        $ref = new \ReflectionProperty($obj, $name);
1674        $ref->setAccessible(true);
1675        $ref->setValue($obj, $value);
1676    }
1677}
1678