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