From 41d1789fd60008a694a2ec0677db8c36cfd70404 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Thu, 4 Jul 2024 17:13:56 -0400 Subject: [PATCH 01/14] non disciminated working and disciminated not working. --- phpcs.xml.dist | 4 +- src/Annotation/UnionDiscriminator.php | 28 + src/Exception/PropertyMissingException.php | 9 + .../DeserializationGraphNavigator.php | 24 +- src/Handler/UnionHandler.php | 78 +- src/JsonDeserializationVisitor.php | 27 +- src/Metadata/PropertyMetadata.php | 8 + .../DeserializationVisitorInterface.php | 4 + tests/Fixtures/DiscriminatedAuthor.php | 41 + tests/Fixtures/DiscriminatedComment.php | 45 + .../ComplexDiscriminatedUnion.php | 29 + .../ComplexUnionTypedProperties.php | 24 + tests/Metadata/Driver/DocBlockDriverTest.php | 2 + .../Serializer/BaseSerializationTestCase.php | 3449 +++++++++-------- tests/Serializer/JsonSerializationTest.php | 578 +-- 15 files changed, 2331 insertions(+), 2019 deletions(-) create mode 100644 src/Annotation/UnionDiscriminator.php create mode 100644 src/Exception/PropertyMissingException.php create mode 100644 tests/Fixtures/DiscriminatedAuthor.php create mode 100644 tests/Fixtures/DiscriminatedComment.php create mode 100644 tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php create mode 100644 tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php diff --git a/phpcs.xml.dist b/phpcs.xml.dist index e2ed35f0c..fec7fd4ce 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -68,9 +68,7 @@ - - - + diff --git a/src/Annotation/UnionDiscriminator.php b/src/Annotation/UnionDiscriminator.php new file mode 100644 index 000000000..5d417019c --- /dev/null +++ b/src/Annotation/UnionDiscriminator.php @@ -0,0 +1,28 @@ + */ + public $map = []; + + /** @var string */ + public $field = 'type'; + + public function __construct(array $values = [], string $field = 'type', array $map = []) + { + echo '\n\n\nITS HAPPENING!\n\n\n'; + $this->loadAnnotationParameters(get_defined_vars()); + + } +} diff --git a/src/Exception/PropertyMissingException.php b/src/Exception/PropertyMissingException.php new file mode 100644 index 000000000..1454c83f5 --- /dev/null +++ b/src/Exception/PropertyMissingException.php @@ -0,0 +1,9 @@ +visitor->startVisitingObject($metadata, $object, $type); foreach ($metadata->propertyMetadata as $propertyMetadata) { + $allowsNull = $this->allowsNull($propertyMetadata->type); if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) { continue; } @@ -212,25 +214,43 @@ public function accept($data, ?array $type = null) $this->context->pushPropertyMetadata($propertyMetadata); try { $v = $this->visitor->visitProperty($propertyMetadata, $data); + $this->accessor->setValue($object, $v, $propertyMetadata, $this->context); } catch (NotAcceptableException $e) { if (true === $propertyMetadata->hasDefault) { $cloned = clone $propertyMetadata; $cloned->setter = null; $this->accessor->setValue($object, $cloned->defaultValue, $cloned, $this->context); + } elseif (!$allowsNull && $this->visitor->getRequireAllRequiredProperties()) { + $this->visitor->endVisitingObject($metadata, $data, $type); + throw new PropertyMissingException("Property $propertyMetadata->name is missing from data "); } } - + $this->context->popPropertyMetadata(); } + $rs = $this->visitor->endVisitingObject($metadata, $data, $type); $this->afterVisitingObject($metadata, $rs, $type); - return $rs; } } + private function allowsNull(array $type) { + $allowsNull = false; + if ($type['name'] === 'union') { + foreach($type['params'] as $param) { + if ($param['name'] === 'NULL') { + $allowsNull = true; + } + } + } elseif($type['name'] === 'NULL') { + $allowsNull = true; + } + return $allowsNull; + } + /** * @param mixed $data */ diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index ad7f4339a..995fcb5f5 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -11,10 +11,18 @@ use JMS\Serializer\SerializationContext; use JMS\Serializer\Visitor\DeserializationVisitorInterface; use JMS\Serializer\Visitor\SerializationVisitorInterface; +use JMS\Serializer\Exception\NonVisitableTypeException; +use JMS\Serializer\Exception\PropertyMissingException; final class UnionHandler implements SubscribingHandlerInterface { private static $aliases = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float']; + private bool $requireAllProperties; + + public function __construct(bool $requireAllProperties = false) + { + $this->requireAllProperties = $requireAllProperties; + } /** * {@inheritdoc} @@ -56,40 +64,74 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed if ($data instanceof \SimpleXMLElement) { throw new RuntimeException('XML deserialisation into union types is not supported yet.'); } - - return $this->matchSimpleType($data, $type, $context); + return $this->deserializeType($visitor, $data, $type, $context); } - private function matchSimpleType(mixed $data, array $type, Context $context) + private function deserializeType(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context) { - $dataType = $this->determineType($data, $type, $context->getFormat()); $alternativeName = null; + + foreach ($this->reorderTypes($type)['params'] as $possibleType) { + if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { + continue; + } - if (isset(static::$aliases[$dataType])) { - $alternativeName = static::$aliases[$dataType]; - } - - foreach ($type['params'] as $possibleType) { - if ($possibleType['name'] === $dataType || $possibleType['name'] === $alternativeName) { - return $context->getNavigator()->accept($data, $possibleType); + $propertyMetadata = $context->getMetadataStack()->top(); + var_dump($propertyMetadata); + + + try { + $exists = class_exists($possibleType['name']); + $previousVisitorRequireSetting = $visitor->getRequireAllRequiredProperties(); + if ($this->requireAllProperties) { + $visitor->setRequireAllRequiredProperties($this->requireAllProperties); + } + + $accept = $context->getNavigator()->accept($data, $possibleType); + if ($this->requireAllProperties) { + $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); + } + + return $accept; + } catch (NonVisitableTypeException $e) { + continue; + } catch (PropertyMissingException $e) { + continue; } + } } - private function determineType(mixed $data, array $type, string $format): ?string + private function matchSimpleType(mixed $data, array $type, Context $context) { + $alternativeName = null; + foreach ($type['params'] as $possibleType) { - if ($this->testPrimitive($data, $possibleType['name'], $format)) { - return $possibleType['name']; + if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { + continue; } + try { + $exists = class_exists($possibleType['name']); + if ($this->requireAllProperties) { + } + + $accept = $context->getNavigator()->accept($data, $possibleType); + return $accept; + } catch (NonVisitableTypeException $e) { + continue; + } catch (PropertyMissingException $e) { + continue; + } + } + } - return null; + private function isPrimitiveType(string $type): bool { + return in_array($type, ['int', 'integer', 'float', 'double', 'bool', 'boolean', 'string']); } - private function testPrimitive(mixed $data, string $type, string $format): bool - { - switch ($type) { + private function testPrimitive(mixed $data, string $type, string $format): bool { + switch($type) { case 'integer': case 'int': return (string) (int) $data === (string) $data; diff --git a/src/JsonDeserializationVisitor.php b/src/JsonDeserializationVisitor.php index 625c8c722..76b3bd5ee 100644 --- a/src/JsonDeserializationVisitor.php +++ b/src/JsonDeserializationVisitor.php @@ -33,13 +33,30 @@ final class JsonDeserializationVisitor extends AbstractVisitor implements Deseri */ private $currentObject; + /** + * @var bool + */ + private $requireAllRequiredProperties; + public function __construct( int $options = 0, - int $depth = 512 + int $depth = 512, + bool $requireAllRequiredProperties = false ) { $this->objectStack = new \SplStack(); $this->options = $options; $this->depth = $depth; + $this->requireAllRequiredProperties = $requireAllRequiredProperties; + } + + public function setRequireAllRequiredProperties(bool $requireAllRequiredProperties): void + { + $this->requireAllRequiredProperties = $requireAllRequiredProperties; + } + + public function getRequireAllRequiredProperties(): bool + { + return $this->requireAllRequiredProperties; } /** @@ -151,6 +168,9 @@ public function visitDiscriminatorMapProperty($data, ClassMetadata $metadata): s */ public function startVisitingObject(ClassMetadata $metadata, object $object, array $type): void { + $cur = $this->getCurrentObject() ? get_class($this->getCurrentObject()): 'null'; + $objtype = $object ? get_class($object) : 'null'; + $stacksize = $this->objectStack->count(); $this->setCurrentObject($object); } @@ -194,8 +214,11 @@ public function visitProperty(PropertyMetadata $metadata, $data) public function endVisitingObject(ClassMetadata $metadata, $data, array $type): object { $obj = $this->currentObject; - $this->revertCurrentObject(); + $prevObj = $this->objectStack->top(); + $prevObjType = $prevObj ? get_class($prevObj) : 'null'; + $this->revertCurrentObject(); + $stacksize = $this->objectStack->count(); return $obj; } diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index ff41c949f..5e63d00d8 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -33,6 +33,12 @@ class PropertyMetadata extends BasePropertyMetadata */ public $serializedName; + + /** + * @var array + */ + public $unionDiscriminator; + /** * @var array|null */ @@ -224,6 +230,7 @@ protected function serializeToArray(): array $this->untilVersion, $this->groups, $this->serializedName, + $this->unionDiscriminator, $this->type, $this->xmlCollection, $this->xmlCollectionInline, @@ -258,6 +265,7 @@ protected function unserializeFromArray(array $data): void $this->untilVersion, $this->groups, $this->serializedName, + $this->unionDiscriminator, $this->type, $this->xmlCollection, $this->xmlCollectionInline, diff --git a/src/Visitor/DeserializationVisitorInterface.php b/src/Visitor/DeserializationVisitorInterface.php index fd16dc483..d924a9dbb 100644 --- a/src/Visitor/DeserializationVisitorInterface.php +++ b/src/Visitor/DeserializationVisitorInterface.php @@ -94,4 +94,8 @@ public function endVisitingObject(ClassMetadata $metadata, $data, array $type): * @return mixed */ public function getResult($data); + + public function setRequireAllRequiredProperties(bool $requireAllRequiredProperties): void; + + public function getRequireAllRequiredProperties(): bool; } diff --git a/tests/Fixtures/DiscriminatedAuthor.php b/tests/Fixtures/DiscriminatedAuthor.php new file mode 100644 index 000000000..7da5c1a91 --- /dev/null +++ b/tests/Fixtures/DiscriminatedAuthor.php @@ -0,0 +1,41 @@ +name = $name; + } + + public function getName() + { + return $this->name; + } + + public function getType() + { + return $this->type; + } +} diff --git a/tests/Fixtures/DiscriminatedComment.php b/tests/Fixtures/DiscriminatedComment.php new file mode 100644 index 000000000..a2ac3505e --- /dev/null +++ b/tests/Fixtures/DiscriminatedComment.php @@ -0,0 +1,45 @@ +author = $author; + $this->text = $text; + } + + public function getAuthor() + { + return $this->author; + } + + public function getType() + { + return $this->type; + } +} diff --git a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php new file mode 100644 index 000000000..9466162bc --- /dev/null +++ b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php @@ -0,0 +1,29 @@ + 'JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor', + 'comment' => 'JMS\Serializer\Tests\Fixtures\DiscriminatedComment', + ])] + private DiscriminatedAuthor|DiscriminatedComment $data; + + public function __construct($data) + { + $this->data = $data; + } + + public function getData(): Author|Comment + { + return $this->data; + } +} + diff --git a/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php b/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php new file mode 100644 index 000000000..6f6c007a1 --- /dev/null +++ b/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php @@ -0,0 +1,24 @@ +data = $data; + } + + public function getData(): Author|Comment + { + return $this->data; + } +} + diff --git a/tests/Metadata/Driver/DocBlockDriverTest.php b/tests/Metadata/Driver/DocBlockDriverTest.php index b772333d6..0640e3140 100644 --- a/tests/Metadata/Driver/DocBlockDriverTest.php +++ b/tests/Metadata/Driver/DocBlockDriverTest.php @@ -412,4 +412,6 @@ public function testAlternativeNames() $m->propertyMetadata['boolean']->type, ); } + + } diff --git a/tests/Serializer/BaseSerializationTestCase.php b/tests/Serializer/BaseSerializationTestCase.php index 52626e14e..b3ed4ba78 100644 --- a/tests/Serializer/BaseSerializationTestCase.php +++ b/tests/Serializer/BaseSerializationTestCase.php @@ -180,131 +180,131 @@ abstract class BaseSerializationTestCase extends TestCase protected $objectConstructor; protected $accessorStrategy; - public function testSerializeNullArray() - { - $arr = ['foo' => 'bar', 'baz' => null, null]; - - self::assertEquals( - static::getContent('nullable'), - $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), - ); - } - - public function testSerializeNullRoot() - { - $context = SerializationContext::create() - ->setAttribute('allows_root_null', true); - - self::assertEquals( - static::getContent('nullable_root'), - $this->serializer->serialize(null, $this->getFormat(), $context), - ); - } - - public function testNoMetadataNeededWhenDeSerializingNotUsedProperty() - { - $object = $this->deserialize(static::getContent('ParentNoMetadataChildObject'), ParentNoMetadataChildObject::class); - assert($object instanceof ParentNoMetadataChildObject); - - self::assertSame('John', $object->bar); - self::assertNull($object->foo); - } - - public function testDeserializeObjectWithMissingTypedArrayProp() - { - $dObj = $this->serializer->deserialize( - static::getContent('empty_object'), - ObjectWithTypedArraySetter::class, - $this->getFormat(), - ); - assert($dObj instanceof ObjectWithTypedArraySetter); - - self::assertInstanceOf(ObjectWithTypedArraySetter::class, $dObj); - - self::assertSame([], $dObj->getEmpty()); - } - - public function testSerializeNullArrayExcludingNulls() - { - $arr = ['foo' => 'bar', 'baz' => null, null]; - - self::assertEquals( - static::getContent('nullable_skip'), - $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(false)), - ); - } - - public function testObjectUsingTypeCasting() - { - $typeAliasing = new ObjectUsingTypeCasting(); - $typeAliasing->asString = new ObjectWithToString('8'); - - self::assertEquals( - static::getContent('type_casting'), - $this->serialize($typeAliasing), - ); - } - - public function testSerializeNullObject() - { - $obj = new ObjectWithNullProperty('foo', 'bar'); - - self::assertEquals( - static::getContent('simple_object_nullable'), - $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), - ); - } - - public function testDeserializeNullObject() - { - if (!$this->hasDeserializer()) { - $this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat())); - } - - $obj = new ObjectWithNullProperty('foo', 'bar'); - - $dObj = $this->serializer->deserialize( - static::getContent('simple_object_nullable'), - ObjectWithNullProperty::class, - $this->getFormat(), - ); - assert($dObj instanceof ObjectWithNullProperty); - - self::assertEquals($obj, $dObj); - self::assertNull($dObj->getNullProperty()); - } - - /** - * @dataProvider getTypes - */ - #[DataProvider('getTypes')] - public function testNull($type) - { - if ($this->hasDeserializer()) { - self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); - } - - // this is the default, but we want to be explicit here - $context = SerializationContext::create()->setSerializeNull(false); - - $this->expectException(NotAcceptableException::class); - - $this->serialize(null, $context); - } - - /** - * @dataProvider getTypes - */ - #[DataProvider('getTypes')] - public function testNullAllowed($type) - { - $context = SerializationContext::create()->setSerializeNull(true); - self::assertEquals(static::getContent('null'), $this->serialize(null, $context), $type); - - if ($this->hasDeserializer()) { - self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); - } - } + // public function testSerializeNullArray() + // { + // $arr = ['foo' => 'bar', 'baz' => null, null]; + + // self::assertEquals( + // static::getContent('nullable'), + // $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), + // ); + // } + + // public function testSerializeNullRoot() + // { + // $context = SerializationContext::create() + // ->setAttribute('allows_root_null', true); + + // self::assertEquals( + // static::getContent('nullable_root'), + // $this->serializer->serialize(null, $this->getFormat(), $context), + // ); + // } + + // public function testNoMetadataNeededWhenDeSerializingNotUsedProperty() + // { + // $object = $this->deserialize(static::getContent('ParentNoMetadataChildObject'), ParentNoMetadataChildObject::class); + // assert($object instanceof ParentNoMetadataChildObject); + + // self::assertSame('John', $object->bar); + // self::assertNull($object->foo); + // } + + // public function testDeserializeObjectWithMissingTypedArrayProp() + // { + // $dObj = $this->serializer->deserialize( + // static::getContent('empty_object'), + // ObjectWithTypedArraySetter::class, + // $this->getFormat(), + // ); + // assert($dObj instanceof ObjectWithTypedArraySetter); + + // self::assertInstanceOf(ObjectWithTypedArraySetter::class, $dObj); + + // self::assertSame([], $dObj->getEmpty()); + // } + + // public function testSerializeNullArrayExcludingNulls() + // { + // $arr = ['foo' => 'bar', 'baz' => null, null]; + + // self::assertEquals( + // static::getContent('nullable_skip'), + // $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(false)), + // ); + // } + + // public function testObjectUsingTypeCasting() + // { + // $typeAliasing = new ObjectUsingTypeCasting(); + // $typeAliasing->asString = new ObjectWithToString('8'); + + // self::assertEquals( + // static::getContent('type_casting'), + // $this->serialize($typeAliasing), + // ); + // } + + // public function testSerializeNullObject() + // { + // $obj = new ObjectWithNullProperty('foo', 'bar'); + + // self::assertEquals( + // static::getContent('simple_object_nullable'), + // $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), + // ); + // } + + // public function testDeserializeNullObject() + // { + // if (!$this->hasDeserializer()) { + // $this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat())); + // } + + // $obj = new ObjectWithNullProperty('foo', 'bar'); + + // $dObj = $this->serializer->deserialize( + // static::getContent('simple_object_nullable'), + // ObjectWithNullProperty::class, + // $this->getFormat(), + // ); + // assert($dObj instanceof ObjectWithNullProperty); + + // self::assertEquals($obj, $dObj); + // self::assertNull($dObj->getNullProperty()); + // } + + // /** + // * @dataProvider getTypes + // */ + // #[DataProvider('getTypes')] + // public function testNull($type) + // { + // if ($this->hasDeserializer()) { + // self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); + // } + + // // this is the default, but we want to be explicit here + // $context = SerializationContext::create()->setSerializeNull(false); + + // $this->expectException(NotAcceptableException::class); + + // $this->serialize(null, $context); + // } + + // /** + // * @dataProvider getTypes + // */ + // #[DataProvider('getTypes')] + // public function testNullAllowed($type) + // { + // $context = SerializationContext::create()->setSerializeNull(true); + // self::assertEquals(static::getContent('null'), $this->serialize(null, $context), $type); + + // if ($this->hasDeserializer()) { + // self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); + // } + // } public static function getTypes() { @@ -319,166 +319,166 @@ public static function getTypes() ]; } - public function testString() - { - self::assertEquals(static::getContent('string'), $this->serialize('foo')); + // public function testString() + // { + // self::assertEquals(static::getContent('string'), $this->serialize('foo')); - if ($this->hasDeserializer()) { - self::assertEquals('foo', $this->deserialize(static::getContent('string'), 'string')); - } - } + // if ($this->hasDeserializer()) { + // self::assertEquals('foo', $this->deserialize(static::getContent('string'), 'string')); + // } + // } - public function testExcludeIfOnClass() - { - $accountNotExpired = new PersonAccount(); - $accountNotExpired->name = 'Not expired account'; - $accountNotExpired->expired = false; + // public function testExcludeIfOnClass() + // { + // $accountNotExpired = new PersonAccount(); + // $accountNotExpired->name = 'Not expired account'; + // $accountNotExpired->expired = false; - $accountExpired = new PersonAccount(); - $accountExpired->name = 'Expired account'; - $accountExpired->expired = true; + // $accountExpired = new PersonAccount(); + // $accountExpired->name = 'Expired account'; + // $accountExpired->expired = true; - $accounts = [$accountExpired, $accountNotExpired]; + // $accounts = [$accountExpired, $accountNotExpired]; - $language = new ExpressionLanguage(); + // $language = new ExpressionLanguage(); - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - $serializer = $builder->build(); + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + // $serializer = $builder->build(); - $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); - $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); + // $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); + // $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); - $this->assertEquals(1, count($deserialized)); - $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); - } + // $this->assertEquals(1, count($deserialized)); + // $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); + // } - public function testEnumDisabledByDefault() - { - if (PHP_VERSION_ID < 80100) { - self::markTestSkipped('No ENUM support'); - } + // public function testEnumDisabledByDefault() + // { + // if (PHP_VERSION_ID < 80100) { + // self::markTestSkipped('No ENUM support'); + // } - $builder = SerializerBuilder::create(); - $serializer = $builder->build(); - $o = new ObjectWithAutoDetectEnums(); - $serialized = $serializer->serialize($o, $this->getFormat()); + // $builder = SerializerBuilder::create(); + // $serializer = $builder->build(); + // $o = new ObjectWithAutoDetectEnums(); + // $serialized = $serializer->serialize($o, $this->getFormat()); - self::assertEquals(static::getContent('object_with_enums_disabled'), $serialized); - } + // self::assertEquals(static::getContent('object_with_enums_disabled'), $serialized); + // } - public function testEnum() - { - if (PHP_VERSION_ID < 80100) { - self::markTestSkipped('No ENUM support'); - } + // public function testEnum() + // { + // if (PHP_VERSION_ID < 80100) { + // self::markTestSkipped('No ENUM support'); + // } - $o = new ObjectWithEnums(); + // $o = new ObjectWithEnums(); - $serialized = $this->serialize($o); + // $serialized = $this->serialize($o); - self::assertEquals(static::getContent('object_with_enums'), $serialized); + // self::assertEquals(static::getContent('object_with_enums'), $serialized); - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize($serialized, ObjectWithEnums::class); - self::assertEquals($o, $deserialized); - } - } + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize($serialized, ObjectWithEnums::class); + // self::assertEquals($o, $deserialized); + // } + // } - public function testEnumAutoDetectArrayOfEnums() - { - if (PHP_VERSION_ID < 80100) { - self::markTestSkipped('No ENUM support'); - } + // public function testEnumAutoDetectArrayOfEnums() + // { + // if (PHP_VERSION_ID < 80100) { + // self::markTestSkipped('No ENUM support'); + // } - $o = new ObjectWithAutoDetectEnums(); + // $o = new ObjectWithAutoDetectEnums(); - $serialized = $this->serialize($o); + // $serialized = $this->serialize($o); - self::assertEquals(static::getContent('object_with_autodetect_enums'), $serialized); - } + // self::assertEquals(static::getContent('object_with_autodetect_enums'), $serialized); + // } - public function testExcludeIfOnClassWithParent() - { - $accountNotExpired = new PersonAccountWithParent(); - $accountNotExpired->name = 'Not expired account'; - $accountNotExpired->expired = false; + // public function testExcludeIfOnClassWithParent() + // { + // $accountNotExpired = new PersonAccountWithParent(); + // $accountNotExpired->name = 'Not expired account'; + // $accountNotExpired->expired = false; - $accountExpired = new PersonAccountWithParent(); - $accountExpired->name = 'Expired account'; - $accountExpired->expired = true; + // $accountExpired = new PersonAccountWithParent(); + // $accountExpired->name = 'Expired account'; + // $accountExpired->expired = true; - $accounts = [$accountNotExpired, $accountExpired]; + // $accounts = [$accountNotExpired, $accountExpired]; - $language = new ExpressionLanguage(); + // $language = new ExpressionLanguage(); - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - $serializer = $builder->build(); + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + // $serializer = $builder->build(); - $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); - $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); + // $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); + // $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); - $this->assertEquals(1, count($deserialized)); - $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); - } + // $this->assertEquals(1, count($deserialized)); + // $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); + // } - public function testExcludeIfOnParentClass() - { - $accountNotExpired = new PersonAccountOnParent(); - $accountNotExpired->name = 'Not expired account'; - $accountNotExpired->expired = false; + // public function testExcludeIfOnParentClass() + // { + // $accountNotExpired = new PersonAccountOnParent(); + // $accountNotExpired->name = 'Not expired account'; + // $accountNotExpired->expired = false; - $accountExpired = new PersonAccountOnParent(); - $accountExpired->name = 'Expired account'; - $accountExpired->expired = true; + // $accountExpired = new PersonAccountOnParent(); + // $accountExpired->name = 'Expired account'; + // $accountExpired->expired = true; - $accounts = [$accountNotExpired, $accountExpired]; + // $accounts = [$accountNotExpired, $accountExpired]; - $language = new ExpressionLanguage(); + // $language = new ExpressionLanguage(); - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - $serializer = $builder->build(); + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + // $serializer = $builder->build(); - $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountOnParent::class)); - $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountOnParent::class), $this->getFormat()); + // $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountOnParent::class)); + // $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountOnParent::class), $this->getFormat()); - $this->assertEquals(1, count($deserialized)); - $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); - } + // $this->assertEquals(1, count($deserialized)); + // $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); + // } - public function testExpressionExclusionNotConfigured() - { - $person = new PersonSecret(); - $person->gender = 'f'; - $person->name = 'mike'; + // public function testExpressionExclusionNotConfigured() + // { + // $person = new PersonSecret(); + // $person->gender = 'f'; + // $person->name = 'mike'; - $this->expectException(ExpressionLanguageRequiredException::class); - $this->expectExceptionMessage('To use conditional exclude/expose in JMS\Serializer\Tests\Fixtures\PersonSecret you must configure the expression language.'); + // $this->expectException(ExpressionLanguageRequiredException::class); + // $this->expectExceptionMessage('To use conditional exclude/expose in JMS\Serializer\Tests\Fixtures\PersonSecret you must configure the expression language.'); - $this->serialize($person); - } + // $this->serialize($person); + // } - public function testExpressionExclusionConfiguredWithDisjunctStrategy() - { - $person = new PersonSecret(); - $person->gender = 'f'; - $person->name = 'mike'; + // public function testExpressionExclusionConfiguredWithDisjunctStrategy() + // { + // $person = new PersonSecret(); + // $person->gender = 'f'; + // $person->name = 'mike'; - $language = new ExpressionLanguage(); - $language->addFunction(new ExpressionFunction('show_data', static function () { - return 'true'; - }, static function () { - return true; - })); + // $language = new ExpressionLanguage(); + // $language->addFunction(new ExpressionFunction('show_data', static function () { + // return 'true'; + // }, static function () { + // return true; + // })); - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - $serializer = $builder->build(); + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + // $serializer = $builder->build(); - self::assertEquals(static::getContent('person_secret_hide'), $serializer->serialize($person, $this->getFormat())); - } + // self::assertEquals(static::getContent('person_secret_hide'), $serializer->serialize($person, $this->getFormat())); + // } public static function expressionFunctionProvider() { @@ -561,1384 +561,1384 @@ public static function expressionFunctionProvider() * * @dataProvider expressionFunctionProvider */ - #[DataProvider('expressionFunctionProvider')] - public function testExpressionExclusion($person, ExpressionFunction $function, $json) - { - $language = new ExpressionLanguage(); - $language->addFunction($function); - - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - $serializer = $builder->build(); - - self::assertEquals(static::getContent($json), $serializer->serialize($person, $this->getFormat())); - } - - /** - * @dataProvider getBooleans - */ - #[DataProvider('getBooleans')] - public function testBooleans($strBoolean, $boolean) - { - self::assertEquals(static::getContent('boolean_' . $strBoolean), $this->serialize($boolean)); + // #[DataProvider('expressionFunctionProvider')] + // public function testExpressionExclusion($person, ExpressionFunction $function, $json) + // { + // $language = new ExpressionLanguage(); + // $language->addFunction($function); + + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + // $serializer = $builder->build(); + + // self::assertEquals(static::getContent($json), $serializer->serialize($person, $this->getFormat())); + // } + + // /** + // * @dataProvider getBooleans + // */ + // #[DataProvider('getBooleans')] + // public function testBooleans($strBoolean, $boolean) + // { + // self::assertEquals(static::getContent('boolean_' . $strBoolean), $this->serialize($boolean)); + + // if ($this->hasDeserializer()) { + // self::assertSame($boolean, $this->deserialize(static::getContent('boolean_' . $strBoolean), 'boolean')); + // } + // } + + // public static function getBooleans() + // { + // return [['true', true], ['false', false]]; + // } + + // /** + // * @dataProvider getNumerics + // */ + // #[DataProvider('getNumerics')] + // public function testNumerics($key, $value, $type) + // { + // self::assertSame(static::getContent($key), $this->serialize($value)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($value, $this->deserialize(static::getContent($key), $type)); + // } + // } + + // public static function getNumerics() + // { + // return [ + // ['integer', 1, 'integer'], + // ['float', 4.533, 'double'], + // ['float', 4.533, 'float'], + // ['float_trailing_zero', 1.0, 'double'], + // ['float_trailing_zero', 1.0, 'float'], + // ]; + // } + + // public function testSimpleInternalObject() + // { + // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + // $builder->setMetadataDirs([ + // 'JMS\Serializer\Tests\Fixtures' => __DIR__ . '/metadata/SimpleInternalObject', + // '' => __DIR__ . '/metadata/SimpleInternalObject', + // ]); + + // $this->serializer = $builder->build(); + + // $obj = new SimpleInternalObject('foo', 'bar'); + + // self::assertEquals(static::getContent('simple_object'), $this->serialize($obj)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); + // } + // } + + // public function testSimpleObject() + // { + // self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar'))); + + // if ($this->hasDeserializer()) { + // self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); + // } + // } + + // public function testSimpleObjectStaticProp() + // { + // self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObjectWithStaticProp('foo', 'bar'))); + + // if ($this->hasDeserializer()) { + // self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); + // } + // } + + // public function testArrayStrings() + // { + // $data = ['foo', 'bar']; + // self::assertEquals(static::getContent('array_strings'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('array_strings'), 'array')); + // } + // } + + // public function testArrayBooleans() + // { + // $data = [true, false]; + // self::assertEquals(static::getContent('array_booleans'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('array_booleans'), 'array')); + // } + // } + + // public function testArrayIntegers() + // { + // $data = [1, 3, 4]; + // self::assertEquals(static::getContent('array_integers'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('array_integers'), 'array')); + // } + // } + + // public function testArrayEmpty() + // { + // if ('xml' === $this->getFormat()) { + // $this->markTestSkipped('XML can\'t be tested for empty array'); + // } + + // $data = ['array' => []]; + // self::assertEquals(static::getContent('array_empty'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('array_empty'), 'array')); + // } + // } + + // public function testArrayFloats() + // { + // $data = [1.34, 3.0, 6.42]; + // self::assertEquals(static::getContent('array_floats'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('array_floats'), 'array')); + // } + // } + + // public function testArrayObjects() + // { + // $data = [new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')]; + // self::assertEquals(static::getContent('array_objects'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('array_objects'), 'array')); + // } + // } + + // public function testArrayListAndMapDifference() + // { + // $arrayData = [0 => 1, 2 => 2, 3 => 3]; // Misses key 1 + // $data = new ObjectWithIntListAndIntMap($arrayData, $arrayData); + + // self::assertEquals(static::getContent('array_list_and_map_difference'), $this->serialize($data)); + // } + + // public function testList(): void + // { + // if ('xml' === $this->getFormat()) { + // $this->markTestSkipped('XML can\'t be tested for list without value type'); + // } + + // $data = [1, 3, 4]; + // self::assertEquals(static::getContent('list'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('list'), 'list')); + // } + // } + + // public function testListEmpty(): void + // { + // if ('xml' === $this->getFormat()) { + // $this->markTestSkipped('XML can\'t be tested for empty list'); + // } + + // $data = []; + // self::assertEquals(static::getContent('list_empty'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('list_empty'), 'list')); + // } + // } + + // public function testListIntegers(): void + // { + // $data = [1, 3, 4]; + // self::assertEquals(static::getContent('list_integers'), $this->serialize($data)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($data, $this->deserialize(static::getContent('list_integers'), 'list')); + // } + // } + + // public function testCustomDateObject() + // { + // $data = new DateTimeContainer(new DateTimeCustomObject('2021-09-07')); + + // self::assertEquals(static::getContent('custom_datetimeinterface'), $this->serialize($data)); + // } + + // public function testDateTimeArrays() + // { + // $data = [ + // new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + // new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), + // ]; + + // $object = new DateTimeArraysObject($data, $data); + // $serializedObject = $this->serialize($object); + + // self::assertEquals(static::getContent('array_datetimes_object'), $serializedObject); + + // if ($this->hasDeserializer()) { + // $deserializedObject = $this->deserialize(static::getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject'); + // assert($deserializedObject instanceof DateTimeArraysObject); + + // /** 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. */ + // foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) { + // $dateTime->setTimezone(new \DateTimeZone('UTC')); + // } + + // foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) { + // $dateTime->setTimezone(new \DateTimeZone('UTC')); + // } + + // self::assertEquals($object, $deserializedObject); + // } + // } + + // public function testNamedDateTimeArrays() + // { + // $data = [ + // new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + // new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), + // ]; + + // $object = new NamedDateTimeArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); + // $serializedObject = $this->serialize($object); + + // self::assertEquals(static::getContent('array_named_datetimes_object'), $serializedObject); + + // if ($this->hasDeserializer()) { + // // skip XML deserialization + // if ('xml' === $this->getFormat()) { + // return; + // } + + // $deserializedObject = $this->deserialize(static::getContent('array_named_datetimes_object'), NamedDateTimeArraysObject::class); + // assert($deserializedObject instanceof NamedDateTimeArraysObject); + + // /** 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. */ + // foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { + // $dateTime->setTimezone(new \DateTimeZone('UTC')); + // } + + // self::assertEquals($object, $deserializedObject); + // } + // } + + // public function testNamedDateTimeImmutableArrays() + // { + // $data = [ + // new \DateTimeImmutable('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + // new \DateTimeImmutable('2016-12-05 00:00:00', new \DateTimeZone('UTC')), + // ]; + + // $object = new NamedDateTimeImmutableArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); + // $serializedObject = $this->serialize($object); + + // self::assertEquals(static::getContent('array_named_datetimeimmutables_object'), $serializedObject); + + // if ($this->hasDeserializer()) { + // if ('xml' === $this->getFormat()) { + // $this->markTestSkipped('XML deserialization does not support key-val pairs mode'); + // } + + // $deserializedObject = $this->deserialize(static::getContent('array_named_datetimeimmutables_object'), NamedDateTimeImmutableArraysObject::class); + // assert($deserializedObject instanceof NamedDateTimeImmutableArraysObject); + + // /** 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. */ + // foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { + // $dateTime->setTimezone(new \DateTimeZone('UTC')); + // } + + // self::assertEquals($object, $deserializedObject); + // } + // } + + // public function testArrayMixed() + // { + // self::assertEquals(static::getContent('array_mixed'), $this->serialize(['foo', 1, true, new SimpleObject('foo', 'bar'), [1, 3, true]])); + // } + + // /** + // * @dataProvider getDateTime + // * @group datetime + // */ + // #[DataProvider('getDateTime')] + // public function testDateTime($key, $value, $type) + // { + // self::assertEquals(static::getContent($key), $this->serialize($value)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent($key), $type); + + // self::assertIsObject($deserialized); + // self::assertInstanceOf(get_class($value), $deserialized); + // self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); + // } + // } - if ($this->hasDeserializer()) { - self::assertSame($boolean, $this->deserialize(static::getContent('boolean_' . $strBoolean), 'boolean')); - } - } - - public static function getBooleans() + public static function getDateTime() { - return [['true', true], ['false', false]]; + return [ + ['date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'], + ['date_time_multi_format', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), "DateTime<'Y-m-d', '', ['Y-m-d','Y-m-d\TH:i:sP']>"], + ]; } /** - * @dataProvider getNumerics + * @dataProvider getDateTimeImmutable + * @group datetime */ - #[DataProvider('getNumerics')] - public function testNumerics($key, $value, $type) - { - self::assertSame(static::getContent($key), $this->serialize($value)); + // #[DataProvider('getDateTimeImmutable')] + // public function testDateTimeImmutable($key, $value, $type) + // { + // self::assertEquals(static::getContent($key), $this->serialize($value)); - if ($this->hasDeserializer()) { - self::assertEquals($value, $this->deserialize(static::getContent($key), $type)); - } - } + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent($key), $type); + + // self::assertIsObject($deserialized); + // self::assertInstanceOf(get_class($value), $deserialized); + // self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); + // } + // } - public static function getNumerics() + public static function getDateTimeImmutable() { return [ - ['integer', 1, 'integer'], - ['float', 4.533, 'double'], - ['float', 4.533, 'float'], - ['float_trailing_zero', 1.0, 'double'], - ['float_trailing_zero', 1.0, 'float'], + ['date_time_immutable', new \DateTimeImmutable('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTimeImmutable'], ]; } - public function testSimpleInternalObject() - { - $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - $builder->setMetadataDirs([ - 'JMS\Serializer\Tests\Fixtures' => __DIR__ . '/metadata/SimpleInternalObject', - '' => __DIR__ . '/metadata/SimpleInternalObject', - ]); - - $this->serializer = $builder->build(); - - $obj = new SimpleInternalObject('foo', 'bar'); - - self::assertEquals(static::getContent('simple_object'), $this->serialize($obj)); - - if ($this->hasDeserializer()) { - self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); - } - } - - public function testSimpleObject() - { - self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar'))); - - if ($this->hasDeserializer()) { - self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); - } - } - - public function testSimpleObjectStaticProp() - { - self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObjectWithStaticProp('foo', 'bar'))); - - if ($this->hasDeserializer()) { - self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); - } - } - - public function testArrayStrings() - { - $data = ['foo', 'bar']; - self::assertEquals(static::getContent('array_strings'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('array_strings'), 'array')); - } - } - - public function testArrayBooleans() - { - $data = [true, false]; - self::assertEquals(static::getContent('array_booleans'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('array_booleans'), 'array')); - } - } - - public function testArrayIntegers() - { - $data = [1, 3, 4]; - self::assertEquals(static::getContent('array_integers'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('array_integers'), 'array')); - } - } - - public function testArrayEmpty() - { - if ('xml' === $this->getFormat()) { - $this->markTestSkipped('XML can\'t be tested for empty array'); - } - - $data = ['array' => []]; - self::assertEquals(static::getContent('array_empty'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('array_empty'), 'array')); - } - } - - public function testArrayFloats() - { - $data = [1.34, 3.0, 6.42]; - self::assertEquals(static::getContent('array_floats'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('array_floats'), 'array')); - } - } - - public function testArrayObjects() - { - $data = [new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')]; - self::assertEquals(static::getContent('array_objects'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('array_objects'), 'array')); - } - } - - public function testArrayListAndMapDifference() - { - $arrayData = [0 => 1, 2 => 2, 3 => 3]; // Misses key 1 - $data = new ObjectWithIntListAndIntMap($arrayData, $arrayData); - - self::assertEquals(static::getContent('array_list_and_map_difference'), $this->serialize($data)); - } - - public function testList(): void - { - if ('xml' === $this->getFormat()) { - $this->markTestSkipped('XML can\'t be tested for list without value type'); - } - - $data = [1, 3, 4]; - self::assertEquals(static::getContent('list'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('list'), 'list')); - } - } - - public function testListEmpty(): void - { - if ('xml' === $this->getFormat()) { - $this->markTestSkipped('XML can\'t be tested for empty list'); - } - - $data = []; - self::assertEquals(static::getContent('list_empty'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('list_empty'), 'list')); - } - } - - public function testListIntegers(): void - { - $data = [1, 3, 4]; - self::assertEquals(static::getContent('list_integers'), $this->serialize($data)); - - if ($this->hasDeserializer()) { - self::assertEquals($data, $this->deserialize(static::getContent('list_integers'), 'list')); - } - } - - public function testCustomDateObject() - { - $data = new DateTimeContainer(new DateTimeCustomObject('2021-09-07')); + // public function testTimestamp() + // { + // $value = new Timestamp(new \DateTime('2016-02-11 00:00:00', new \DateTimeZone('UTC'))); + // self::assertEquals(static::getContent('timestamp'), $this->serialize($value)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('timestamp'), Timestamp::class); + // self::assertEquals($value, $deserialized); + // self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); + + // $deserialized = $this->deserialize(static::getContent('timestamp_prev'), Timestamp::class); + // self::assertEquals($value, $deserialized); + // self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); + // } + // } + + // public function testDateInterval() + // { + // $duration = new \DateInterval('PT45M'); + + // self::assertEquals(static::getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat())); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('date_interval'), \DateInterval::class); + // self::assertEquals($duration, $deserialized); + // self::assertEquals($duration->i, $deserialized->i); + // } + // } - self::assertEquals(static::getContent('custom_datetimeinterface'), $this->serialize($data)); - } + // public function testBlogPost() + // { + // $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')); + // $post->addComment($comment = new Comment($author, 'foo')); + + // $post->addTag($tag1 = new Tag('tag1')); + // $post->addTag($tag2 = new Tag('tag2')); + + // self::assertEquals(static::getContent('blog_post'), $this->serialize($post)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('blog_post'), get_class($post)); + // self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); + // self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); + // self::assertFalse($this->getField($deserialized, 'published')); + // self::assertFalse($this->getField($deserialized, 'reviewed')); + // self::assertSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', $this->getField($deserialized, 'etag')); + // self::assertEquals(new ArrayCollection([$comment]), $this->getField($deserialized, 'comments')); + // self::assertEquals([$comment], $this->getField($deserialized, 'comments2')); + // self::assertEquals($author, $this->getField($deserialized, 'author')); + // self::assertEquals([$tag1, $tag2], $this->getField($deserialized, 'tag')); + // } + // } + + // public function testDeserializingNull() + // { + // $objectConstructor = new InitializedBlogPostConstructor(); + + // $builder = SerializerBuilder::create(); + // $builder->setObjectConstructor($objectConstructor); + // $this->serializer = $builder->build(); + + // $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')); + + // $this->setField($post, 'author', null); + // $this->setField($post, 'publisher', null); + + // self::assertEquals(static::getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true))); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create()); + + // self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); + // self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); + // self::assertFalse($this->getField($deserialized, 'published')); + // self::assertFalse($this->getField($deserialized, 'reviewed')); + // self::assertEquals(new ArrayCollection(), $this->getField($deserialized, 'comments')); + // self::assertEquals(null, $this->getField($deserialized, 'author')); + // } + // } + + // public function testSymfonyUid() + // { + // $uid = Uuid::fromString('66b3177c-e03b-4a22-9dee-ddd7d37a04d5'); + + // self::assertEquals(static::getContent('uid'), $this->serialize($uid)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('uid'), UuidV4::class); + + // self::assertInstanceOf(UuidV4::class, $deserialized); + // self::assertTrue($uid->equals($deserialized)); + // } + // } + + // public function testExpressionAuthor() + // { + // $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); + + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator($evaluator); + // $serializer = $builder->build(); + + // $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); + // self::assertEquals(static::getContent('author_expression'), $serializer->serialize($author, $this->getFormat())); + // } + + // public function testExpressionAuthorWithContextVars() + // { + // $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); + + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator($evaluator); + // $serializer = $builder->build(); + + // $author = new AuthorExpressionAccessContext('Ruud'); + // self::assertEquals(static::getContent('author_expression_context'), $serializer->serialize($author, $this->getFormat())); + // } + + // public function testExpressionAccessorStrategNotEnabled() + // { + // $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); + + // $this->expectException(ExpressionLanguageRequiredException::class); + // $this->expectExceptionMessage('The property firstName on JMS\Serializer\Tests\Fixtures\AuthorExpressionAccess requires the expression accessor strategy to be enabled.'); + + // $this->serialize($author); + // } + + // public function testReadOnly() + // { + // $author = new AuthorReadOnly(123, 'Ruud Kamphuis'); + // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + // self::assertNull($this->getField($deserialized, 'id')); + // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + // } + // } + + // public function testDeprecatedReadOnly() + // { + // $author = new AuthorDeprecatedReadOnly(123, 'Ruud Kamphuis'); + // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + // self::assertNull($this->getField($deserialized, 'id')); + // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + // } + // } + + // public function testReadOnlyClass() + // { + // $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis'); + // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + // self::assertNull($this->getField($deserialized, 'id')); + // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + // } + // } + + // public function testDeprecatedReadOnlyClass() + // { + // $author = new AuthorDeprecatedReadOnlyPerClass(123, 'Ruud Kamphuis'); + // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + // self::assertNull($this->getField($deserialized, 'id')); + // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + // } + // } + + // public function testPrice() + // { + // $price = new Price(3); + // self::assertEquals(static::getContent('price'), $this->serialize($price)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('price'), get_class($price)); + // self::assertEquals(3, $this->getField($deserialized, 'price')); + // } + // } + + // public function testOrder() + // { + // $order = new Order(new Price(12.34)); + // self::assertEquals(static::getContent('order'), $this->serialize($order)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($order, $this->deserialize(static::getContent('order'), get_class($order))); + // } + // } + + // public function testCurrencyAwarePrice() + // { + // $price = new CurrencyAwarePrice(2.34); + // self::assertEquals(static::getContent('currency_aware_price'), $this->serialize($price)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($price, $this->deserialize(static::getContent('currency_aware_price'), get_class($price))); + // } + // } + + // public function testOrderWithCurrencyAwarePrice() + // { + // $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23)); + // self::assertEquals(static::getContent('order_with_currency_aware_price'), $this->serialize($order)); + + // if ($this->hasDeserializer()) { + // self::assertEquals($order, $this->deserialize(static::getContent('order_with_currency_aware_price'), get_class($order))); + // } + // } + + // public function testInline() + // { + // $inline = new InlineParent(); + + // $result = $this->serialize($inline); + // self::assertEquals(static::getContent('inline'), $result); + + // if ($this->hasDeserializer()) { + // self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); + // } + // } + + // public function testInlineEmptyChild() + // { + // $inline = new InlineParentWithEmptyChild(new InlineChildEmpty()); + // $result = $this->serialize($inline); + // self::assertEquals(static::getContent('inline_child_empty'), $result); + // if ($this->hasDeserializer()) { + // self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); + // } + // } + + // public function testEmptyChild() + // { + // // by empty object + // $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildEmpty()); + // self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); + + // // by nulls + // $inner = new InlineChild(); + // $inner->a = null; + // $inner->b = null; + // $inline = new ParentDoNotSkipWithEmptyChild($inner); + // self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); + + // // by exclusion strategy + // $context = SerializationContext::create()->setGroups(['Default']); + // $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildWithGroups()); + // self::assertEquals(static::getContent('empty_child'), $this->serialize($inline, $context)); + // } + + // public function testSkipEmptyChild() + // { + // // by empty object + // $inline = new ParentSkipWithEmptyChild(new InlineChildEmpty()); + // self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); + + // // by nulls + // $inner = new InlineChild(); + // $inner->a = null; + // $inner->b = null; + // $inline = new ParentSkipWithEmptyChild($inner); + // self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); + + // // by exclusion strategy + // $context = SerializationContext::create()->setGroups(['Default']); + // $inline = new ParentSkipWithEmptyChild(new InlineChildWithGroups()); + // self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline, $context)); + // } + + // public function testLog() + // { + // self::assertEquals(static::getContent('log'), $this->serialize($log = new Log())); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('log'), get_class($log)); + // self::assertEquals($log, $deserialized); + // } + // } + + // public function testSelfCircularReferenceCollection() + // { + // $object = new CircularReferenceCollection(); + // $object->collection[] = $object; + // self::assertEquals(static::getContent('circular_reference_collection'), $this->serialize($object)); + // } + + // public function testCircularReference() + // { + // $object = new CircularReferenceParent(); + // self::assertEquals(static::getContent('circular_reference'), $this->serialize($object)); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('circular_reference'), get_class($object)); + + // $col = $this->getField($deserialized, 'collection'); + // self::assertCount(2, $col); + // self::assertEquals('child1', $col[0]->getName()); + // self::assertEquals('child2', $col[1]->getName()); + // self::assertSame($deserialized, $col[0]->getParent()); + // self::assertSame($deserialized, $col[1]->getParent()); + + // $col = $this->getField($deserialized, 'anotherCollection'); + // self::assertCount(2, $col); + // self::assertEquals('child1', $col[0]->getName()); + // self::assertEquals('child2', $col[1]->getName()); + // self::assertSame($deserialized, $col[0]->getParent()); + // self::assertSame($deserialized, $col[1]->getParent()); + // } + // } + + // public function testLifecycleCallbacks() + // { + // $object = new ObjectWithLifecycleCallbacks(); + // self::assertEquals(static::getContent('lifecycle_callbacks'), $this->serialize($object)); + // self::assertNull($this->getField($object, 'name')); + + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('lifecycle_callbacks'), get_class($object)); + // self::assertEquals($object, $deserialized); + // } + // } + + // public function testFormErrors() + // { + // $errors = [ + // new FormError('This is the form error'), + // new FormError('Another error'), + // ]; + + // self::assertEquals(static::getContent('form_errors'), $this->serialize($errors)); + // } + + // /** + // * @dataProvider initialFormTypeProvider + // */ + // #[DataProvider('initialFormTypeProvider')] + // public function testNestedFormErrors($type) + // { + // $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); + + // $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); + // $formConfigBuilder->setCompound(true); + // $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); + // $fooConfig = $formConfigBuilder->getFormConfig(); + + // $form = new Form($fooConfig); + // $form->addError(new FormError('This is the form error')); + + // $formConfigBuilder = new FormConfigBuilder('bar', null, $dispatcher); + // $barConfig = $formConfigBuilder->getFormConfig(); + // $child = new Form($barConfig); + // $child->addError(new FormError('Error of the child form')); + // $form->add($child); + + // $context = SerializationContext::create(); + // $context->setInitialType($type); + + // self::assertEquals(static::getContent('nested_form_errors'), $this->serialize($form, $context)); + // } + + // /** + // * @doesNotPerformAssertions + // * @dataProvider initialFormTypeProvider + // */ + // #[DataProvider('initialFormTypeProvider')] + // #[DoesNotPerformAssertions] + // public function testFormErrorsWithNonFormComponents($type) + // { + // $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); + + // $factoryBuilder = new FormFactoryBuilder(); + // $factoryBuilder->addType(new SubmitType()); + // $factoryBuilder->addType(new ButtonType()); + // $factory = $factoryBuilder->getFormFactory(); + + // $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); + // $formConfigBuilder->setFormFactory($factory); + // $formConfigBuilder->setCompound(true); + // $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); + // $fooConfig = $formConfigBuilder->getFormConfig(); + + // $form = new Form($fooConfig); + // $form->add('save', SubmitType::class); + + // $context = SerializationContext::create(); + // $context->setInitialType($type); + + // try { + // $this->serialize($form, $context); + // } catch (\Throwable $e) { + // self::assertTrue(false, 'Serialization should not throw an exception'); + // } + // } - public function testDateTimeArrays() + public static function initialFormTypeProvider() { - $data = [ - new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), + return [ + [Form::class], + [FormInterface::class], ]; - - $object = new DateTimeArraysObject($data, $data); - $serializedObject = $this->serialize($object); - - self::assertEquals(static::getContent('array_datetimes_object'), $serializedObject); - - if ($this->hasDeserializer()) { - $deserializedObject = $this->deserialize(static::getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject'); - assert($deserializedObject instanceof DateTimeArraysObject); - - /** 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. */ - foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - self::assertEquals($object, $deserializedObject); - } } - public function testNamedDateTimeArrays() - { - $data = [ - new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), - ]; - - $object = new NamedDateTimeArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); - $serializedObject = $this->serialize($object); - - self::assertEquals(static::getContent('array_named_datetimes_object'), $serializedObject); + // public function testConstraintViolation() + // { + // $violation = new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null); + + // self::assertEquals(static::getContent('constraint_violation'), $this->serialize($violation)); + // } + + // public function testConstraintViolationList() + // { + // $violations = new ConstraintViolationList(); + // $violations->add(new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null)); + // $violations->add(new ConstraintViolation('Message of another violation', 'Message of another violation', [], null, 'bar', null)); + + // self::assertEquals(static::getContent('constraint_violation_list'), $this->serialize($violations)); + // } + + // public function testDoctrineProxy() + // { + // if (!class_exists(Version::class)) { + // $this->markTestSkipped('Doctrine is not available.'); + // } + + // $object = new SimpleObjectProxy('foo', 'bar'); + + // self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); + // } + + // public function testInitializedDoctrineProxy() + // { + // if (!class_exists(Version::class)) { + // $this->markTestSkipped('Doctrine is not available.'); + // } + + // $object = new SimpleObjectProxy('foo', 'bar'); + // $object->__load(); + + // self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); + // } + + // public function testCustomAccessor() + // { + // $post = new IndexedCommentsBlogPost(); + + // self::assertEquals(static::getContent('custom_accessor'), $this->serialize($post)); + // } + + // public function testMixedAccessTypes() + // { + // $object = new GetSetObject(); + + // self::assertEquals(static::getContent('mixed_access_types'), $this->serialize($object)); + + // if ($this->hasDeserializer()) { + // $object = $this->deserialize(static::getContent('mixed_access_types'), GetSetObject::class); + // self::assertSame(1, $this->getField($object, 'id')); + // self::assertSame('Johannes', $this->getField($object, 'name')); + // self::assertSame(42, $this->getField($object, 'readOnlyProperty')); + // } + // } + + // public function testAccessorOrder() + // { + // self::assertEquals(static::getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild())); + // self::assertEquals(static::getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent())); + // self::assertEquals(static::getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod())); + // } + + // public function testGroups() + // { + // $groupsObject = new GroupsObject(); + + // self::assertEquals(static::getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat())); + + // self::assertEquals( + // static::getContent('groups_foo'), + // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo'])), + // ); + + // self::assertEquals( + // static::getContent('groups_foobar'), + // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo', 'bar'])), + // ); + + // self::assertEquals( + // static::getContent('groups_all'), + // $this->serializer->serialize($groupsObject, $this->getFormat()), + // ); + + // self::assertEquals( + // static::getContent('groups_default'), + // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), + // ); + + // self::assertEquals( + // static::getContent('groups_default'), + // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), + // ); + // } + + // public function testAdvancedGroups() + // { + // $adrien = new GroupsUser( + // 'John', + // new GroupsUser( + // 'John Manager', + // null, + // [ + // new GroupsUser( + // 'John Manager friend 1', + // new GroupsUser('John Manager friend 1 manager'), + // ), + // new GroupsUser('John Manager friend 2'), + // ], + // ), + // [ + // new GroupsUser( + // 'John friend 1', + // new GroupsUser('John friend 1 manager'), + // ), + // new GroupsUser( + // 'John friend 2', + // new GroupsUser('John friend 2 manager'), + // ), + // ], + // ); + + // self::assertEquals( + // static::getContent('groups_advanced'), + // $this->serializer->serialize( + // $adrien, + // $this->getFormat(), + // SerializationContext::create()->setGroups([ + // GroupsExclusionStrategy::DEFAULT_GROUP, + // 'manager_group', + // 'friends_group', + + // 'manager' => [ + // GroupsExclusionStrategy::DEFAULT_GROUP, + // 'friends_group', + + // 'friends' => ['nickname_group'], + // ], + // 'friends' => [ + // 'manager_group', + // 'nickname_group', + // ], + // ]), + // ), + // ); + // } + + // public function testInvalidGroupName() + // { + // $groupsObject = new InvalidGroupsObject(); + + // $this->expectException(InvalidMetadataException::class); + // $this->expectExceptionMessage('Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups?'); + + // $this->serializer->serialize($groupsObject, $this->getFormat()); + // } + + // public function testVirtualProperty() + // { + // self::assertEquals(static::getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties())); + // } + + // public function testVirtualVersions() + // { + // $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); + + // $builder = SerializerBuilder::create(); + // $builder->setExpressionEvaluator($evaluator); + // $serializer = $builder->build(); + + // self::assertEquals( + // static::getContent('virtual_properties_low'), + // $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('2')), + // ); + + // self::assertEquals( + // static::getContent('virtual_properties_all'), + // $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('7')), + // ); + + // self::assertEquals( + // static::getContent('virtual_properties_high'), + // $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('9')), + // ); + // } + + // public function testCustomHandler() + // { + // if (!$this->hasDeserializer()) { + // return; + // } + + // $handler = static function () { + // return new CustomDeserializationObject('customly_unserialized_value'); + // }; + + // $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler); + + // $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat()); + // $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat()); + // self::assertEquals('customly_unserialized_value', $object->someProperty); + // } + + // public function testTypedProperties() + // { + // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + // $builder->includeInterfaceMetadata(true); + // $this->serializer = $builder->build(); + + // $user = new TypedProperties\User(); + // $user->id = 1; + // $user->created = new \DateTime('2010-10-01 00:00:00'); + // $user->updated = new \DateTime('2011-10-01 00:00:00'); + // $user->tags = ['a', 'b']; + // $role = new TypedProperties\Role(); + // $role->id = 5; + // $user->role = $role; + // $user->vehicle = new TypedProperties\Car(); + + // $result = $this->serialize($user); + + // self::assertEquals(static::getContent('typed_props'), $result); + + // if ($this->hasDeserializer()) { + // // updated is read only + // $user->updated = null; + // $user->tags = []; + + // self::assertEquals($user, $this->deserialize(static::getContent('typed_props'), get_class($user))); + // } + // } + + // public function testConstructorPromotionWithDefaultValues() + // { + // if (PHP_VERSION_ID < 80000) { + // $this->markTestSkipped(sprintf('%s requires PHP 8.0', __METHOD__)); + // } + + // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + // $builder->includeInterfaceMetadata(true); + // $this->serializer = $builder->build(); + + // $vase = new TypedProperties\ConstructorPromotion\Vase('blue', 'big'); + // $result = $this->serialize($vase); + // self::assertEquals(static::getContent('typed_props_constructor_promotion_with_default_values'), $result); + // if ($this->hasDeserializer()) { + // $deserialized = $this->deserialize(static::getContent('typed_props_constructor_promotion_with_default_values'), get_class($vase)); + // self::assertEquals($vase->color, $deserialized->color); + // self::assertEquals($vase->plant, $deserialized->plant); + // self::assertEquals($vase->typeOfSoil, $deserialized->typeOfSoil); + // self::assertEquals($vase->daysSincePotting, $deserialized->daysSincePotting); + // self::assertEquals('huge', $deserialized->size); + // self::assertEquals(40, $deserialized->weight); + // } + // } + + // public function testUninitializedTypedProperties() + // { + // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + // $builder->includeInterfaceMetadata(true); + // $this->serializer = $builder->build(); + + // $user = new TypedProperties\User(); + // $user->id = 1; + // $role = new TypedProperties\Role(); + // $user->role = $role; + + // // Ensure uninitialized typed property exists + // $reflectionProp = new \ReflectionProperty($user, 'vehicle'); + // $this->assertFalse($reflectionProp->isInitialized($user)); + + // $result = $this->serialize($user); + + // self::assertEquals(static::getContent('uninitialized_typed_props'), $result); + // } + + // /** + // * @doesNotPerformAssertions + // */ + // #[DoesNotPerformAssertions] + // public function testCustomHandlerVisitingNull() + // { + // $handler = static function ($visitor, $attachment, array $type, Context $context) { + // return $context->getNavigator()->accept(null); + // }; + + // $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, Author::class, $this->getFormat(), $handler); + + // $author = new Author('me'); + // $comment = new Comment($author, 'too'); + // $this->serializer->serialize($comment, $this->getFormat()); + // } + + // public function testInput() + // { + // self::assertEquals(static::getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat())); + // } + + // public function testObjectWithEmptyHash() + // { + // self::assertEquals(static::getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat())); + // } + + // public function testSerializeObjectWhenNull() + // { + // self::assertEquals( + // static::getContent('object_when_null'), + // $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false)), + // ); + + // self::assertEquals( + // static::getContent('object_when_null_and_serialized'), + // $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true)), + // ); + // } + + // public function testPolymorphicObjectsWithGroup() + // { + // $context = SerializationContext::create(); + // $context->setGroups(['foo']); + + // self::assertEquals( + // static::getContent('car'), + // $this->serialize(new DiscriminatorGroupCar(5), $context), + // ); + // } - if ($this->hasDeserializer()) { - // skip XML deserialization - if ('xml' === $this->getFormat()) { - return; - } - - $deserializedObject = $this->deserialize(static::getContent('array_named_datetimes_object'), NamedDateTimeArraysObject::class); - assert($deserializedObject instanceof NamedDateTimeArraysObject); - - /** 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. */ - foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - self::assertEquals($object, $deserializedObject); - } - } - - public function testNamedDateTimeImmutableArrays() + public static function getDiscrimatorObjectsSamples(): array { - $data = [ - new \DateTimeImmutable('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - new \DateTimeImmutable('2016-12-05 00:00:00', new \DateTimeZone('UTC')), - ]; - - $object = new NamedDateTimeImmutableArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); - $serializedObject = $this->serialize($object); - - self::assertEquals(static::getContent('array_named_datetimeimmutables_object'), $serializedObject); - - if ($this->hasDeserializer()) { - if ('xml' === $this->getFormat()) { - $this->markTestSkipped('XML deserialization does not support key-val pairs mode'); - } - - $deserializedObject = $this->deserialize(static::getContent('array_named_datetimeimmutables_object'), NamedDateTimeImmutableArraysObject::class); - assert($deserializedObject instanceof NamedDateTimeImmutableArraysObject); - - /** 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. */ - foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { - $dateTime->setTimezone(new \DateTimeZone('UTC')); - } - - self::assertEquals($object, $deserializedObject); - } - } + $u1 = new User(5, 'userName', 'userDesc'); + $u2 = new ExtendedUser(5, 'userName', 'userDesc', 'extednedContent'); + $arr = new ArrayCollection([$u1, $u2]); - public function testArrayMixed() - { - self::assertEquals(static::getContent('array_mixed'), $this->serialize(['foo', 1, true, new SimpleObject('foo', 'bar'), [1, 3, true]])); + return [ + [$u1, 'user_discriminator'], + [$u2, 'user_discriminator_extended'], + [$arr, 'user_discriminator_array'], + ]; } /** - * @dataProvider getDateTime - * @group datetime + * Test serializing entity that uses Discriminator and extends some base model class + * + * @dataProvider getDiscrimatorObjectsSamples */ - #[DataProvider('getDateTime')] - public function testDateTime($key, $value, $type) - { - self::assertEquals(static::getContent($key), $this->serialize($value)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent($key), $type); - - self::assertIsObject($deserialized); - self::assertInstanceOf(get_class($value), $deserialized); - self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); - } - } + // #[DataProvider('getDiscrimatorObjectsSamples')] + // public function testDiscrimatorObjects($data, $contentId) + // { + // $context = SerializationContext::create()->setGroups(['entity.identification']); + // self::assertEquals( + // static::getContent($contentId), + // $this->serialize($data, $context), + // ); + // } + + // public function testPolymorphicObjects() + // { + // self::assertEquals( + // static::getContent('car'), + // $this->serialize(new Car(5)), + // ); + // self::assertEquals( + // static::getContent('post'), + // $this->serialize(new Post('Post Title')), + // ); + // self::assertEquals( + // static::getContent('image_post'), + // $this->serialize(new ImagePost('Image Post Title')), + // ); + + // if ($this->hasDeserializer()) { + // self::assertEquals( + // new Car(5), + // $this->deserialize( + // static::getContent('car'), + // Car::class, + // ), + // 'Class is resolved correctly when concrete sub-class is used.', + // ); + + // self::assertEquals( + // new Car(5), + // $this->deserialize( + // static::getContent('car'), + // Vehicle::class, + // ), + // 'Class is resolved correctly when least supertype is used.', + // ); + + // self::assertEquals( + // new Car(5), + // $this->deserialize( + // static::getContent('car_without_type'), + // Car::class, + // ), + // 'Class is resolved correctly when concrete sub-class is used and no type is defined.', + // ); + + // self::assertEquals( + // new Post('Post Title'), + // $this->deserialize( + // static::getContent('post'), + // Post::class, + // ), + // 'Class is resolved correctly when parent class is used and type is set.', + // ); + + // self::assertEquals( + // new ImagePost('Image Post Title'), + // $this->deserialize( + // static::getContent('image_post'), + // Post::class, + // ), + // 'Class is resolved correctly when least supertype is used.', + // ); + + // self::assertEquals( + // new ImagePost('Image Post Title'), + // $this->deserialize( + // static::getContent('image_post'), + // ImagePost::class, + // ), + // 'Class is resolved correctly when concrete sub-class is used and no type is defined.', + // ); + // } + // } + + // public function testNestedPolymorphicObjects() + // { + // $garage = new Garage([new Car(3), new Moped(1)]); + // self::assertEquals( + // static::getContent('garage'), + // $this->serialize($garage), + // ); + + // if ($this->hasDeserializer()) { + // self::assertEquals( + // $garage, + // $this->deserialize( + // static::getContent('garage'), + // Garage::class, + // ), + // ); + // } + // } + + // public function testNestedPolymorphicInterfaces() + // { + // $garage = new VehicleInterfaceGarage([new Car(3), new Moped(1)]); + // self::assertEquals( + // static::getContent('garage'), + // $this->serialize($garage), + // ); + + // if ($this->hasDeserializer()) { + // self::assertEquals( + // $garage, + // $this->deserialize( + // static::getContent('garage'), + // VehicleInterfaceGarage::class, + // ), + // ); + // } + // } + + // public function testPolymorphicObjectsInvalidDeserialization() + // { + // $this->expectException(\LogicException::class); + + // if (!$this->hasDeserializer()) { + // throw new \LogicException('No deserializer'); + // } + + // $this->deserialize( + // static::getContent('car_without_type'), + // Vehicle::class, + // ); + // } + + // public function testDepthExclusionStrategy() + // { + // $context = SerializationContext::create() + // ->addExclusionStrategy(new DepthExclusionStrategy()); + + // $data = new Tree( + // new Node([ + // new Node([ + // new Node([ + // new Node([ + // new Node(), + // ]), + // ]), + // ]), + // ]), + // ); + + // self::assertEquals(static::getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context)); + // } + + // public function testMaxDepthWithSkippableObject() + // { + // $data = new Gh236Foo(); + + // $context = SerializationContext::create()->enableMaxDepthChecks(); + // $serialized = $this->serialize($data, $context); + + // self::assertEquals(static::getContent('maxdepth_skippabe_object'), $serialized); + // } + + // public function testMaxDepthWithZeroDepthObject() + // { + // $data = new Gh1382Foo(); + + // $context = SerializationContext::create()->enableMaxDepthChecks(); + // $serialized = $this->serialize($data, $context); + + // self::assertEquals(static::getContent('maxdepth_0'), $serialized); + // } + + // public function testMaxDepthWithOneDepthObject() + // { + // $data = new Gh1382Baz(); + + // $context = SerializationContext::create()->enableMaxDepthChecks(); + // $serialized = $this->serialize($data, $context); + + // self::assertEquals(static::getContent('maxdepth_1'), $serialized); + // } + + // public function testDeserializingIntoExistingObject() + // { + // if (!$this->hasDeserializer()) { + // return; + // } + + // $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor()); + + // $builder = SerializerBuilder::create(); + // $builder->setObjectConstructor($objectConstructor); + // $serializer = $builder->build(); + + // $order = new Order(new Price(12)); + + // $context = new DeserializationContext(); + // $context->setAttribute('target', $order); + + // $deseralizedOrder = $serializer->deserialize( + // static::getContent('order'), + // get_class($order), + // $this->getFormat(), + // $context, + // ); + + // self::assertSame($order, $deseralizedOrder); + // self::assertEquals(new Order(new Price(12.34)), $deseralizedOrder); + // self::assertInstanceOf(Price::class, $this->getField($deseralizedOrder, 'cost')); + // } + + // public function testObjectWithNullableArrays() + // { + // $object = new ObjectWithEmptyNullableAndEmptyArrays(); + // self::assertEquals(static::getContent('nullable_arrays'), $this->serializer->serialize($object, $this->getFormat())); + // } + + // /** + // * @dataProvider getSerializeNullCases + // */ + // #[DataProvider('getSerializeNullCases')] + // public function testSerializeNullArrayObjectWithExclusionStrategy(bool $serializeNull) + // { + // $arr = [ + // new SimpleObject('foo1', 'bar1'), + // ]; + + // $serializationContext = SerializationContext::create(); + // $serializationContext->setSerializeNull($serializeNull); + // $serializationContext->setInitialType('array<' . SimpleObject::class . '>'); + // $serializationContext->addExclusionStrategy(new AlwaysExcludeExclusionStrategy()); + // self::assertEquals( + // static::getContent('array_objects_nullable'), + // $this->serializer->serialize($arr, $this->getFormat(), $serializationContext), + // ); + // } + + // public function testHandlerInvokedOnPrimitives() + // { + // $invoked = false; + // $this->handlerRegistry->registerHandler( + // GraphNavigatorInterface::DIRECTION_SERIALIZATION, + // 'Virtual', + // $this->getFormat(), + // static function ($visitor, $data) use (&$invoked) { + // $invoked = true; + // self::assertEquals('foo', $data); + + // return null; + // }, + // ); + + // $this->serializer->serialize('foo', $this->getFormat(), null, 'Virtual'); + // self::assertTrue($invoked); + // } - public static function getDateTime() + public static function getFirstClassListCollectionsValues() { + $collection = new FirstClassListCollection([1, 2]); + return [ - ['date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'], - ['date_time_multi_format', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), "DateTime<'Y-m-d', '', ['Y-m-d','Y-m-d\TH:i:sP']>"], - ]; - } - - /** - * @dataProvider getDateTimeImmutable - * @group datetime - */ - #[DataProvider('getDateTimeImmutable')] - public function testDateTimeImmutable($key, $value, $type) - { - self::assertEquals(static::getContent($key), $this->serialize($value)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent($key), $type); - - self::assertIsObject($deserialized); - self::assertInstanceOf(get_class($value), $deserialized); - self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); - } - } - - public static function getDateTimeImmutable() - { - return [ - ['date_time_immutable', new \DateTimeImmutable('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTimeImmutable'], - ]; - } - - public function testTimestamp() - { - $value = new Timestamp(new \DateTime('2016-02-11 00:00:00', new \DateTimeZone('UTC'))); - self::assertEquals(static::getContent('timestamp'), $this->serialize($value)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('timestamp'), Timestamp::class); - self::assertEquals($value, $deserialized); - self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); - - $deserialized = $this->deserialize(static::getContent('timestamp_prev'), Timestamp::class); - self::assertEquals($value, $deserialized); - self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); - } - } - - public function testDateInterval() - { - $duration = new \DateInterval('PT45M'); - - self::assertEquals(static::getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat())); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('date_interval'), \DateInterval::class); - self::assertEquals($duration, $deserialized); - self::assertEquals($duration->i, $deserialized->i); - } - } - - public function testBlogPost() - { - $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')); - $post->addComment($comment = new Comment($author, 'foo')); - - $post->addTag($tag1 = new Tag('tag1')); - $post->addTag($tag2 = new Tag('tag2')); - - self::assertEquals(static::getContent('blog_post'), $this->serialize($post)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('blog_post'), get_class($post)); - self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); - self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); - self::assertFalse($this->getField($deserialized, 'published')); - self::assertFalse($this->getField($deserialized, 'reviewed')); - self::assertSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', $this->getField($deserialized, 'etag')); - self::assertEquals(new ArrayCollection([$comment]), $this->getField($deserialized, 'comments')); - self::assertEquals([$comment], $this->getField($deserialized, 'comments2')); - self::assertEquals($author, $this->getField($deserialized, 'author')); - self::assertEquals([$tag1, $tag2], $this->getField($deserialized, 'tag')); - } - } - - public function testDeserializingNull() - { - $objectConstructor = new InitializedBlogPostConstructor(); - - $builder = SerializerBuilder::create(); - $builder->setObjectConstructor($objectConstructor); - $this->serializer = $builder->build(); - - $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')); - - $this->setField($post, 'author', null); - $this->setField($post, 'publisher', null); - - self::assertEquals(static::getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true))); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create()); - - self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); - self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); - self::assertFalse($this->getField($deserialized, 'published')); - self::assertFalse($this->getField($deserialized, 'reviewed')); - self::assertEquals(new ArrayCollection(), $this->getField($deserialized, 'comments')); - self::assertEquals(null, $this->getField($deserialized, 'author')); - } - } - - public function testSymfonyUid() - { - $uid = Uuid::fromString('66b3177c-e03b-4a22-9dee-ddd7d37a04d5'); - - self::assertEquals(static::getContent('uid'), $this->serialize($uid)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('uid'), UuidV4::class); - - self::assertInstanceOf(UuidV4::class, $deserialized); - self::assertTrue($uid->equals($deserialized)); - } - } - - public function testExpressionAuthor() - { - $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); - - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator($evaluator); - $serializer = $builder->build(); - - $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); - self::assertEquals(static::getContent('author_expression'), $serializer->serialize($author, $this->getFormat())); - } - - public function testExpressionAuthorWithContextVars() - { - $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); - - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator($evaluator); - $serializer = $builder->build(); - - $author = new AuthorExpressionAccessContext('Ruud'); - self::assertEquals(static::getContent('author_expression_context'), $serializer->serialize($author, $this->getFormat())); - } - - public function testExpressionAccessorStrategNotEnabled() - { - $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); - - $this->expectException(ExpressionLanguageRequiredException::class); - $this->expectExceptionMessage('The property firstName on JMS\Serializer\Tests\Fixtures\AuthorExpressionAccess requires the expression accessor strategy to be enabled.'); - - $this->serialize($author); - } - - public function testReadOnly() - { - $author = new AuthorReadOnly(123, 'Ruud Kamphuis'); - self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - self::assertNull($this->getField($deserialized, 'id')); - self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - } - } - - public function testDeprecatedReadOnly() - { - $author = new AuthorDeprecatedReadOnly(123, 'Ruud Kamphuis'); - self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - self::assertNull($this->getField($deserialized, 'id')); - self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - } - } - - public function testReadOnlyClass() - { - $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis'); - self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - self::assertNull($this->getField($deserialized, 'id')); - self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - } - } - - public function testDeprecatedReadOnlyClass() - { - $author = new AuthorDeprecatedReadOnlyPerClass(123, 'Ruud Kamphuis'); - self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - self::assertNull($this->getField($deserialized, 'id')); - self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - } - } - - public function testPrice() - { - $price = new Price(3); - self::assertEquals(static::getContent('price'), $this->serialize($price)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('price'), get_class($price)); - self::assertEquals(3, $this->getField($deserialized, 'price')); - } - } - - public function testOrder() - { - $order = new Order(new Price(12.34)); - self::assertEquals(static::getContent('order'), $this->serialize($order)); - - if ($this->hasDeserializer()) { - self::assertEquals($order, $this->deserialize(static::getContent('order'), get_class($order))); - } - } - - public function testCurrencyAwarePrice() - { - $price = new CurrencyAwarePrice(2.34); - self::assertEquals(static::getContent('currency_aware_price'), $this->serialize($price)); - - if ($this->hasDeserializer()) { - self::assertEquals($price, $this->deserialize(static::getContent('currency_aware_price'), get_class($price))); - } - } - - public function testOrderWithCurrencyAwarePrice() - { - $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23)); - self::assertEquals(static::getContent('order_with_currency_aware_price'), $this->serialize($order)); - - if ($this->hasDeserializer()) { - self::assertEquals($order, $this->deserialize(static::getContent('order_with_currency_aware_price'), get_class($order))); - } - } - - public function testInline() - { - $inline = new InlineParent(); - - $result = $this->serialize($inline); - self::assertEquals(static::getContent('inline'), $result); - - if ($this->hasDeserializer()) { - self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); - } - } - - public function testInlineEmptyChild() - { - $inline = new InlineParentWithEmptyChild(new InlineChildEmpty()); - $result = $this->serialize($inline); - self::assertEquals(static::getContent('inline_child_empty'), $result); - if ($this->hasDeserializer()) { - self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); - } - } - - public function testEmptyChild() - { - // by empty object - $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildEmpty()); - self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); - - // by nulls - $inner = new InlineChild(); - $inner->a = null; - $inner->b = null; - $inline = new ParentDoNotSkipWithEmptyChild($inner); - self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); - - // by exclusion strategy - $context = SerializationContext::create()->setGroups(['Default']); - $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildWithGroups()); - self::assertEquals(static::getContent('empty_child'), $this->serialize($inline, $context)); - } - - public function testSkipEmptyChild() - { - // by empty object - $inline = new ParentSkipWithEmptyChild(new InlineChildEmpty()); - self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); - - // by nulls - $inner = new InlineChild(); - $inner->a = null; - $inner->b = null; - $inline = new ParentSkipWithEmptyChild($inner); - self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); - - // by exclusion strategy - $context = SerializationContext::create()->setGroups(['Default']); - $inline = new ParentSkipWithEmptyChild(new InlineChildWithGroups()); - self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline, $context)); - } - - public function testLog() - { - self::assertEquals(static::getContent('log'), $this->serialize($log = new Log())); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('log'), get_class($log)); - self::assertEquals($log, $deserialized); - } - } - - public function testSelfCircularReferenceCollection() - { - $object = new CircularReferenceCollection(); - $object->collection[] = $object; - self::assertEquals(static::getContent('circular_reference_collection'), $this->serialize($object)); - } - - public function testCircularReference() - { - $object = new CircularReferenceParent(); - self::assertEquals(static::getContent('circular_reference'), $this->serialize($object)); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('circular_reference'), get_class($object)); - - $col = $this->getField($deserialized, 'collection'); - self::assertCount(2, $col); - self::assertEquals('child1', $col[0]->getName()); - self::assertEquals('child2', $col[1]->getName()); - self::assertSame($deserialized, $col[0]->getParent()); - self::assertSame($deserialized, $col[1]->getParent()); - - $col = $this->getField($deserialized, 'anotherCollection'); - self::assertCount(2, $col); - self::assertEquals('child1', $col[0]->getName()); - self::assertEquals('child2', $col[1]->getName()); - self::assertSame($deserialized, $col[0]->getParent()); - self::assertSame($deserialized, $col[1]->getParent()); - } - } - - public function testLifecycleCallbacks() - { - $object = new ObjectWithLifecycleCallbacks(); - self::assertEquals(static::getContent('lifecycle_callbacks'), $this->serialize($object)); - self::assertNull($this->getField($object, 'name')); - - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('lifecycle_callbacks'), get_class($object)); - self::assertEquals($object, $deserialized); - } - } - - public function testFormErrors() - { - $errors = [ - new FormError('This is the form error'), - new FormError('Another error'), - ]; - - self::assertEquals(static::getContent('form_errors'), $this->serialize($errors)); - } - - /** - * @dataProvider initialFormTypeProvider - */ - #[DataProvider('initialFormTypeProvider')] - public function testNestedFormErrors($type) - { - $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); - - $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); - $formConfigBuilder->setCompound(true); - $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); - $fooConfig = $formConfigBuilder->getFormConfig(); - - $form = new Form($fooConfig); - $form->addError(new FormError('This is the form error')); - - $formConfigBuilder = new FormConfigBuilder('bar', null, $dispatcher); - $barConfig = $formConfigBuilder->getFormConfig(); - $child = new Form($barConfig); - $child->addError(new FormError('Error of the child form')); - $form->add($child); - - $context = SerializationContext::create(); - $context->setInitialType($type); - - self::assertEquals(static::getContent('nested_form_errors'), $this->serialize($form, $context)); - } - - /** - * @doesNotPerformAssertions - * @dataProvider initialFormTypeProvider - */ - #[DataProvider('initialFormTypeProvider')] - #[DoesNotPerformAssertions] - public function testFormErrorsWithNonFormComponents($type) - { - $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); - - $factoryBuilder = new FormFactoryBuilder(); - $factoryBuilder->addType(new SubmitType()); - $factoryBuilder->addType(new ButtonType()); - $factory = $factoryBuilder->getFormFactory(); - - $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); - $formConfigBuilder->setFormFactory($factory); - $formConfigBuilder->setCompound(true); - $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); - $fooConfig = $formConfigBuilder->getFormConfig(); - - $form = new Form($fooConfig); - $form->add('save', SubmitType::class); - - $context = SerializationContext::create(); - $context->setInitialType($type); - - try { - $this->serialize($form, $context); - } catch (\Throwable $e) { - self::assertTrue(false, 'Serialization should not throw an exception'); - } - } - - public static function initialFormTypeProvider() - { - return [ - [Form::class], - [FormInterface::class], - ]; - } - - public function testConstraintViolation() - { - $violation = new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null); - - self::assertEquals(static::getContent('constraint_violation'), $this->serialize($violation)); - } - - public function testConstraintViolationList() - { - $violations = new ConstraintViolationList(); - $violations->add(new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null)); - $violations->add(new ConstraintViolation('Message of another violation', 'Message of another violation', [], null, 'bar', null)); - - self::assertEquals(static::getContent('constraint_violation_list'), $this->serialize($violations)); - } - - public function testDoctrineProxy() - { - if (!class_exists(Version::class)) { - $this->markTestSkipped('Doctrine is not available.'); - } - - $object = new SimpleObjectProxy('foo', 'bar'); - - self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); - } - - public function testInitializedDoctrineProxy() - { - if (!class_exists(Version::class)) { - $this->markTestSkipped('Doctrine is not available.'); - } - - $object = new SimpleObjectProxy('foo', 'bar'); - $object->__load(); - - self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); - } - - public function testCustomAccessor() - { - $post = new IndexedCommentsBlogPost(); - - self::assertEquals(static::getContent('custom_accessor'), $this->serialize($post)); - } - - public function testMixedAccessTypes() - { - $object = new GetSetObject(); - - self::assertEquals(static::getContent('mixed_access_types'), $this->serialize($object)); - - if ($this->hasDeserializer()) { - $object = $this->deserialize(static::getContent('mixed_access_types'), GetSetObject::class); - self::assertSame(1, $this->getField($object, 'id')); - self::assertSame('Johannes', $this->getField($object, 'name')); - self::assertSame(42, $this->getField($object, 'readOnlyProperty')); - } - } - - public function testAccessorOrder() - { - self::assertEquals(static::getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild())); - self::assertEquals(static::getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent())); - self::assertEquals(static::getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod())); - } - - public function testGroups() - { - $groupsObject = new GroupsObject(); - - self::assertEquals(static::getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat())); - - self::assertEquals( - static::getContent('groups_foo'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo'])), - ); - - self::assertEquals( - static::getContent('groups_foobar'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo', 'bar'])), - ); - - self::assertEquals( - static::getContent('groups_all'), - $this->serializer->serialize($groupsObject, $this->getFormat()), - ); - - self::assertEquals( - static::getContent('groups_default'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), - ); - - self::assertEquals( - static::getContent('groups_default'), - $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), - ); - } - - public function testAdvancedGroups() - { - $adrien = new GroupsUser( - 'John', - new GroupsUser( - 'John Manager', - null, - [ - new GroupsUser( - 'John Manager friend 1', - new GroupsUser('John Manager friend 1 manager'), - ), - new GroupsUser('John Manager friend 2'), - ], - ), - [ - new GroupsUser( - 'John friend 1', - new GroupsUser('John friend 1 manager'), - ), - new GroupsUser( - 'John friend 2', - new GroupsUser('John friend 2 manager'), - ), - ], - ); - - self::assertEquals( - static::getContent('groups_advanced'), - $this->serializer->serialize( - $adrien, - $this->getFormat(), - SerializationContext::create()->setGroups([ - GroupsExclusionStrategy::DEFAULT_GROUP, - 'manager_group', - 'friends_group', - - 'manager' => [ - GroupsExclusionStrategy::DEFAULT_GROUP, - 'friends_group', - - 'friends' => ['nickname_group'], - ], - 'friends' => [ - 'manager_group', - 'nickname_group', - ], - ]), - ), - ); - } - - public function testInvalidGroupName() - { - $groupsObject = new InvalidGroupsObject(); - - $this->expectException(InvalidMetadataException::class); - $this->expectExceptionMessage('Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups?'); - - $this->serializer->serialize($groupsObject, $this->getFormat()); - } - - public function testVirtualProperty() - { - self::assertEquals(static::getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties())); - } - - public function testVirtualVersions() - { - $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); - - $builder = SerializerBuilder::create(); - $builder->setExpressionEvaluator($evaluator); - $serializer = $builder->build(); - - self::assertEquals( - static::getContent('virtual_properties_low'), - $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('2')), - ); - - self::assertEquals( - static::getContent('virtual_properties_all'), - $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('7')), - ); - - self::assertEquals( - static::getContent('virtual_properties_high'), - $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('9')), - ); - } - - public function testCustomHandler() - { - if (!$this->hasDeserializer()) { - return; - } - - $handler = static function () { - return new CustomDeserializationObject('customly_unserialized_value'); - }; - - $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler); - - $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat()); - $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat()); - self::assertEquals('customly_unserialized_value', $object->someProperty); - } - - public function testTypedProperties() - { - $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - $builder->includeInterfaceMetadata(true); - $this->serializer = $builder->build(); - - $user = new TypedProperties\User(); - $user->id = 1; - $user->created = new \DateTime('2010-10-01 00:00:00'); - $user->updated = new \DateTime('2011-10-01 00:00:00'); - $user->tags = ['a', 'b']; - $role = new TypedProperties\Role(); - $role->id = 5; - $user->role = $role; - $user->vehicle = new TypedProperties\Car(); - - $result = $this->serialize($user); - - self::assertEquals(static::getContent('typed_props'), $result); - - if ($this->hasDeserializer()) { - // updated is read only - $user->updated = null; - $user->tags = []; - - self::assertEquals($user, $this->deserialize(static::getContent('typed_props'), get_class($user))); - } - } - - public function testConstructorPromotionWithDefaultValues() - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped(sprintf('%s requires PHP 8.0', __METHOD__)); - } - - $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - $builder->includeInterfaceMetadata(true); - $this->serializer = $builder->build(); - - $vase = new TypedProperties\ConstructorPromotion\Vase('blue', 'big'); - $result = $this->serialize($vase); - self::assertEquals(static::getContent('typed_props_constructor_promotion_with_default_values'), $result); - if ($this->hasDeserializer()) { - $deserialized = $this->deserialize(static::getContent('typed_props_constructor_promotion_with_default_values'), get_class($vase)); - self::assertEquals($vase->color, $deserialized->color); - self::assertEquals($vase->plant, $deserialized->plant); - self::assertEquals($vase->typeOfSoil, $deserialized->typeOfSoil); - self::assertEquals($vase->daysSincePotting, $deserialized->daysSincePotting); - self::assertEquals('huge', $deserialized->size); - self::assertEquals(40, $deserialized->weight); - } - } - - public function testUninitializedTypedProperties() - { - $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - $builder->includeInterfaceMetadata(true); - $this->serializer = $builder->build(); - - $user = new TypedProperties\User(); - $user->id = 1; - $role = new TypedProperties\Role(); - $user->role = $role; - - // Ensure uninitialized typed property exists - $reflectionProp = new \ReflectionProperty($user, 'vehicle'); - $this->assertFalse($reflectionProp->isInitialized($user)); - - $result = $this->serialize($user); - - self::assertEquals(static::getContent('uninitialized_typed_props'), $result); - } - - /** - * @doesNotPerformAssertions - */ - #[DoesNotPerformAssertions] - public function testCustomHandlerVisitingNull() - { - $handler = static function ($visitor, $attachment, array $type, Context $context) { - return $context->getNavigator()->accept(null); - }; - - $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, Author::class, $this->getFormat(), $handler); - - $author = new Author('me'); - $comment = new Comment($author, 'too'); - $this->serializer->serialize($comment, $this->getFormat()); - } - - public function testInput() - { - self::assertEquals(static::getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat())); - } - - public function testObjectWithEmptyHash() - { - self::assertEquals(static::getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat())); - } - - public function testSerializeObjectWhenNull() - { - self::assertEquals( - static::getContent('object_when_null'), - $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false)), - ); - - self::assertEquals( - static::getContent('object_when_null_and_serialized'), - $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true)), - ); - } - - public function testPolymorphicObjectsWithGroup() - { - $context = SerializationContext::create(); - $context->setGroups(['foo']); - - self::assertEquals( - static::getContent('car'), - $this->serialize(new DiscriminatorGroupCar(5), $context), - ); - } - - public static function getDiscrimatorObjectsSamples(): array - { - $u1 = new User(5, 'userName', 'userDesc'); - $u2 = new ExtendedUser(5, 'userName', 'userDesc', 'extednedContent'); - $arr = new ArrayCollection([$u1, $u2]); - - return [ - [$u1, 'user_discriminator'], - [$u2, 'user_discriminator_extended'], - [$arr, 'user_discriminator_array'], - ]; - } - - /** - * Test serializing entity that uses Discriminator and extends some base model class - * - * @dataProvider getDiscrimatorObjectsSamples - */ - #[DataProvider('getDiscrimatorObjectsSamples')] - public function testDiscrimatorObjects($data, $contentId) - { - $context = SerializationContext::create()->setGroups(['entity.identification']); - self::assertEquals( - static::getContent($contentId), - $this->serialize($data, $context), - ); - } - - public function testPolymorphicObjects() - { - self::assertEquals( - static::getContent('car'), - $this->serialize(new Car(5)), - ); - self::assertEquals( - static::getContent('post'), - $this->serialize(new Post('Post Title')), - ); - self::assertEquals( - static::getContent('image_post'), - $this->serialize(new ImagePost('Image Post Title')), - ); - - if ($this->hasDeserializer()) { - self::assertEquals( - new Car(5), - $this->deserialize( - static::getContent('car'), - Car::class, - ), - 'Class is resolved correctly when concrete sub-class is used.', - ); - - self::assertEquals( - new Car(5), - $this->deserialize( - static::getContent('car'), - Vehicle::class, - ), - 'Class is resolved correctly when least supertype is used.', - ); - - self::assertEquals( - new Car(5), - $this->deserialize( - static::getContent('car_without_type'), - Car::class, - ), - 'Class is resolved correctly when concrete sub-class is used and no type is defined.', - ); - - self::assertEquals( - new Post('Post Title'), - $this->deserialize( - static::getContent('post'), - Post::class, - ), - 'Class is resolved correctly when parent class is used and type is set.', - ); - - self::assertEquals( - new ImagePost('Image Post Title'), - $this->deserialize( - static::getContent('image_post'), - Post::class, - ), - 'Class is resolved correctly when least supertype is used.', - ); - - self::assertEquals( - new ImagePost('Image Post Title'), - $this->deserialize( - static::getContent('image_post'), - ImagePost::class, - ), - 'Class is resolved correctly when concrete sub-class is used and no type is defined.', - ); - } - } - - public function testNestedPolymorphicObjects() - { - $garage = new Garage([new Car(3), new Moped(1)]); - self::assertEquals( - static::getContent('garage'), - $this->serialize($garage), - ); - - if ($this->hasDeserializer()) { - self::assertEquals( - $garage, - $this->deserialize( - static::getContent('garage'), - Garage::class, - ), - ); - } - } - - public function testNestedPolymorphicInterfaces() - { - $garage = new VehicleInterfaceGarage([new Car(3), new Moped(1)]); - self::assertEquals( - static::getContent('garage'), - $this->serialize($garage), - ); - - if ($this->hasDeserializer()) { - self::assertEquals( - $garage, - $this->deserialize( - static::getContent('garage'), - VehicleInterfaceGarage::class, - ), - ); - } - } - - public function testPolymorphicObjectsInvalidDeserialization() - { - $this->expectException(\LogicException::class); - - if (!$this->hasDeserializer()) { - throw new \LogicException('No deserializer'); - } - - $this->deserialize( - static::getContent('car_without_type'), - Vehicle::class, - ); - } - - public function testDepthExclusionStrategy() - { - $context = SerializationContext::create() - ->addExclusionStrategy(new DepthExclusionStrategy()); - - $data = new Tree( - new Node([ - new Node([ - new Node([ - new Node([ - new Node(), - ]), - ]), - ]), - ]), - ); - - self::assertEquals(static::getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context)); - } - - public function testMaxDepthWithSkippableObject() - { - $data = new Gh236Foo(); - - $context = SerializationContext::create()->enableMaxDepthChecks(); - $serialized = $this->serialize($data, $context); - - self::assertEquals(static::getContent('maxdepth_skippabe_object'), $serialized); - } - - public function testMaxDepthWithZeroDepthObject() - { - $data = new Gh1382Foo(); - - $context = SerializationContext::create()->enableMaxDepthChecks(); - $serialized = $this->serialize($data, $context); - - self::assertEquals(static::getContent('maxdepth_0'), $serialized); - } - - public function testMaxDepthWithOneDepthObject() - { - $data = new Gh1382Baz(); - - $context = SerializationContext::create()->enableMaxDepthChecks(); - $serialized = $this->serialize($data, $context); - - self::assertEquals(static::getContent('maxdepth_1'), $serialized); - } - - public function testDeserializingIntoExistingObject() - { - if (!$this->hasDeserializer()) { - return; - } - - $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor()); - - $builder = SerializerBuilder::create(); - $builder->setObjectConstructor($objectConstructor); - $serializer = $builder->build(); - - $order = new Order(new Price(12)); - - $context = new DeserializationContext(); - $context->setAttribute('target', $order); - - $deseralizedOrder = $serializer->deserialize( - static::getContent('order'), - get_class($order), - $this->getFormat(), - $context, - ); - - self::assertSame($order, $deseralizedOrder); - self::assertEquals(new Order(new Price(12.34)), $deseralizedOrder); - self::assertInstanceOf(Price::class, $this->getField($deseralizedOrder, 'cost')); - } - - public function testObjectWithNullableArrays() - { - $object = new ObjectWithEmptyNullableAndEmptyArrays(); - self::assertEquals(static::getContent('nullable_arrays'), $this->serializer->serialize($object, $this->getFormat())); - } - - /** - * @dataProvider getSerializeNullCases - */ - #[DataProvider('getSerializeNullCases')] - public function testSerializeNullArrayObjectWithExclusionStrategy(bool $serializeNull) - { - $arr = [ - new SimpleObject('foo1', 'bar1'), - ]; - - $serializationContext = SerializationContext::create(); - $serializationContext->setSerializeNull($serializeNull); - $serializationContext->setInitialType('array<' . SimpleObject::class . '>'); - $serializationContext->addExclusionStrategy(new AlwaysExcludeExclusionStrategy()); - self::assertEquals( - static::getContent('array_objects_nullable'), - $this->serializer->serialize($arr, $this->getFormat(), $serializationContext), - ); - } - - public function testHandlerInvokedOnPrimitives() - { - $invoked = false; - $this->handlerRegistry->registerHandler( - GraphNavigatorInterface::DIRECTION_SERIALIZATION, - 'Virtual', - $this->getFormat(), - static function ($visitor, $data) use (&$invoked) { - $invoked = true; - self::assertEquals('foo', $data); - - return null; - }, - ); - - $this->serializer->serialize('foo', $this->getFormat(), null, 'Virtual'); - self::assertTrue($invoked); - } - - public static function getFirstClassListCollectionsValues() - { - $collection = new FirstClassListCollection([1, 2]); - - return [ - [[1, 2, 3], static::getContent('inline_list_collection')], - [[], static::getContent('inline_empty_list_collection')], - [[1, 'a' => 2], static::getContent('inline_deserialization_list_collection'), $collection], + [[1, 2, 3], static::getContent('inline_list_collection')], + [[], static::getContent('inline_empty_list_collection')], + [[1, 'a' => 2], static::getContent('inline_deserialization_list_collection'), $collection], ]; } @@ -1948,136 +1948,137 @@ public static function getFirstClassListCollectionsValues() * * @dataProvider getFirstClassListCollectionsValues */ - #[DataProvider('getFirstClassListCollectionsValues')] - public function testFirstClassListCollections($items, $expected, ?FirstClassListCollection $expectedDeserializatrion = null) - { - $collection = new FirstClassListCollection($items); - - self::assertSame($expected, $this->serialize($collection)); - self::assertEquals( - $expectedDeserializatrion ?: $collection, - $this->deserialize($expected, get_class($collection)), - ); - } - - public function testInlineCollection() - { - $list = new AuthorsInline(new Author('foo'), new Author('bar')); - self::assertEquals(static::getContent('authors_inline'), $this->serialize($list)); - self::assertEquals($list, $this->deserialize(static::getContent('authors_inline'), AuthorsInline::class)); - } - - public function testSerializingUnionTypedProperties() - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - } - - $object = new TypedProperties\UnionTypedProperties(10000); - - self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); - } - - public function testSerializingUnionDocBlockTypesProperties() - { - $object = new UnionTypedDocBlockProperty(10000); - - self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); - - $object = new UnionTypedDocBlockProperty(1.236); - - self::assertEquals(static::getContent('data_float'), $this->serialize($object)); - } - - public function testThrowingExceptionWhenDeserializingUnionDocBlockTypes() - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - - return; - } - - $this->expectException(RuntimeException::class); - - $object = new UnionTypedDocBlockProperty(10000); - $deserialized = $this->deserialize(static::getContent('data_integer'), UnionTypedDocBlockProperty::class); - } - - public function testIterable(): void - { - $generator = static function (): iterable { - yield 'foo' => 'bar'; - yield 'bar' => 'foo'; - }; - $withIterable = new ObjectWithIterable($generator()); - self::assertEquals(static::getContent('iterable'), $this->serialize($withIterable)); - - if (!$this->hasDeserializer()) { - return; - } - - self::assertEquals( - new ObjectWithIterable(Functions::iterableToArray($generator())), - $this->deserialize(static::getContent('iterable'), get_class($withIterable)), - ); - } - - public function testGenerator(): void - { - $generator = static function (): \Generator { - yield 'foo' => 'bar'; - yield 'bar' => 'foo'; - }; - $withGenerator = new ObjectWithGenerator($generator()); - self::assertEquals(static::getContent('generator'), $this->serialize($withGenerator)); - - if (!$this->hasDeserializer()) { - return; - } - - self::assertEquals( - $withGenerator, - $this->deserialize(static::getContent('generator'), get_class($withGenerator)), - ); - } - - public function testIterator(): void - { - $iterator = new \ArrayIterator([ - 'foo' => 'bar', - 'bar' => 'foo', - ]); - $withIterator = new ObjectWithIterator($iterator); - self::assertEquals(static::getContent('iterator'), $this->serialize($withIterator)); - - if (!$this->hasDeserializer()) { - return; - } - - self::assertEquals( - $withIterator, - $this->deserialize(static::getContent('iterator'), get_class($withIterator)), - ); - } - - public function testArrayIterator(): void - { - $iterator = new \ArrayIterator([ - 'foo' => 'bar', - 'bar' => 'foo', - ]); - $withArrayIterator = new ObjectWithArrayIterator($iterator); - self::assertEquals(static::getContent('iterator'), $this->serialize($withArrayIterator)); - - if (!$this->hasDeserializer()) { - return; - } - - self::assertEquals( - $withArrayIterator, - $this->deserialize(static::getContent('iterator'), get_class($withArrayIterator)), - ); - } + // #[DataProvider('getFirstClassListCollectionsValues')] + // public function testFirstClassListCollections($items, $expected, ?FirstClassListCollection $expectedDeserializatrion = null) + // { + // $collection = new FirstClassListCollection($items); + + // self::assertSame($expected, $this->serialize($collection)); + // self::assertEquals( + // $expectedDeserializatrion ?: $collection, + // $this->deserialize($expected, get_class($collection)), + // ); + // } + + // public function testInlineCollection() + // { + // $list = new AuthorsInline(new Author('foo'), new Author('bar')); + // self::assertEquals(static::getContent('authors_inline'), $this->serialize($list)); + // self::assertEquals($list, $this->deserialize(static::getContent('authors_inline'), AuthorsInline::class)); + // } + + // public function testSerializingUnionTypedProperties() + // { + // if (PHP_VERSION_ID < 80000) { + // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + // } + + // $object = new TypedProperties\UnionTypedProperties(10000); + + // self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); + // } + + // public function testSerializingUnionDocBlockTypesProperties() + // { + // $object = new UnionTypedDocBlockProperty(10000); + + // self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); + + // $object = new UnionTypedDocBlockProperty(1.236); + + // self::assertEquals(static::getContent('data_float'), $this->serialize($object)); + + // } + + // public function testThrowingExceptionWhenDeserializingUnionDocBlockTypes() + // { + // if (PHP_VERSION_ID < 80000) { + // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + // return; + // } + + // $this->expectException(RuntimeException::class); + + // $object = new UnionTypedDocBlockProperty(10000); + // $deserialized = $this->deserialize(static::getContent('data_integer'), UnionTypedDocBlockProperty::class); + // } + + // public function testIterable(): void + // { + // $generator = static function (): iterable { + // yield 'foo' => 'bar'; + // yield 'bar' => 'foo'; + // }; + // $withIterable = new ObjectWithIterable($generator()); + // self::assertEquals(static::getContent('iterable'), $this->serialize($withIterable)); + + // if (!$this->hasDeserializer()) { + // return; + // } + + // self::assertEquals( + // new ObjectWithIterable(Functions::iterableToArray($generator())), + // $this->deserialize(static::getContent('iterable'), get_class($withIterable)), + // ); + // } + + // public function testGenerator(): void + // { + // $generator = static function (): \Generator { + // yield 'foo' => 'bar'; + // yield 'bar' => 'foo'; + // }; + // $withGenerator = new ObjectWithGenerator($generator()); + // self::assertEquals(static::getContent('generator'), $this->serialize($withGenerator)); + + // if (!$this->hasDeserializer()) { + // return; + // } + + // self::assertEquals( + // $withGenerator, + // $this->deserialize(static::getContent('generator'), get_class($withGenerator)), + // ); + // } + + // public function testIterator(): void + // { + // $iterator = new \ArrayIterator([ + // 'foo' => 'bar', + // 'bar' => 'foo', + // ]); + // $withIterator = new ObjectWithIterator($iterator); + // self::assertEquals(static::getContent('iterator'), $this->serialize($withIterator)); + + // if (!$this->hasDeserializer()) { + // return; + // } + + // self::assertEquals( + // $withIterator, + // $this->deserialize(static::getContent('iterator'), get_class($withIterator)), + // ); + // } + + // public function testArrayIterator(): void + // { + // $iterator = new \ArrayIterator([ + // 'foo' => 'bar', + // 'bar' => 'foo', + // ]); + // $withArrayIterator = new ObjectWithArrayIterator($iterator); + // self::assertEquals(static::getContent('iterator'), $this->serialize($withArrayIterator)); + + // if (!$this->hasDeserializer()) { + // return; + // } + + // self::assertEquals( + // $withArrayIterator, + // $this->deserialize(static::getContent('iterator'), get_class($withArrayIterator)), + // ); + // } public static function getSerializeNullCases() { @@ -2118,7 +2119,7 @@ protected function setUp(): void $this->handlerRegistry->registerSubscribingHandler(new SymfonyUidHandler()); $this->handlerRegistry->registerSubscribingHandler(new EnumHandler()); if (PHP_VERSION_ID >= 80000) { - $this->handlerRegistry->registerSubscribingHandler(new UnionHandler()); + $this->handlerRegistry->registerSubscribingHandler(new UnionHandler(requireAllProperties: true)); } $this->handlerRegistry->registerHandler( diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index 25cb0cf17..b389aa258 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -14,12 +14,17 @@ use JMS\Serializer\SerializationContext; use JMS\Serializer\Tests\Fixtures\Author; use JMS\Serializer\Tests\Fixtures\AuthorList; +use JMS\Serializer\Tests\Fixtures\Comment; use JMS\Serializer\Tests\Fixtures\FirstClassMapCollection; use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyArrayAndHash; use JMS\Serializer\Tests\Fixtures\ObjectWithInlineArray; use JMS\Serializer\Tests\Fixtures\ObjectWithObjectProperty; use JMS\Serializer\Tests\Fixtures\Tag; +use JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor; +use JMS\Serializer\Tests\Fixtures\DiscriminatedComment; use JMS\Serializer\Tests\Fixtures\TypedProperties\UnionTypedProperties; +use JMS\Serializer\Tests\Fixtures\TypedProperties\ComplexUnionTypedProperties; +use JMS\Serializer\Tests\Fixtures\TypedProperties\ComplexDiscriminatedUnion; use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory; use JMS\Serializer\Visitor\SerializationVisitorInterface; use PHPUnit\Framework\Attributes\DataProvider; @@ -147,6 +152,10 @@ protected static function getContent($key) $outputs['data_float'] = '{"data":1.236}'; $outputs['data_bool'] = '{"data":false}'; $outputs['data_string'] = '{"data":"foo"}'; + $outputs['data_author'] = '{"data":{"full_name":"foo"}}'; + $outputs['data_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar"}}'; + $outputs['data_discriminated_author'] = '{"data":{"type":"author","full_name":"foo"}}'; + $outputs['data_discriminated_comment'] = '{"data":{"type":"comment","full_name":"foo"}}'; $outputs['uid'] = '"66b3177c-e03b-4a22-9dee-ddd7d37a04d5"'; $outputs['object_with_enums'] = '{"ordinary":"Clubs","backed_value":"C","backed_without_param":"C","ordinary_array":["Clubs","Spades"],"backed_array":["C","H"],"backed_array_without_param":["C","H"],"ordinary_auto_detect":"Clubs","backed_auto_detect":"C","backed_int_auto_detect":3,"backed_int":3,"backed_name":"C","backed_int_forced_str":3}'; $outputs['object_with_autodetect_enums'] = '{"ordinary_array_auto_detect":["Clubs","Spades"],"backed_array_auto_detect":["C","H"],"mixed_array_auto_detect":["Clubs","H"]}'; @@ -160,275 +169,309 @@ protected static function getContent($key) return $outputs[$key]; } - public function testSkipEmptyArrayAndHash() - { - $object = new ObjectWithEmptyArrayAndHash(); - - self::assertEquals('{}', $this->serialize($object)); - } - - public static function getFirstClassMapCollectionsValues() - { - return [ - [['a' => '1', 'b' => '2', 'c' => '3'], self::getContent('inline_map')], - [[], self::getContent('inline_empty_map')], - [['a' => 'b', 'c' => 'd', 'e' => '5'], self::getContent('inline_deserialization_map')], - ]; - } - - /** - * @dataProvider getFirstClassMapCollectionsValues - */ - #[DataProvider('getFirstClassMapCollectionsValues')] - public function testFirstClassMapCollections(array $items, string $expected): void - { - $collection = new FirstClassMapCollection($items); - - self::assertSame($expected, $this->serialize($collection)); - self::assertEquals( - $collection, - $this->deserialize($expected, get_class($collection)), - ); - } - - public function testAddLinksToOutput() - { - $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { - self::assertFalse($event->getVisitor()->hasData('_links')); - }, Author::class, 'json'); - - $this->dispatcher->addSubscriber(new LinkAddingSubscriber()); - - $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { - self::assertTrue($event->getVisitor()->hasData('_links')); - }, Author::class, 'json'); - - $this->handlerRegistry->registerHandler( - GraphNavigatorInterface::DIRECTION_SERIALIZATION, - AuthorList::class, - 'json', - static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { - return $visitor->visitArray(iterator_to_array($data), $type); - }, - ); - - $list = new AuthorList(); - $list->add(new Author('foo')); - $list->add(new Author('bar')); - - self::assertEquals('[{"full_name":"foo","_links":{"details":"http:\/\/foo.bar\/details\/foo","comments":"http:\/\/foo.bar\/details\/foo\/comments"}},{"full_name":"bar","_links":{"details":"http:\/\/foo.bar\/details\/bar","comments":"http:\/\/foo.bar\/details\/bar\/comments"}}]', $this->serialize($list)); - } - - public function testReplaceNameInOutput() - { - $this->dispatcher->addSubscriber(new ReplaceNameSubscriber()); - $this->handlerRegistry->registerHandler( - GraphNavigatorInterface::DIRECTION_SERIALIZATION, - AuthorList::class, - 'json', - static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { - return $visitor->visitArray(iterator_to_array($data), $type); - }, - ); - - $list = new AuthorList(); - $list->add(new Author('foo')); - $list->add(new Author('bar')); - - self::assertEquals('[{"full_name":"new name"},{"full_name":"new name"}]', $this->serialize($list)); - } - - public function testDeserializingObjectWithObjectPropertyWithNoArrayToObject() - { - $content = self::getContent('object_with_object_property_no_array_to_author'); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Invalid data "baz" (string), expected "JMS\Serializer\Tests\Fixtures\Author".'); - - $this->deserialize($content, ObjectWithObjectProperty::class); - } - - public function testDeserializingObjectWithObjectProperty() - { - $content = self::getContent('object_with_object_property'); - $object = $this->deserialize($content, ObjectWithObjectProperty::class); - self::assertEquals('bar', $object->getFoo()); - self::assertInstanceOf(Author::class, $object->getAuthor()); - self::assertEquals('baz', $object->getAuthor()->getName()); - } - - public static function getPrimitiveTypes() - { - return [ - [ - 'type' => 'boolean', - 'data' => true, - ], - [ - 'type' => 'integer', - 'data' => 123, - ], - [ - 'type' => 'string', - 'data' => 'hello', - ], - [ - 'type' => 'double', - 'data' => 0.1234, - ], - ]; - } - - /** - * @dataProvider getPrimitiveTypes - */ - #[DataProvider('getPrimitiveTypes')] - public function testPrimitiveTypes(string $type, $data) - { - $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock(); - - $factory = new JsonSerializationVisitorFactory(); - $visitor = $factory->getVisitor(); - $visitor->setNavigator($navigator); - $functionToCall = 'visit' . ucfirst($type); - $result = $visitor->$functionToCall($data, [], $this->getMockBuilder(SerializationContext::class)->getMock()); - self::{'assertIs' . (['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'][$type] ?? $type)}($result); - } - - public function testSerializeEmptyObject() - { - self::assertEquals('{}', $this->serialize(new Author(null))); - } - - public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOff() - { - ini_set('display_errors', '1'); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); - - $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); - } + // public function testSkipEmptyArrayAndHash() + // { + // $object = new ObjectWithEmptyArrayAndHash(); + + // self::assertEquals('{}', $this->serialize($object)); + // } + + // public static function getFirstClassMapCollectionsValues() + // { + // return [ + // [['a' => '1', 'b' => '2', 'c' => '3'], self::getContent('inline_map')], + // [[], self::getContent('inline_empty_map')], + // [['a' => 'b', 'c' => 'd', 'e' => '5'], self::getContent('inline_deserialization_map')], + // ]; + // } + + + // #[DataProvider('getFirstClassMapCollectionsValues')] + // public function testFirstClassMapCollections(array $items, string $expected): void + // { + // $collection = new FirstClassMapCollection($items); + + // self::assertSame($expected, $this->serialize($collection)); + // self::assertEquals( + // $collection, + // $this->deserialize($expected, get_class($collection)), + // ); + // } + + // public function testAddLinksToOutput() + // { + // $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { + // self::assertFalse($event->getVisitor()->hasData('_links')); + // }, Author::class, 'json'); + + // $this->dispatcher->addSubscriber(new LinkAddingSubscriber()); + + // $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { + // self::assertTrue($event->getVisitor()->hasData('_links')); + // }, Author::class, 'json'); + + // $this->handlerRegistry->registerHandler( + // GraphNavigatorInterface::DIRECTION_SERIALIZATION, + // AuthorList::class, + // 'json', + // static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { + // return $visitor->visitArray(iterator_to_array($data), $type); + // }, + // ); + + // $list = new AuthorList(); + // $list->add(new Author('foo')); + // $list->add(new Author('bar')); + + // self::assertEquals('[{"full_name":"foo","_links":{"details":"http:\/\/foo.bar\/details\/foo","comments":"http:\/\/foo.bar\/details\/foo\/comments"}},{"full_name":"bar","_links":{"details":"http:\/\/foo.bar\/details\/bar","comments":"http:\/\/foo.bar\/details\/bar\/comments"}}]', $this->serialize($list)); + // } + + // public function testReplaceNameInOutput() + // { + // $this->dispatcher->addSubscriber(new ReplaceNameSubscriber()); + // $this->handlerRegistry->registerHandler( + // GraphNavigatorInterface::DIRECTION_SERIALIZATION, + // AuthorList::class, + // 'json', + // static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { + // return $visitor->visitArray(iterator_to_array($data), $type); + // }, + // ); + + // $list = new AuthorList(); + // $list->add(new Author('foo')); + // $list->add(new Author('bar')); + + // self::assertEquals('[{"full_name":"new name"},{"full_name":"new name"}]', $this->serialize($list)); + // } + + // public function testDeserializingObjectWithObjectPropertyWithNoArrayToObject() + // { + // $content = self::getContent('object_with_object_property_no_array_to_author'); + + // $this->expectException(\RuntimeException::class); + // $this->expectExceptionMessage('Invalid data "baz" (string), expected "JMS\Serializer\Tests\Fixtures\Author".'); + + // $this->deserialize($content, ObjectWithObjectProperty::class); + // } + + // public function testDeserializingObjectWithObjectProperty() + // { + // $content = self::getContent('object_with_object_property'); + // $object = $this->deserialize($content, ObjectWithObjectProperty::class); + // self::assertEquals('bar', $object->getFoo()); + // self::assertInstanceOf(Author::class, $object->getAuthor()); + // self::assertEquals('baz', $object->getAuthor()->getName()); + // } + + // public static function getPrimitiveTypes() + // { + // return [ + // [ + // 'type' => 'boolean', + // 'data' => true, + // ], + // [ + // 'type' => 'integer', + // 'data' => 123, + // ], + // [ + // 'type' => 'string', + // 'data' => 'hello', + // ], + // [ + // 'type' => 'double', + // 'data' => 0.1234, + // ], + // ]; + // } + + // /** + // * @dataProvider getPrimitiveTypes + // */ + // #[DataProvider('getPrimitiveTypes')] + // public function testPrimitiveTypes(string $type, $data) + // { + // $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock(); + + // $factory = new JsonSerializationVisitorFactory(); + // $visitor = $factory->getVisitor(); + // $visitor->setNavigator($navigator); + // $functionToCall = 'visit' . ucfirst($type); + // $result = $visitor->$functionToCall($data, [], $this->getMockBuilder(SerializationContext::class)->getMock()); + // self::{'assertIs' . (['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'][$type] ?? $type)}($result); + // } + + // public function testSerializeEmptyObject() + // { + // self::assertEquals('{}', $this->serialize(new Author(null))); + // } + + // public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOff() + // { + // ini_set('display_errors', '1'); + + // $this->expectException(\RuntimeException::class); + // $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); + + // $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); + // } + + // public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOn() + // { + // ini_set('display_errors', '0'); + + // $this->expectException(\RuntimeException::class); + // $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); + + // $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); + // } + + // public function testSerializeArrayWithEmptyObject() + // { + // self::assertEquals('[{}]', $this->serialize([new \stdClass()])); + // } + + // public function testInlineArray() + // { + // $object = new ObjectWithInlineArray(['a' => 'b', 'c' => 'd']); + // $serialized = $this->serialize($object); + // self::assertEquals('{"a":"b","c":"d"}', $serialized); + // self::assertEquals($object, $this->deserialize($serialized, ObjectWithInlineArray::class)); + // } + + // public function testSerializeRootArrayWithDefinedKeys() + // { + // $author1 = new Author('Jim'); + // $author2 = new Author('Mark'); + + // $data = [ + // 'jim' => $author1, + // 'mark' => $author2, + // ]; + + // self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + // self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + // self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + + // $data = [ + // $author1, + // $author2, + // ]; + // self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + // self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + // self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + // } + + // public static function getTypeHintedArrays() + // { + // return [ + + // [[1, 2], '[1,2]', null], + // [['a', 'b'], '["a","b"]', null], + // [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', null], + + // [[], '[]', null], + // [[], '[]', SerializationContext::create()->setInitialType('array')], + // [[], '[]', SerializationContext::create()->setInitialType('array')], + // [[], '{}', SerializationContext::create()->setInitialType('array')], + + // [[1, 2], '[1,2]', SerializationContext::create()->setInitialType('array')], + // [[1 => 1, 2 => 2], '{"1":1,"2":2}', SerializationContext::create()->setInitialType('array')], + // [[1 => 1, 2 => 2], '[1,2]', SerializationContext::create()->setInitialType('array')], + // [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], + // [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('list')], + + // [[1 => 'a', 2 => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], + // [['a' => 'a', 'b' => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], + + // [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], + // [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], + // [[1, 2], '{"0":"1","1":"2"}', SerializationContext::create()->setInitialType('array')], + + // [['a', 'b'], '{"0":"a","1":"b"}', SerializationContext::create()->setInitialType('array')], + // [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', SerializationContext::create()->setInitialType('array')], - public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOn() - { - ini_set('display_errors', '0'); + // [[15.6, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], + // [[5.2 * 3, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], + // ]; + // } - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); - $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); - } + // #[DataProvider('getTypeHintedArrays')] + // public function testTypeHintedArraySerialization(array $array, string $expected, ?SerializationContext $context = null) + // { + // self::assertEquals($expected, $this->serialize($array, $context)); + // } - public function testSerializeArrayWithEmptyObject() - { - self::assertEquals('[{}]', $this->serialize([new \stdClass()])); - } - - public function testInlineArray() - { - $object = new ObjectWithInlineArray(['a' => 'b', 'c' => 'd']); - $serialized = $this->serialize($object); - self::assertEquals('{"a":"b","c":"d"}', $serialized); - self::assertEquals($object, $this->deserialize($serialized, ObjectWithInlineArray::class)); - } + // public static function getTypeHintedArraysAndStdClass() + // { + // $c1 = new \stdClass(); + // $c2 = new \stdClass(); + // $c2->foo = 'bar'; - public function testSerializeRootArrayWithDefinedKeys() - { - $author1 = new Author('Jim'); - $author2 = new Author('Mark'); - - $data = [ - 'jim' => $author1, - 'mark' => $author2, - ]; - - self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - - $data = [ - $author1, - $author2, - ]; - self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - } - - public static function getTypeHintedArrays() - { - return [ + // $tag = new Tag('tag'); - [[1, 2], '[1,2]', null], - [['a', 'b'], '["a","b"]', null], - [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', null], + // $c3 = new \stdClass(); + // $c3->foo = $tag; - [[], '[]', null], - [[], '[]', SerializationContext::create()->setInitialType('array')], - [[], '[]', SerializationContext::create()->setInitialType('array')], - [[], '{}', SerializationContext::create()->setInitialType('array')], + // return [ - [[1, 2], '[1,2]', SerializationContext::create()->setInitialType('array')], - [[1 => 1, 2 => 2], '{"1":1,"2":2}', SerializationContext::create()->setInitialType('array')], - [[1 => 1, 2 => 2], '[1,2]', SerializationContext::create()->setInitialType('array')], - [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], - [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('list')], + // [[$c1], '[{}]', SerializationContext::create()->setInitialType('array')], - [[1 => 'a', 2 => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], - [['a' => 'a', 'b' => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], + // [[$c2], '[{"foo":"bar"}]', SerializationContext::create()->setInitialType('array')], - [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], - [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], - [[1, 2], '{"0":"1","1":"2"}', SerializationContext::create()->setInitialType('array')], + // [[$tag], '[{"name":"tag"}]', SerializationContext::create()->setInitialType('array')], - [['a', 'b'], '{"0":"a","1":"b"}', SerializationContext::create()->setInitialType('array')], - [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', SerializationContext::create()->setInitialType('array')], + // [[$c1], '{"0":{}}', SerializationContext::create()->setInitialType('array')], + // [[$c2], '{"0":{"foo":"bar"}}', SerializationContext::create()->setInitialType('array')], - [[15.6, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], - [[5.2 * 3, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], - ]; - } + // [[$c3], '{"0":{"foo":{"name":"tag"}}}', SerializationContext::create()->setInitialType('array')], + // [[$c3], '[{"foo":{"name":"tag"}}]', SerializationContext::create()->setInitialType('array')], - /** - * @dataProvider getTypeHintedArrays - */ - #[DataProvider('getTypeHintedArrays')] - public function testTypeHintedArraySerialization(array $array, string $expected, ?SerializationContext $context = null) - { - self::assertEquals($expected, $this->serialize($array, $context)); - } + // [[$tag], '{"0":{"name":"tag"}}', SerializationContext::create()->setInitialType('array')], + // ]; + // } - public static function getTypeHintedArraysAndStdClass() - { - $c1 = new \stdClass(); - $c2 = new \stdClass(); - $c2->foo = 'bar'; + // public function testDeserializingUnionProperties() + // { + // if (PHP_VERSION_ID < 80000) { + // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - $tag = new Tag('tag'); + // return; + // } - $c3 = new \stdClass(); - $c3->foo = $tag; + // $object = new UnionTypedProperties(10000); + // self::assertEquals($object, $this->deserialize(static::getContent('data_integer'), UnionTypedProperties::class)); - return [ + // $object = new UnionTypedProperties(1.236); + // self::assertEquals($object, $this->deserialize(static::getContent('data_float'), UnionTypedProperties::class)); - [[$c1], '[{}]', SerializationContext::create()->setInitialType('array')], + // $object = new UnionTypedProperties(false); + // self::assertEquals($object, $this->deserialize(static::getContent('data_bool'), UnionTypedProperties::class)); - [[$c2], '[{"foo":"bar"}]', SerializationContext::create()->setInitialType('array')], + // $object = new UnionTypedProperties('foo'); + // self::assertEquals($object, $this->deserialize(static::getContent('data_string'), UnionTypedProperties::class)); + // } - [[$tag], '[{"name":"tag"}]', SerializationContext::create()->setInitialType('array')], + // public function testDeserializingComplexUnionProperties() + // { + // if (PHP_VERSION_ID < 80000) { + // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - [[$c1], '{"0":{}}', SerializationContext::create()->setInitialType('array')], - [[$c2], '{"0":{"foo":"bar"}}', SerializationContext::create()->setInitialType('array')], + // return; + // } - [[$c3], '{"0":{"foo":{"name":"tag"}}}', SerializationContext::create()->setInitialType('array')], - [[$c3], '[{"foo":{"name":"tag"}}]', SerializationContext::create()->setInitialType('array')], + // $authorUnion = new ComplexUnionTypedProperties(new Author('foo')); + // self::assertEquals($authorUnion, $this->deserialize(static::getContent('data_author'), ComplexUnionTypedProperties::class)); + + // $commentUnion = new ComplexUnionTypedProperties(new Comment(new Author('foo'), 'bar')); + // $deserialized = $this->deserialize(static::getContent('data_comment'), ComplexUnionTypedProperties::class); - [[$tag], '{"0":{"name":"tag"}}', SerializationContext::create()->setInitialType('array')], - ]; - } + // self::assertEquals($commentUnion, $deserialized); + // } - public function testDeserializingUnionProperties() + public function testDeserializingComplexDiscriminatedUnionProperties() { if (PHP_VERSION_ID < 80000) { $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); @@ -436,39 +479,34 @@ public function testDeserializingUnionProperties() return; } - $object = new UnionTypedProperties(10000); - self::assertEquals($object, $this->deserialize(static::getContent('data_integer'), UnionTypedProperties::class)); - - $object = new UnionTypedProperties(1.236); - self::assertEquals($object, $this->deserialize(static::getContent('data_float'), UnionTypedProperties::class)); + $authorUnion = new ComplexDiscriminatedUnion(new DiscriminatedAuthor('foo')); + self::assertEquals($authorUnion, $this->deserialize(static::getContent('data_discriminated_author'), ComplexDiscriminatedUnion::class)); + + $commentUnion = new ComplexDiscriminatedUnion(new DiscriminatedComment(new Author('foo'), 'bar')); + $deserialized = $this->deserialize(static::getContent('data_discriminated_comment'), ComplexDiscriminatedUnion::class); - $object = new UnionTypedProperties(false); - self::assertEquals($object, $this->deserialize(static::getContent('data_bool'), UnionTypedProperties::class)); - - $object = new UnionTypedProperties('foo'); - self::assertEquals($object, $this->deserialize(static::getContent('data_string'), UnionTypedProperties::class)); + self::assertEquals($commentUnion, $deserialized); } + // public function testSerializeUnionProperties() + // { + // if (PHP_VERSION_ID < 80000) { + // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - public function testSerializeUnionProperties() - { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - - return; - } + // return; + // } - $serialized = $this->serialize(new UnionTypedProperties(10000)); - self::assertEquals(static::getContent('data_integer'), $serialized); - } + // $serialized = $this->serialize(new UnionTypedProperties(10000)); + // self::assertEquals(static::getContent('data_integer'), $serialized); + // } /** * @dataProvider getTypeHintedArraysAndStdClass */ - #[DataProvider('getTypeHintedArraysAndStdClass')] - public function testTypeHintedArrayAncdtdClassSerialization(array $array, string $expected, ?SerializationContext $context = null) - { - self::assertEquals($expected, $this->serialize($array, $context)); - } + // #[DataProvider('getTypeHintedArraysAndStdClass')] + // public function testTypeHintedArrayAncdtdClassSerialization(array $array, string $expected, ?SerializationContext $context = null) + // { + // self::assertEquals($expected, $this->serialize($array, $context)); + // } protected function getFormat() { From d2a5090987bf883c73f8f53353f4268d545fdda2 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Mon, 8 Jul 2024 16:26:44 -0400 Subject: [PATCH 02/14] iter --- src/Annotation/UnionDiscriminator.php | 9 +- .../DeserializationGraphNavigator.php | 2 +- src/Handler/UnionHandler.php | 50 +- src/JsonDeserializationStrictVisitor.php | 15 + .../Driver/AnnotationOrAttributeDriver.php | 3 + src/Metadata/Driver/TypedPropertiesDriver.php | 54 +- src/Metadata/PropertyMetadata.php | 12 +- src/XmlDeserializationVisitor.php | 16 + tests/Fixtures/DiscriminatedAuthor.php | 2 +- tests/Fixtures/DiscriminatedComment.php | 2 +- tests/Fixtures/MoreSpecificAuthor.php | 41 + .../ComplexDiscriminatedUnion.php | 5 +- .../ComplexUnionTypedProperties.php | 3 +- .../Serializer/BaseSerializationTestCase.php | 3400 ++++++++--------- tests/Serializer/JsonSerializationTest.php | 594 +-- 15 files changed, 2162 insertions(+), 2046 deletions(-) create mode 100644 tests/Fixtures/MoreSpecificAuthor.php diff --git a/src/Annotation/UnionDiscriminator.php b/src/Annotation/UnionDiscriminator.php index 5d417019c..f062b3064 100644 --- a/src/Annotation/UnionDiscriminator.php +++ b/src/Annotation/UnionDiscriminator.php @@ -6,23 +6,18 @@ /** * @Annotation - * @Target({"PROPERTY" }) + * @Target({"PROPERTY"}) */ #[\Attribute(\Attribute::TARGET_PROPERTY)] final class UnionDiscriminator implements SerializerAttribute { use AnnotationUtilsTrait; - /** @var array */ - public $map = []; - /** @var string */ public $field = 'type'; - public function __construct(array $values = [], string $field = 'type', array $map = []) + public function __construct(array $values = [], string $field = 'type') { - echo '\n\n\nITS HAPPENING!\n\n\n'; $this->loadAnnotationParameters(get_defined_vars()); - } } diff --git a/src/GraphNavigator/DeserializationGraphNavigator.php b/src/GraphNavigator/DeserializationGraphNavigator.php index 2a8f841e0..4ee9b2ba0 100644 --- a/src/GraphNavigator/DeserializationGraphNavigator.php +++ b/src/GraphNavigator/DeserializationGraphNavigator.php @@ -198,7 +198,7 @@ public function accept($data, ?array $type = null) $this->visitor->startVisitingObject($metadata, $object, $type); foreach ($metadata->propertyMetadata as $propertyMetadata) { - $allowsNull = $this->allowsNull($propertyMetadata->type); + $allowsNull = $propertyMetadata->type == null ? true : $this->allowsNull($propertyMetadata->type); if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) { continue; } diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 995fcb5f5..54678a143 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -71,32 +71,37 @@ private function deserializeType(DeserializationVisitorInterface $visitor, mixed { $alternativeName = null; - foreach ($this->reorderTypes($type)['params'] as $possibleType) { + foreach ($type['params'] as $possibleType) { if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { continue; } $propertyMetadata = $context->getMetadataStack()->top(); - var_dump($propertyMetadata); - - - try { - $exists = class_exists($possibleType['name']); - $previousVisitorRequireSetting = $visitor->getRequireAllRequiredProperties(); - if ($this->requireAllProperties) { - $visitor->setRequireAllRequiredProperties($this->requireAllProperties); - } - - $accept = $context->getNavigator()->accept($data, $possibleType); - if ($this->requireAllProperties) { - $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); + $discriminatorAttribute = $propertyMetadata->unionDiscriminatorAttribute; + if ($discriminatorAttribute !== null) { + $finalType = [ + 'name' => $data[$discriminatorAttribute], + 'params' => [] + ]; + return $context->getNavigator()->accept($data, $finalType); + } else { + try { + $previousVisitorRequireSetting = $visitor->getRequireAllRequiredProperties(); + if ($this->requireAllProperties) { + $visitor->setRequireAllRequiredProperties($this->requireAllProperties); + } + + $accept = $context->getNavigator()->accept($data, $possibleType); + if ($this->requireAllProperties) { + $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); + } + + return $accept; + } catch (NonVisitableTypeException $e) { + continue; + } catch (PropertyMissingException $e) { + continue; } - - return $accept; - } catch (NonVisitableTypeException $e) { - continue; - } catch (PropertyMissingException $e) { - continue; } } @@ -111,11 +116,8 @@ private function matchSimpleType(mixed $data, array $type, Context $context) continue; } try { - $exists = class_exists($possibleType['name']); - if ($this->requireAllProperties) { - } - $accept = $context->getNavigator()->accept($data, $possibleType); + return $accept; } catch (NonVisitableTypeException $e) { continue; diff --git a/src/JsonDeserializationStrictVisitor.php b/src/JsonDeserializationStrictVisitor.php index 3c4e69a85..3fc1ca205 100644 --- a/src/JsonDeserializationStrictVisitor.php +++ b/src/JsonDeserializationStrictVisitor.php @@ -19,12 +19,27 @@ final class JsonDeserializationStrictVisitor extends AbstractVisitor implements /** @var JsonDeserializationVisitor */ private $wrappedDeserializationVisitor; + /** + * THIS IS ONLY USED FOR UNION DESERIALIZATION WHICH IS NOT SUPPORTED IN XML + * @var bool + */ + private $requireAllRequiredProperties = false; + public function __construct( int $options = 0, int $depth = 512 ) { $this->wrappedDeserializationVisitor = new JsonDeserializationVisitor($options, $depth); } + public function setRequireAllRequiredProperties(bool $requireAllRequiredProperties): void + { + $this->requireAllRequiredProperties = $requireAllRequiredProperties; + } + + public function getRequireAllRequiredProperties(): bool + { + return $this->requireAllRequiredProperties; + } public function setNavigator(GraphNavigatorInterface $navigator): void { diff --git a/src/Metadata/Driver/AnnotationOrAttributeDriver.php b/src/Metadata/Driver/AnnotationOrAttributeDriver.php index 92becb724..0b55c30a4 100644 --- a/src/Metadata/Driver/AnnotationOrAttributeDriver.php +++ b/src/Metadata/Driver/AnnotationOrAttributeDriver.php @@ -36,6 +36,7 @@ use JMS\Serializer\Annotation\XmlNamespace; use JMS\Serializer\Annotation\XmlRoot; use JMS\Serializer\Annotation\XmlValue; +use JMS\Serializer\Annotation\UnionDiscriminator; use JMS\Serializer\Exception\InvalidMetadataException; use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface; use JMS\Serializer\Metadata\ClassMetadata; @@ -258,6 +259,8 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat $propertyMetadata->xmlAttributeMap = true; } elseif ($annot instanceof MaxDepth) { $propertyMetadata->maxDepth = $annot->depth; + } elseif ($annot instanceof UnionDiscriminator) { + $propertyMetadata->setUnionDiscriminator($annot->field); } } diff --git a/src/Metadata/Driver/TypedPropertiesDriver.php b/src/Metadata/Driver/TypedPropertiesDriver.php index 99f93cbaa..f5900f5f0 100644 --- a/src/Metadata/Driver/TypedPropertiesDriver.php +++ b/src/Metadata/Driver/TypedPropertiesDriver.php @@ -48,19 +48,43 @@ public function __construct(DriverInterface $delegate, ?ParserInterface $typePar } /** - * ReflectionUnionType::getTypes() returns the types sorted according to these rules: - * - Classes, interfaces, traits, iterable (replaced by Traversable), ReflectionIntersectionType objects, parent and self: - * these types will be returned first, in the order in which they were declared. - * - static and all built-in types (iterable replaced by array) will come next. They will always be returned in this order: - * static, callable, array, string, int, float, bool (or false or true), null. + * In order to deserialize non-discriminated unions, each possible type is attempted in turn. + * Therefore, the types must be ordered from most specific to least specific, so that the most specific type is attempted first. + * + * ReflectionUnionType::getTypes() does not return types in that order, so we need to reorder them. * - * For determining types of primitives, it is necessary to reorder primitives so that they are tested from lowest specificity to highest: - * i.e. null, true, false, int, float, bool, string + * This method reorders the types in the following order: + * - primitives in speficity order: null, true, false, int, float, bool, string + * - classes and interaces in order of most number of required properties */ private function reorderTypes(array $type): array { + $self = $this; if ($type['params']) { - uasort($type['params'], static function ($a, $b) { + uasort($type['params'], function ($a, $b) use ($self) { + if (\class_exists($a['name']) && \class_exists($b['name'])) { + $aMetadata = $self->loadMetadataForClass(new \ReflectionClass($a['name'])); + $bMetadata = $self->loadMetadataForClass(new \ReflectionClass($b['name'])); + $aRequiredPropertyCount = 0; + $bRequiredPropertyCount = 0; + foreach ($aMetadata->propertyMetadata as $propertyMetadata) { + if (!$self->allowsNull($propertyMetadata->type)) { + $aRequiredPropertyCount++; + } + } + foreach ($bMetadata->propertyMetadata as $propertyMetadata) { + if (!$self->allowsNull($propertyMetadata->type)) { + $bRequiredPropertyCount++; + } + } + return $bRequiredPropertyCount <=> $aRequiredPropertyCount; + } + if(\class_exists($a['name'])) { + return 1; + } + if(\class_exists($b['name'])) { + return -1; + } $order = ['null' => 0, 'true' => 1, 'false' => 2, 'bool' => 3, 'int' => 4, 'float' => 5, 'string' => 6]; return ($order[$a['name']] ?? 7) <=> ($order[$b['name']] ?? 7); @@ -69,6 +93,20 @@ private function reorderTypes(array $type): array return $type; } + + private function allowsNull(array $type) { + $allowsNull = false; + if ($type['name'] === 'union') { + foreach($type['params'] as $param) { + if ($param['name'] === 'NULL') { + $allowsNull = true; + } + } + } elseif($type['name'] === 'NULL') { + $allowsNull = true; + } + return $allowsNull; + } private function getDefaultWhiteList(): array { diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index 5e63d00d8..f6f9c703d 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -35,9 +35,9 @@ class PropertyMetadata extends BasePropertyMetadata /** - * @var array + * @var string|null */ - public $unionDiscriminator; + public $unionDiscriminatorAttribute; /** * @var array|null @@ -201,6 +201,10 @@ public function setAccessor(string $type, ?string $getter = null, ?string $sette $this->getter = $getter; $this->setter = $setter; } + public function setUnionDiscriminator(string $field): void + { + $this->unionDiscriminatorAttribute = $field; + } public function setType(array $type): void { @@ -230,7 +234,7 @@ protected function serializeToArray(): array $this->untilVersion, $this->groups, $this->serializedName, - $this->unionDiscriminator, + $this->unionDiscriminatorAttribute, $this->type, $this->xmlCollection, $this->xmlCollectionInline, @@ -265,7 +269,7 @@ protected function unserializeFromArray(array $data): void $this->untilVersion, $this->groups, $this->serializedName, - $this->unionDiscriminator, + $this->unionDiscriminatorAttribute, $this->type, $this->xmlCollection, $this->xmlCollectionInline, diff --git a/src/XmlDeserializationVisitor.php b/src/XmlDeserializationVisitor.php index 1efa3c90c..64b803421 100644 --- a/src/XmlDeserializationVisitor.php +++ b/src/XmlDeserializationVisitor.php @@ -54,6 +54,12 @@ final class XmlDeserializationVisitor extends AbstractVisitor implements NullAwa */ private $options; + /** + * THIS IS ONLY USED FOR UNION DESERIALIZATION WHICH IS NOT SUPPORTED IN XML + * @var bool + */ + private $requireAllRequiredProperties = false; + public function __construct( bool $disableExternalEntities = true, array $doctypeAllowList = [], @@ -65,6 +71,16 @@ public function __construct( $this->disableExternalEntities = $disableExternalEntities; $this->doctypeAllowList = $doctypeAllowList; $this->options = $options; + + } + public function setRequireAllRequiredProperties(bool $requireAllRequiredProperties): void + { + $this->requireAllRequiredProperties = $requireAllRequiredProperties; + } + + public function getRequireAllRequiredProperties(): bool + { + return $this->requireAllRequiredProperties; } /** diff --git a/tests/Fixtures/DiscriminatedAuthor.php b/tests/Fixtures/DiscriminatedAuthor.php index 7da5c1a91..ea0ddb1ae 100644 --- a/tests/Fixtures/DiscriminatedAuthor.php +++ b/tests/Fixtures/DiscriminatedAuthor.php @@ -22,7 +22,7 @@ class DiscriminatedAuthor * @Type("string") */ #[Type(name: 'string')] - private $type = 'author'; + private $type = 'JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor'; public function __construct($name) { diff --git a/tests/Fixtures/DiscriminatedComment.php b/tests/Fixtures/DiscriminatedComment.php index a2ac3505e..49661f997 100644 --- a/tests/Fixtures/DiscriminatedComment.php +++ b/tests/Fixtures/DiscriminatedComment.php @@ -24,7 +24,7 @@ class DiscriminatedComment * @Type("string") */ #[Type(name: 'string')] - private $type = 'comment'; + private $type = 'JMS\Serializer\Tests\Fixtures\DiscriminatedComment'; public function __construct(?Author $author, $text) diff --git a/tests/Fixtures/MoreSpecificAuthor.php b/tests/Fixtures/MoreSpecificAuthor.php new file mode 100644 index 000000000..c48974712 --- /dev/null +++ b/tests/Fixtures/MoreSpecificAuthor.php @@ -0,0 +1,41 @@ +name = $name; + $this->isMoreSpecific = $isMoreSpecific; + } + + public function getName() + { + return $this->name; + } + + public function getIsMoreSpecific() + { + return $this->isMoreSpecific; + } +} diff --git a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php index 9466162bc..b60227b49 100644 --- a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php +++ b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php @@ -10,10 +10,7 @@ class ComplexDiscriminatedUnion { - #[UnionDiscriminator(field: 'type', map: [ - 'author' => 'JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor', - 'comment' => 'JMS\Serializer\Tests\Fixtures\DiscriminatedComment', - ])] + #[UnionDiscriminator(field: 'type')] private DiscriminatedAuthor|DiscriminatedComment $data; public function __construct($data) diff --git a/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php b/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php index 6f6c007a1..62eb0d197 100644 --- a/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php +++ b/tests/Fixtures/TypedProperties/ComplexUnionTypedProperties.php @@ -6,10 +6,11 @@ use JMS\Serializer\Tests\Fixtures\Author; use JMS\Serializer\Tests\Fixtures\Comment; +use JMS\Serializer\Tests\Fixtures\MoreSpecificAuthor; class ComplexUnionTypedProperties { - private Author|Comment $data; + private Author|Comment|MoreSpecificAuthor $data; public function __construct($data) { diff --git a/tests/Serializer/BaseSerializationTestCase.php b/tests/Serializer/BaseSerializationTestCase.php index b3ed4ba78..c375f8dfc 100644 --- a/tests/Serializer/BaseSerializationTestCase.php +++ b/tests/Serializer/BaseSerializationTestCase.php @@ -180,131 +180,131 @@ abstract class BaseSerializationTestCase extends TestCase protected $objectConstructor; protected $accessorStrategy; - // public function testSerializeNullArray() - // { - // $arr = ['foo' => 'bar', 'baz' => null, null]; - - // self::assertEquals( - // static::getContent('nullable'), - // $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), - // ); - // } - - // public function testSerializeNullRoot() - // { - // $context = SerializationContext::create() - // ->setAttribute('allows_root_null', true); - - // self::assertEquals( - // static::getContent('nullable_root'), - // $this->serializer->serialize(null, $this->getFormat(), $context), - // ); - // } - - // public function testNoMetadataNeededWhenDeSerializingNotUsedProperty() - // { - // $object = $this->deserialize(static::getContent('ParentNoMetadataChildObject'), ParentNoMetadataChildObject::class); - // assert($object instanceof ParentNoMetadataChildObject); - - // self::assertSame('John', $object->bar); - // self::assertNull($object->foo); - // } - - // public function testDeserializeObjectWithMissingTypedArrayProp() - // { - // $dObj = $this->serializer->deserialize( - // static::getContent('empty_object'), - // ObjectWithTypedArraySetter::class, - // $this->getFormat(), - // ); - // assert($dObj instanceof ObjectWithTypedArraySetter); - - // self::assertInstanceOf(ObjectWithTypedArraySetter::class, $dObj); - - // self::assertSame([], $dObj->getEmpty()); - // } - - // public function testSerializeNullArrayExcludingNulls() - // { - // $arr = ['foo' => 'bar', 'baz' => null, null]; - - // self::assertEquals( - // static::getContent('nullable_skip'), - // $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(false)), - // ); - // } - - // public function testObjectUsingTypeCasting() - // { - // $typeAliasing = new ObjectUsingTypeCasting(); - // $typeAliasing->asString = new ObjectWithToString('8'); - - // self::assertEquals( - // static::getContent('type_casting'), - // $this->serialize($typeAliasing), - // ); - // } - - // public function testSerializeNullObject() - // { - // $obj = new ObjectWithNullProperty('foo', 'bar'); - - // self::assertEquals( - // static::getContent('simple_object_nullable'), - // $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), - // ); - // } - - // public function testDeserializeNullObject() - // { - // if (!$this->hasDeserializer()) { - // $this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat())); - // } - - // $obj = new ObjectWithNullProperty('foo', 'bar'); - - // $dObj = $this->serializer->deserialize( - // static::getContent('simple_object_nullable'), - // ObjectWithNullProperty::class, - // $this->getFormat(), - // ); - // assert($dObj instanceof ObjectWithNullProperty); - - // self::assertEquals($obj, $dObj); - // self::assertNull($dObj->getNullProperty()); - // } + public function testSerializeNullArray() + { + $arr = ['foo' => 'bar', 'baz' => null, null]; + + self::assertEquals( + static::getContent('nullable'), + $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), + ); + } + + public function testSerializeNullRoot() + { + $context = SerializationContext::create() + ->setAttribute('allows_root_null', true); + + self::assertEquals( + static::getContent('nullable_root'), + $this->serializer->serialize(null, $this->getFormat(), $context), + ); + } + + public function testNoMetadataNeededWhenDeSerializingNotUsedProperty() + { + $object = $this->deserialize(static::getContent('ParentNoMetadataChildObject'), ParentNoMetadataChildObject::class); + assert($object instanceof ParentNoMetadataChildObject); + + self::assertSame('John', $object->bar); + self::assertNull($object->foo); + } + + public function testDeserializeObjectWithMissingTypedArrayProp() + { + $dObj = $this->serializer->deserialize( + static::getContent('empty_object'), + ObjectWithTypedArraySetter::class, + $this->getFormat(), + ); + assert($dObj instanceof ObjectWithTypedArraySetter); + + self::assertInstanceOf(ObjectWithTypedArraySetter::class, $dObj); + + self::assertSame([], $dObj->getEmpty()); + } + + public function testSerializeNullArrayExcludingNulls() + { + $arr = ['foo' => 'bar', 'baz' => null, null]; + + self::assertEquals( + static::getContent('nullable_skip'), + $this->serializer->serialize($arr, $this->getFormat(), SerializationContext::create()->setSerializeNull(false)), + ); + } + + public function testObjectUsingTypeCasting() + { + $typeAliasing = new ObjectUsingTypeCasting(); + $typeAliasing->asString = new ObjectWithToString('8'); + + self::assertEquals( + static::getContent('type_casting'), + $this->serialize($typeAliasing), + ); + } + + public function testSerializeNullObject() + { + $obj = new ObjectWithNullProperty('foo', 'bar'); + + self::assertEquals( + static::getContent('simple_object_nullable'), + $this->serializer->serialize($obj, $this->getFormat(), SerializationContext::create()->setSerializeNull(true)), + ); + } + + public function testDeserializeNullObject() + { + if (!$this->hasDeserializer()) { + $this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat())); + } + + $obj = new ObjectWithNullProperty('foo', 'bar'); + + $dObj = $this->serializer->deserialize( + static::getContent('simple_object_nullable'), + ObjectWithNullProperty::class, + $this->getFormat(), + ); + assert($dObj instanceof ObjectWithNullProperty); + + self::assertEquals($obj, $dObj); + self::assertNull($dObj->getNullProperty()); + } // /** // * @dataProvider getTypes // */ - // #[DataProvider('getTypes')] - // public function testNull($type) - // { - // if ($this->hasDeserializer()) { - // self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); - // } + #[DataProvider('getTypes')] + public function testNull($type) + { + if ($this->hasDeserializer()) { + self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); + } - // // this is the default, but we want to be explicit here - // $context = SerializationContext::create()->setSerializeNull(false); + // this is the default, but we want to be explicit here + $context = SerializationContext::create()->setSerializeNull(false); - // $this->expectException(NotAcceptableException::class); + $this->expectException(NotAcceptableException::class); - // $this->serialize(null, $context); - // } + $this->serialize(null, $context); + } // /** // * @dataProvider getTypes // */ - // #[DataProvider('getTypes')] - // public function testNullAllowed($type) - // { - // $context = SerializationContext::create()->setSerializeNull(true); - // self::assertEquals(static::getContent('null'), $this->serialize(null, $context), $type); + #[DataProvider('getTypes')] + public function testNullAllowed($type) + { + $context = SerializationContext::create()->setSerializeNull(true); + self::assertEquals(static::getContent('null'), $this->serialize(null, $context), $type); - // if ($this->hasDeserializer()) { - // self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); - // } - // } + if ($this->hasDeserializer()) { + self::assertEquals(null, $this->deserialize(static::getContent('null'), $type)); + } + } public static function getTypes() { @@ -319,166 +319,166 @@ public static function getTypes() ]; } - // public function testString() - // { - // self::assertEquals(static::getContent('string'), $this->serialize('foo')); + public function testString() + { + self::assertEquals(static::getContent('string'), $this->serialize('foo')); - // if ($this->hasDeserializer()) { - // self::assertEquals('foo', $this->deserialize(static::getContent('string'), 'string')); - // } - // } + if ($this->hasDeserializer()) { + self::assertEquals('foo', $this->deserialize(static::getContent('string'), 'string')); + } + } - // public function testExcludeIfOnClass() - // { - // $accountNotExpired = new PersonAccount(); - // $accountNotExpired->name = 'Not expired account'; - // $accountNotExpired->expired = false; + public function testExcludeIfOnClass() + { + $accountNotExpired = new PersonAccount(); + $accountNotExpired->name = 'Not expired account'; + $accountNotExpired->expired = false; - // $accountExpired = new PersonAccount(); - // $accountExpired->name = 'Expired account'; - // $accountExpired->expired = true; + $accountExpired = new PersonAccount(); + $accountExpired->name = 'Expired account'; + $accountExpired->expired = true; - // $accounts = [$accountExpired, $accountNotExpired]; + $accounts = [$accountExpired, $accountNotExpired]; - // $language = new ExpressionLanguage(); + $language = new ExpressionLanguage(); - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - // $serializer = $builder->build(); + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + $serializer = $builder->build(); - // $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); - // $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); + $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); + $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); - // $this->assertEquals(1, count($deserialized)); - // $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); - // } + $this->assertEquals(1, count($deserialized)); + $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); + } - // public function testEnumDisabledByDefault() - // { - // if (PHP_VERSION_ID < 80100) { - // self::markTestSkipped('No ENUM support'); - // } + public function testEnumDisabledByDefault() + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('No ENUM support'); + } - // $builder = SerializerBuilder::create(); - // $serializer = $builder->build(); - // $o = new ObjectWithAutoDetectEnums(); - // $serialized = $serializer->serialize($o, $this->getFormat()); + $builder = SerializerBuilder::create(); + $serializer = $builder->build(); + $o = new ObjectWithAutoDetectEnums(); + $serialized = $serializer->serialize($o, $this->getFormat()); - // self::assertEquals(static::getContent('object_with_enums_disabled'), $serialized); - // } + self::assertEquals(static::getContent('object_with_enums_disabled'), $serialized); + } - // public function testEnum() - // { - // if (PHP_VERSION_ID < 80100) { - // self::markTestSkipped('No ENUM support'); - // } + public function testEnum() + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('No ENUM support'); + } - // $o = new ObjectWithEnums(); + $o = new ObjectWithEnums(); - // $serialized = $this->serialize($o); + $serialized = $this->serialize($o); - // self::assertEquals(static::getContent('object_with_enums'), $serialized); + self::assertEquals(static::getContent('object_with_enums'), $serialized); - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize($serialized, ObjectWithEnums::class); - // self::assertEquals($o, $deserialized); - // } - // } + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize($serialized, ObjectWithEnums::class); + self::assertEquals($o, $deserialized); + } + } - // public function testEnumAutoDetectArrayOfEnums() - // { - // if (PHP_VERSION_ID < 80100) { - // self::markTestSkipped('No ENUM support'); - // } + public function testEnumAutoDetectArrayOfEnums() + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('No ENUM support'); + } - // $o = new ObjectWithAutoDetectEnums(); + $o = new ObjectWithAutoDetectEnums(); - // $serialized = $this->serialize($o); + $serialized = $this->serialize($o); - // self::assertEquals(static::getContent('object_with_autodetect_enums'), $serialized); - // } + self::assertEquals(static::getContent('object_with_autodetect_enums'), $serialized); + } - // public function testExcludeIfOnClassWithParent() - // { - // $accountNotExpired = new PersonAccountWithParent(); - // $accountNotExpired->name = 'Not expired account'; - // $accountNotExpired->expired = false; + public function testExcludeIfOnClassWithParent() + { + $accountNotExpired = new PersonAccountWithParent(); + $accountNotExpired->name = 'Not expired account'; + $accountNotExpired->expired = false; - // $accountExpired = new PersonAccountWithParent(); - // $accountExpired->name = 'Expired account'; - // $accountExpired->expired = true; + $accountExpired = new PersonAccountWithParent(); + $accountExpired->name = 'Expired account'; + $accountExpired->expired = true; - // $accounts = [$accountNotExpired, $accountExpired]; + $accounts = [$accountNotExpired, $accountExpired]; - // $language = new ExpressionLanguage(); + $language = new ExpressionLanguage(); - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - // $serializer = $builder->build(); + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + $serializer = $builder->build(); - // $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); - // $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); + $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountWithParent::class)); + $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountWithParent::class), $this->getFormat()); - // $this->assertEquals(1, count($deserialized)); - // $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); - // } + $this->assertEquals(1, count($deserialized)); + $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); + } - // public function testExcludeIfOnParentClass() - // { - // $accountNotExpired = new PersonAccountOnParent(); - // $accountNotExpired->name = 'Not expired account'; - // $accountNotExpired->expired = false; + public function testExcludeIfOnParentClass() + { + $accountNotExpired = new PersonAccountOnParent(); + $accountNotExpired->name = 'Not expired account'; + $accountNotExpired->expired = false; - // $accountExpired = new PersonAccountOnParent(); - // $accountExpired->name = 'Expired account'; - // $accountExpired->expired = true; + $accountExpired = new PersonAccountOnParent(); + $accountExpired->name = 'Expired account'; + $accountExpired->expired = true; - // $accounts = [$accountNotExpired, $accountExpired]; + $accounts = [$accountNotExpired, $accountExpired]; - // $language = new ExpressionLanguage(); + $language = new ExpressionLanguage(); - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - // $serializer = $builder->build(); + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + $serializer = $builder->build(); - // $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountOnParent::class)); - // $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountOnParent::class), $this->getFormat()); + $serialized = $serializer->serialize($accounts, $this->getFormat(), null, sprintf('array<%s>', PersonAccountOnParent::class)); + $deserialized = $serializer->deserialize($serialized, sprintf('array<%s>', PersonAccountOnParent::class), $this->getFormat()); - // $this->assertEquals(1, count($deserialized)); - // $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); - // } + $this->assertEquals(1, count($deserialized)); + $this->assertEquals($accountNotExpired->name, $deserialized[0]->name); + } - // public function testExpressionExclusionNotConfigured() - // { - // $person = new PersonSecret(); - // $person->gender = 'f'; - // $person->name = 'mike'; + public function testExpressionExclusionNotConfigured() + { + $person = new PersonSecret(); + $person->gender = 'f'; + $person->name = 'mike'; - // $this->expectException(ExpressionLanguageRequiredException::class); - // $this->expectExceptionMessage('To use conditional exclude/expose in JMS\Serializer\Tests\Fixtures\PersonSecret you must configure the expression language.'); + $this->expectException(ExpressionLanguageRequiredException::class); + $this->expectExceptionMessage('To use conditional exclude/expose in JMS\Serializer\Tests\Fixtures\PersonSecret you must configure the expression language.'); - // $this->serialize($person); - // } + $this->serialize($person); + } - // public function testExpressionExclusionConfiguredWithDisjunctStrategy() - // { - // $person = new PersonSecret(); - // $person->gender = 'f'; - // $person->name = 'mike'; + public function testExpressionExclusionConfiguredWithDisjunctStrategy() + { + $person = new PersonSecret(); + $person->gender = 'f'; + $person->name = 'mike'; - // $language = new ExpressionLanguage(); - // $language->addFunction(new ExpressionFunction('show_data', static function () { - // return 'true'; - // }, static function () { - // return true; - // })); + $language = new ExpressionLanguage(); + $language->addFunction(new ExpressionFunction('show_data', static function () { + return 'true'; + }, static function () { + return true; + })); - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - // $serializer = $builder->build(); + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + $serializer = $builder->build(); - // self::assertEquals(static::getContent('person_secret_hide'), $serializer->serialize($person, $this->getFormat())); - // } + self::assertEquals(static::getContent('person_secret_hide'), $serializer->serialize($person, $this->getFormat())); + } public static function expressionFunctionProvider() { @@ -561,1524 +561,1524 @@ public static function expressionFunctionProvider() * * @dataProvider expressionFunctionProvider */ - // #[DataProvider('expressionFunctionProvider')] - // public function testExpressionExclusion($person, ExpressionFunction $function, $json) - // { - // $language = new ExpressionLanguage(); - // $language->addFunction($function); + #[DataProvider('expressionFunctionProvider')] + public function testExpressionExclusion($person, ExpressionFunction $function, $json) + { + $language = new ExpressionLanguage(); + $language->addFunction($function); - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); - // $serializer = $builder->build(); + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator(new ExpressionEvaluator($language)); + $serializer = $builder->build(); - // self::assertEquals(static::getContent($json), $serializer->serialize($person, $this->getFormat())); - // } + self::assertEquals(static::getContent($json), $serializer->serialize($person, $this->getFormat())); + } - // /** - // * @dataProvider getBooleans - // */ - // #[DataProvider('getBooleans')] - // public function testBooleans($strBoolean, $boolean) - // { - // self::assertEquals(static::getContent('boolean_' . $strBoolean), $this->serialize($boolean)); + /** + * @dataProvider getBooleans + */ + #[DataProvider('getBooleans')] + public function testBooleans($strBoolean, $boolean) + { + self::assertEquals(static::getContent('boolean_' . $strBoolean), $this->serialize($boolean)); - // if ($this->hasDeserializer()) { - // self::assertSame($boolean, $this->deserialize(static::getContent('boolean_' . $strBoolean), 'boolean')); - // } - // } + if ($this->hasDeserializer()) { + self::assertSame($boolean, $this->deserialize(static::getContent('boolean_' . $strBoolean), 'boolean')); + } + } - // public static function getBooleans() - // { - // return [['true', true], ['false', false]]; - // } + public static function getBooleans() + { + return [['true', true], ['false', false]]; + } - // /** - // * @dataProvider getNumerics - // */ - // #[DataProvider('getNumerics')] - // public function testNumerics($key, $value, $type) - // { - // self::assertSame(static::getContent($key), $this->serialize($value)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($value, $this->deserialize(static::getContent($key), $type)); - // } - // } - - // public static function getNumerics() - // { - // return [ - // ['integer', 1, 'integer'], - // ['float', 4.533, 'double'], - // ['float', 4.533, 'float'], - // ['float_trailing_zero', 1.0, 'double'], - // ['float_trailing_zero', 1.0, 'float'], - // ]; - // } - - // public function testSimpleInternalObject() - // { - // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - // $builder->setMetadataDirs([ - // 'JMS\Serializer\Tests\Fixtures' => __DIR__ . '/metadata/SimpleInternalObject', - // '' => __DIR__ . '/metadata/SimpleInternalObject', - // ]); - - // $this->serializer = $builder->build(); - - // $obj = new SimpleInternalObject('foo', 'bar'); - - // self::assertEquals(static::getContent('simple_object'), $this->serialize($obj)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); - // } - // } - - // public function testSimpleObject() - // { - // self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar'))); - - // if ($this->hasDeserializer()) { - // self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); - // } - // } - - // public function testSimpleObjectStaticProp() - // { - // self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObjectWithStaticProp('foo', 'bar'))); - - // if ($this->hasDeserializer()) { - // self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); - // } - // } - - // public function testArrayStrings() - // { - // $data = ['foo', 'bar']; - // self::assertEquals(static::getContent('array_strings'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('array_strings'), 'array')); - // } - // } - - // public function testArrayBooleans() - // { - // $data = [true, false]; - // self::assertEquals(static::getContent('array_booleans'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('array_booleans'), 'array')); - // } - // } - - // public function testArrayIntegers() - // { - // $data = [1, 3, 4]; - // self::assertEquals(static::getContent('array_integers'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('array_integers'), 'array')); - // } - // } - - // public function testArrayEmpty() - // { - // if ('xml' === $this->getFormat()) { - // $this->markTestSkipped('XML can\'t be tested for empty array'); - // } - - // $data = ['array' => []]; - // self::assertEquals(static::getContent('array_empty'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('array_empty'), 'array')); - // } - // } - - // public function testArrayFloats() - // { - // $data = [1.34, 3.0, 6.42]; - // self::assertEquals(static::getContent('array_floats'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('array_floats'), 'array')); - // } - // } - - // public function testArrayObjects() - // { - // $data = [new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')]; - // self::assertEquals(static::getContent('array_objects'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('array_objects'), 'array')); - // } - // } - - // public function testArrayListAndMapDifference() - // { - // $arrayData = [0 => 1, 2 => 2, 3 => 3]; // Misses key 1 - // $data = new ObjectWithIntListAndIntMap($arrayData, $arrayData); - - // self::assertEquals(static::getContent('array_list_and_map_difference'), $this->serialize($data)); - // } - - // public function testList(): void - // { - // if ('xml' === $this->getFormat()) { - // $this->markTestSkipped('XML can\'t be tested for list without value type'); - // } - - // $data = [1, 3, 4]; - // self::assertEquals(static::getContent('list'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('list'), 'list')); - // } - // } - - // public function testListEmpty(): void - // { - // if ('xml' === $this->getFormat()) { - // $this->markTestSkipped('XML can\'t be tested for empty list'); - // } - - // $data = []; - // self::assertEquals(static::getContent('list_empty'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('list_empty'), 'list')); - // } - // } - - // public function testListIntegers(): void - // { - // $data = [1, 3, 4]; - // self::assertEquals(static::getContent('list_integers'), $this->serialize($data)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($data, $this->deserialize(static::getContent('list_integers'), 'list')); - // } - // } - - // public function testCustomDateObject() - // { - // $data = new DateTimeContainer(new DateTimeCustomObject('2021-09-07')); - - // self::assertEquals(static::getContent('custom_datetimeinterface'), $this->serialize($data)); - // } - - // public function testDateTimeArrays() - // { - // $data = [ - // new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - // new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), - // ]; - - // $object = new DateTimeArraysObject($data, $data); - // $serializedObject = $this->serialize($object); - - // self::assertEquals(static::getContent('array_datetimes_object'), $serializedObject); - - // if ($this->hasDeserializer()) { - // $deserializedObject = $this->deserialize(static::getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject'); - // assert($deserializedObject instanceof DateTimeArraysObject); - - // /** 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. */ - // foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) { - // $dateTime->setTimezone(new \DateTimeZone('UTC')); - // } - - // foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) { - // $dateTime->setTimezone(new \DateTimeZone('UTC')); - // } - - // self::assertEquals($object, $deserializedObject); - // } - // } - - // public function testNamedDateTimeArrays() - // { - // $data = [ - // new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - // new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), - // ]; - - // $object = new NamedDateTimeArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); - // $serializedObject = $this->serialize($object); - - // self::assertEquals(static::getContent('array_named_datetimes_object'), $serializedObject); - - // if ($this->hasDeserializer()) { - // // skip XML deserialization - // if ('xml' === $this->getFormat()) { - // return; - // } - - // $deserializedObject = $this->deserialize(static::getContent('array_named_datetimes_object'), NamedDateTimeArraysObject::class); - // assert($deserializedObject instanceof NamedDateTimeArraysObject); - - // /** 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. */ - // foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { - // $dateTime->setTimezone(new \DateTimeZone('UTC')); - // } - - // self::assertEquals($object, $deserializedObject); - // } - // } - - // public function testNamedDateTimeImmutableArrays() - // { - // $data = [ - // new \DateTimeImmutable('2047-01-01 12:47:47', new \DateTimeZone('UTC')), - // new \DateTimeImmutable('2016-12-05 00:00:00', new \DateTimeZone('UTC')), - // ]; - - // $object = new NamedDateTimeImmutableArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); - // $serializedObject = $this->serialize($object); - - // self::assertEquals(static::getContent('array_named_datetimeimmutables_object'), $serializedObject); - - // if ($this->hasDeserializer()) { - // if ('xml' === $this->getFormat()) { - // $this->markTestSkipped('XML deserialization does not support key-val pairs mode'); - // } - - // $deserializedObject = $this->deserialize(static::getContent('array_named_datetimeimmutables_object'), NamedDateTimeImmutableArraysObject::class); - // assert($deserializedObject instanceof NamedDateTimeImmutableArraysObject); - - // /** 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. */ - // foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { - // $dateTime->setTimezone(new \DateTimeZone('UTC')); - // } - - // self::assertEquals($object, $deserializedObject); - // } - // } + /** + * @dataProvider getNumerics + */ + #[DataProvider('getNumerics')] + public function testNumerics($key, $value, $type) + { + self::assertSame(static::getContent($key), $this->serialize($value)); - // public function testArrayMixed() - // { - // self::assertEquals(static::getContent('array_mixed'), $this->serialize(['foo', 1, true, new SimpleObject('foo', 'bar'), [1, 3, true]])); - // } + if ($this->hasDeserializer()) { + self::assertEquals($value, $this->deserialize(static::getContent($key), $type)); + } + } - // /** - // * @dataProvider getDateTime - // * @group datetime - // */ - // #[DataProvider('getDateTime')] - // public function testDateTime($key, $value, $type) - // { - // self::assertEquals(static::getContent($key), $this->serialize($value)); + public static function getNumerics() + { + return [ + ['integer', 1, 'integer'], + ['float', 4.533, 'double'], + ['float', 4.533, 'float'], + ['float_trailing_zero', 1.0, 'double'], + ['float_trailing_zero', 1.0, 'float'], + ]; + } + + public function testSimpleInternalObject() + { + $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + $builder->setMetadataDirs([ + 'JMS\Serializer\Tests\Fixtures' => __DIR__ . '/metadata/SimpleInternalObject', + '' => __DIR__ . '/metadata/SimpleInternalObject', + ]); + + $this->serializer = $builder->build(); - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent($key), $type); + $obj = new SimpleInternalObject('foo', 'bar'); - // self::assertIsObject($deserialized); - // self::assertInstanceOf(get_class($value), $deserialized); - // self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); - // } - // } + self::assertEquals(static::getContent('simple_object'), $this->serialize($obj)); - public static function getDateTime() + if ($this->hasDeserializer()) { + self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); + } + } + + public function testSimpleObject() { - return [ - ['date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'], - ['date_time_multi_format', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), "DateTime<'Y-m-d', '', ['Y-m-d','Y-m-d\TH:i:sP']>"], - ]; + self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObject('foo', 'bar'))); + + if ($this->hasDeserializer()) { + self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); + } } - /** - * @dataProvider getDateTimeImmutable - * @group datetime - */ - // #[DataProvider('getDateTimeImmutable')] - // public function testDateTimeImmutable($key, $value, $type) - // { - // self::assertEquals(static::getContent($key), $this->serialize($value)); + public function testSimpleObjectStaticProp() + { + self::assertEquals(static::getContent('simple_object'), $this->serialize($obj = new SimpleObjectWithStaticProp('foo', 'bar'))); + + if ($this->hasDeserializer()) { + self::assertEquals($obj, $this->deserialize(static::getContent('simple_object'), get_class($obj))); + } + } - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent($key), $type); + public function testArrayStrings() + { + $data = ['foo', 'bar']; + self::assertEquals(static::getContent('array_strings'), $this->serialize($data)); - // self::assertIsObject($deserialized); - // self::assertInstanceOf(get_class($value), $deserialized); - // self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); - // } - // } + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('array_strings'), 'array')); + } + } - public static function getDateTimeImmutable() + public function testArrayBooleans() { - return [ - ['date_time_immutable', new \DateTimeImmutable('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTimeImmutable'], - ]; + $data = [true, false]; + self::assertEquals(static::getContent('array_booleans'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('array_booleans'), 'array')); + } } - // public function testTimestamp() - // { - // $value = new Timestamp(new \DateTime('2016-02-11 00:00:00', new \DateTimeZone('UTC'))); - // self::assertEquals(static::getContent('timestamp'), $this->serialize($value)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('timestamp'), Timestamp::class); - // self::assertEquals($value, $deserialized); - // self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); - - // $deserialized = $this->deserialize(static::getContent('timestamp_prev'), Timestamp::class); - // self::assertEquals($value, $deserialized); - // self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); - // } - // } - - // public function testDateInterval() - // { - // $duration = new \DateInterval('PT45M'); - - // self::assertEquals(static::getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat())); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('date_interval'), \DateInterval::class); - // self::assertEquals($duration, $deserialized); - // self::assertEquals($duration->i, $deserialized->i); - // } - // } + public function testArrayIntegers() + { + $data = [1, 3, 4]; + self::assertEquals(static::getContent('array_integers'), $this->serialize($data)); - // public function testBlogPost() - // { - // $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')); - // $post->addComment($comment = new Comment($author, 'foo')); - - // $post->addTag($tag1 = new Tag('tag1')); - // $post->addTag($tag2 = new Tag('tag2')); - - // self::assertEquals(static::getContent('blog_post'), $this->serialize($post)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('blog_post'), get_class($post)); - // self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); - // self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); - // self::assertFalse($this->getField($deserialized, 'published')); - // self::assertFalse($this->getField($deserialized, 'reviewed')); - // self::assertSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', $this->getField($deserialized, 'etag')); - // self::assertEquals(new ArrayCollection([$comment]), $this->getField($deserialized, 'comments')); - // self::assertEquals([$comment], $this->getField($deserialized, 'comments2')); - // self::assertEquals($author, $this->getField($deserialized, 'author')); - // self::assertEquals([$tag1, $tag2], $this->getField($deserialized, 'tag')); - // } - // } - - // public function testDeserializingNull() - // { - // $objectConstructor = new InitializedBlogPostConstructor(); - - // $builder = SerializerBuilder::create(); - // $builder->setObjectConstructor($objectConstructor); - // $this->serializer = $builder->build(); - - // $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')); - - // $this->setField($post, 'author', null); - // $this->setField($post, 'publisher', null); - - // self::assertEquals(static::getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true))); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create()); - - // self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); - // self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); - // self::assertFalse($this->getField($deserialized, 'published')); - // self::assertFalse($this->getField($deserialized, 'reviewed')); - // self::assertEquals(new ArrayCollection(), $this->getField($deserialized, 'comments')); - // self::assertEquals(null, $this->getField($deserialized, 'author')); - // } - // } - - // public function testSymfonyUid() - // { - // $uid = Uuid::fromString('66b3177c-e03b-4a22-9dee-ddd7d37a04d5'); - - // self::assertEquals(static::getContent('uid'), $this->serialize($uid)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('uid'), UuidV4::class); - - // self::assertInstanceOf(UuidV4::class, $deserialized); - // self::assertTrue($uid->equals($deserialized)); - // } - // } - - // public function testExpressionAuthor() - // { - // $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); - - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator($evaluator); - // $serializer = $builder->build(); - - // $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); - // self::assertEquals(static::getContent('author_expression'), $serializer->serialize($author, $this->getFormat())); - // } - - // public function testExpressionAuthorWithContextVars() - // { - // $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); - - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator($evaluator); - // $serializer = $builder->build(); - - // $author = new AuthorExpressionAccessContext('Ruud'); - // self::assertEquals(static::getContent('author_expression_context'), $serializer->serialize($author, $this->getFormat())); - // } - - // public function testExpressionAccessorStrategNotEnabled() - // { - // $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); - - // $this->expectException(ExpressionLanguageRequiredException::class); - // $this->expectExceptionMessage('The property firstName on JMS\Serializer\Tests\Fixtures\AuthorExpressionAccess requires the expression accessor strategy to be enabled.'); - - // $this->serialize($author); - // } - - // public function testReadOnly() - // { - // $author = new AuthorReadOnly(123, 'Ruud Kamphuis'); - // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - // self::assertNull($this->getField($deserialized, 'id')); - // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - // } - // } - - // public function testDeprecatedReadOnly() - // { - // $author = new AuthorDeprecatedReadOnly(123, 'Ruud Kamphuis'); - // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - // self::assertNull($this->getField($deserialized, 'id')); - // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - // } - // } - - // public function testReadOnlyClass() - // { - // $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis'); - // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - // self::assertNull($this->getField($deserialized, 'id')); - // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - // } - // } - - // public function testDeprecatedReadOnlyClass() - // { - // $author = new AuthorDeprecatedReadOnlyPerClass(123, 'Ruud Kamphuis'); - // self::assertEquals(static::getContent('readonly'), $this->serialize($author)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); - // self::assertNull($this->getField($deserialized, 'id')); - // self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); - // } - // } - - // public function testPrice() - // { - // $price = new Price(3); - // self::assertEquals(static::getContent('price'), $this->serialize($price)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('price'), get_class($price)); - // self::assertEquals(3, $this->getField($deserialized, 'price')); - // } - // } - - // public function testOrder() - // { - // $order = new Order(new Price(12.34)); - // self::assertEquals(static::getContent('order'), $this->serialize($order)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($order, $this->deserialize(static::getContent('order'), get_class($order))); - // } - // } - - // public function testCurrencyAwarePrice() - // { - // $price = new CurrencyAwarePrice(2.34); - // self::assertEquals(static::getContent('currency_aware_price'), $this->serialize($price)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($price, $this->deserialize(static::getContent('currency_aware_price'), get_class($price))); - // } - // } - - // public function testOrderWithCurrencyAwarePrice() - // { - // $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23)); - // self::assertEquals(static::getContent('order_with_currency_aware_price'), $this->serialize($order)); - - // if ($this->hasDeserializer()) { - // self::assertEquals($order, $this->deserialize(static::getContent('order_with_currency_aware_price'), get_class($order))); - // } - // } - - // public function testInline() - // { - // $inline = new InlineParent(); - - // $result = $this->serialize($inline); - // self::assertEquals(static::getContent('inline'), $result); - - // if ($this->hasDeserializer()) { - // self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); - // } - // } - - // public function testInlineEmptyChild() - // { - // $inline = new InlineParentWithEmptyChild(new InlineChildEmpty()); - // $result = $this->serialize($inline); - // self::assertEquals(static::getContent('inline_child_empty'), $result); - // if ($this->hasDeserializer()) { - // self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); - // } - // } - - // public function testEmptyChild() - // { - // // by empty object - // $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildEmpty()); - // self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); - - // // by nulls - // $inner = new InlineChild(); - // $inner->a = null; - // $inner->b = null; - // $inline = new ParentDoNotSkipWithEmptyChild($inner); - // self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); - - // // by exclusion strategy - // $context = SerializationContext::create()->setGroups(['Default']); - // $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildWithGroups()); - // self::assertEquals(static::getContent('empty_child'), $this->serialize($inline, $context)); - // } - - // public function testSkipEmptyChild() - // { - // // by empty object - // $inline = new ParentSkipWithEmptyChild(new InlineChildEmpty()); - // self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); - - // // by nulls - // $inner = new InlineChild(); - // $inner->a = null; - // $inner->b = null; - // $inline = new ParentSkipWithEmptyChild($inner); - // self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); - - // // by exclusion strategy - // $context = SerializationContext::create()->setGroups(['Default']); - // $inline = new ParentSkipWithEmptyChild(new InlineChildWithGroups()); - // self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline, $context)); - // } - - // public function testLog() - // { - // self::assertEquals(static::getContent('log'), $this->serialize($log = new Log())); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('log'), get_class($log)); - // self::assertEquals($log, $deserialized); - // } - // } - - // public function testSelfCircularReferenceCollection() - // { - // $object = new CircularReferenceCollection(); - // $object->collection[] = $object; - // self::assertEquals(static::getContent('circular_reference_collection'), $this->serialize($object)); - // } - - // public function testCircularReference() - // { - // $object = new CircularReferenceParent(); - // self::assertEquals(static::getContent('circular_reference'), $this->serialize($object)); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('circular_reference'), get_class($object)); - - // $col = $this->getField($deserialized, 'collection'); - // self::assertCount(2, $col); - // self::assertEquals('child1', $col[0]->getName()); - // self::assertEquals('child2', $col[1]->getName()); - // self::assertSame($deserialized, $col[0]->getParent()); - // self::assertSame($deserialized, $col[1]->getParent()); - - // $col = $this->getField($deserialized, 'anotherCollection'); - // self::assertCount(2, $col); - // self::assertEquals('child1', $col[0]->getName()); - // self::assertEquals('child2', $col[1]->getName()); - // self::assertSame($deserialized, $col[0]->getParent()); - // self::assertSame($deserialized, $col[1]->getParent()); - // } - // } - - // public function testLifecycleCallbacks() - // { - // $object = new ObjectWithLifecycleCallbacks(); - // self::assertEquals(static::getContent('lifecycle_callbacks'), $this->serialize($object)); - // self::assertNull($this->getField($object, 'name')); - - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('lifecycle_callbacks'), get_class($object)); - // self::assertEquals($object, $deserialized); - // } - // } - - // public function testFormErrors() - // { - // $errors = [ - // new FormError('This is the form error'), - // new FormError('Another error'), - // ]; - - // self::assertEquals(static::getContent('form_errors'), $this->serialize($errors)); - // } + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('array_integers'), 'array')); + } + } - // /** - // * @dataProvider initialFormTypeProvider - // */ - // #[DataProvider('initialFormTypeProvider')] - // public function testNestedFormErrors($type) - // { - // $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); + public function testArrayEmpty() + { + if ('xml' === $this->getFormat()) { + $this->markTestSkipped('XML can\'t be tested for empty array'); + } - // $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); - // $formConfigBuilder->setCompound(true); - // $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); - // $fooConfig = $formConfigBuilder->getFormConfig(); + $data = ['array' => []]; + self::assertEquals(static::getContent('array_empty'), $this->serialize($data)); - // $form = new Form($fooConfig); - // $form->addError(new FormError('This is the form error')); + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('array_empty'), 'array')); + } + } - // $formConfigBuilder = new FormConfigBuilder('bar', null, $dispatcher); - // $barConfig = $formConfigBuilder->getFormConfig(); - // $child = new Form($barConfig); - // $child->addError(new FormError('Error of the child form')); - // $form->add($child); + public function testArrayFloats() + { + $data = [1.34, 3.0, 6.42]; + self::assertEquals(static::getContent('array_floats'), $this->serialize($data)); - // $context = SerializationContext::create(); - // $context->setInitialType($type); + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('array_floats'), 'array')); + } + } - // self::assertEquals(static::getContent('nested_form_errors'), $this->serialize($form, $context)); - // } + public function testArrayObjects() + { + $data = [new SimpleObject('foo', 'bar'), new SimpleObject('baz', 'boo')]; + self::assertEquals(static::getContent('array_objects'), $this->serialize($data)); - // /** - // * @doesNotPerformAssertions - // * @dataProvider initialFormTypeProvider - // */ - // #[DataProvider('initialFormTypeProvider')] - // #[DoesNotPerformAssertions] - // public function testFormErrorsWithNonFormComponents($type) - // { - // $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); - - // $factoryBuilder = new FormFactoryBuilder(); - // $factoryBuilder->addType(new SubmitType()); - // $factoryBuilder->addType(new ButtonType()); - // $factory = $factoryBuilder->getFormFactory(); - - // $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); - // $formConfigBuilder->setFormFactory($factory); - // $formConfigBuilder->setCompound(true); - // $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); - // $fooConfig = $formConfigBuilder->getFormConfig(); - - // $form = new Form($fooConfig); - // $form->add('save', SubmitType::class); - - // $context = SerializationContext::create(); - // $context->setInitialType($type); - - // try { - // $this->serialize($form, $context); - // } catch (\Throwable $e) { - // self::assertTrue(false, 'Serialization should not throw an exception'); - // } - // } + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('array_objects'), 'array')); + } + } - public static function initialFormTypeProvider() + public function testArrayListAndMapDifference() { - return [ - [Form::class], - [FormInterface::class], - ]; + $arrayData = [0 => 1, 2 => 2, 3 => 3]; // Misses key 1 + $data = new ObjectWithIntListAndIntMap($arrayData, $arrayData); + + self::assertEquals(static::getContent('array_list_and_map_difference'), $this->serialize($data)); } - // public function testConstraintViolation() - // { - // $violation = new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null); - - // self::assertEquals(static::getContent('constraint_violation'), $this->serialize($violation)); - // } - - // public function testConstraintViolationList() - // { - // $violations = new ConstraintViolationList(); - // $violations->add(new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null)); - // $violations->add(new ConstraintViolation('Message of another violation', 'Message of another violation', [], null, 'bar', null)); - - // self::assertEquals(static::getContent('constraint_violation_list'), $this->serialize($violations)); - // } - - // public function testDoctrineProxy() - // { - // if (!class_exists(Version::class)) { - // $this->markTestSkipped('Doctrine is not available.'); - // } - - // $object = new SimpleObjectProxy('foo', 'bar'); - - // self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); - // } - - // public function testInitializedDoctrineProxy() - // { - // if (!class_exists(Version::class)) { - // $this->markTestSkipped('Doctrine is not available.'); - // } - - // $object = new SimpleObjectProxy('foo', 'bar'); - // $object->__load(); - - // self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); - // } - - // public function testCustomAccessor() - // { - // $post = new IndexedCommentsBlogPost(); - - // self::assertEquals(static::getContent('custom_accessor'), $this->serialize($post)); - // } - - // public function testMixedAccessTypes() - // { - // $object = new GetSetObject(); - - // self::assertEquals(static::getContent('mixed_access_types'), $this->serialize($object)); - - // if ($this->hasDeserializer()) { - // $object = $this->deserialize(static::getContent('mixed_access_types'), GetSetObject::class); - // self::assertSame(1, $this->getField($object, 'id')); - // self::assertSame('Johannes', $this->getField($object, 'name')); - // self::assertSame(42, $this->getField($object, 'readOnlyProperty')); - // } - // } - - // public function testAccessorOrder() - // { - // self::assertEquals(static::getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild())); - // self::assertEquals(static::getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent())); - // self::assertEquals(static::getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod())); - // } - - // public function testGroups() - // { - // $groupsObject = new GroupsObject(); - - // self::assertEquals(static::getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat())); - - // self::assertEquals( - // static::getContent('groups_foo'), - // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo'])), - // ); - - // self::assertEquals( - // static::getContent('groups_foobar'), - // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo', 'bar'])), - // ); - - // self::assertEquals( - // static::getContent('groups_all'), - // $this->serializer->serialize($groupsObject, $this->getFormat()), - // ); - - // self::assertEquals( - // static::getContent('groups_default'), - // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), - // ); - - // self::assertEquals( - // static::getContent('groups_default'), - // $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), - // ); - // } - - // public function testAdvancedGroups() - // { - // $adrien = new GroupsUser( - // 'John', - // new GroupsUser( - // 'John Manager', - // null, - // [ - // new GroupsUser( - // 'John Manager friend 1', - // new GroupsUser('John Manager friend 1 manager'), - // ), - // new GroupsUser('John Manager friend 2'), - // ], - // ), - // [ - // new GroupsUser( - // 'John friend 1', - // new GroupsUser('John friend 1 manager'), - // ), - // new GroupsUser( - // 'John friend 2', - // new GroupsUser('John friend 2 manager'), - // ), - // ], - // ); - - // self::assertEquals( - // static::getContent('groups_advanced'), - // $this->serializer->serialize( - // $adrien, - // $this->getFormat(), - // SerializationContext::create()->setGroups([ - // GroupsExclusionStrategy::DEFAULT_GROUP, - // 'manager_group', - // 'friends_group', - - // 'manager' => [ - // GroupsExclusionStrategy::DEFAULT_GROUP, - // 'friends_group', - - // 'friends' => ['nickname_group'], - // ], - // 'friends' => [ - // 'manager_group', - // 'nickname_group', - // ], - // ]), - // ), - // ); - // } - - // public function testInvalidGroupName() - // { - // $groupsObject = new InvalidGroupsObject(); - - // $this->expectException(InvalidMetadataException::class); - // $this->expectExceptionMessage('Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups?'); - - // $this->serializer->serialize($groupsObject, $this->getFormat()); - // } - - // public function testVirtualProperty() - // { - // self::assertEquals(static::getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties())); - // } - - // public function testVirtualVersions() - // { - // $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); - - // $builder = SerializerBuilder::create(); - // $builder->setExpressionEvaluator($evaluator); - // $serializer = $builder->build(); - - // self::assertEquals( - // static::getContent('virtual_properties_low'), - // $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('2')), - // ); - - // self::assertEquals( - // static::getContent('virtual_properties_all'), - // $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('7')), - // ); - - // self::assertEquals( - // static::getContent('virtual_properties_high'), - // $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('9')), - // ); - // } - - // public function testCustomHandler() - // { - // if (!$this->hasDeserializer()) { - // return; - // } - - // $handler = static function () { - // return new CustomDeserializationObject('customly_unserialized_value'); - // }; - - // $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler); - - // $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat()); - // $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat()); - // self::assertEquals('customly_unserialized_value', $object->someProperty); - // } - - // public function testTypedProperties() - // { - // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - // $builder->includeInterfaceMetadata(true); - // $this->serializer = $builder->build(); - - // $user = new TypedProperties\User(); - // $user->id = 1; - // $user->created = new \DateTime('2010-10-01 00:00:00'); - // $user->updated = new \DateTime('2011-10-01 00:00:00'); - // $user->tags = ['a', 'b']; - // $role = new TypedProperties\Role(); - // $role->id = 5; - // $user->role = $role; - // $user->vehicle = new TypedProperties\Car(); - - // $result = $this->serialize($user); - - // self::assertEquals(static::getContent('typed_props'), $result); - - // if ($this->hasDeserializer()) { - // // updated is read only - // $user->updated = null; - // $user->tags = []; - - // self::assertEquals($user, $this->deserialize(static::getContent('typed_props'), get_class($user))); - // } - // } - - // public function testConstructorPromotionWithDefaultValues() - // { - // if (PHP_VERSION_ID < 80000) { - // $this->markTestSkipped(sprintf('%s requires PHP 8.0', __METHOD__)); - // } - - // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - // $builder->includeInterfaceMetadata(true); - // $this->serializer = $builder->build(); - - // $vase = new TypedProperties\ConstructorPromotion\Vase('blue', 'big'); - // $result = $this->serialize($vase); - // self::assertEquals(static::getContent('typed_props_constructor_promotion_with_default_values'), $result); - // if ($this->hasDeserializer()) { - // $deserialized = $this->deserialize(static::getContent('typed_props_constructor_promotion_with_default_values'), get_class($vase)); - // self::assertEquals($vase->color, $deserialized->color); - // self::assertEquals($vase->plant, $deserialized->plant); - // self::assertEquals($vase->typeOfSoil, $deserialized->typeOfSoil); - // self::assertEquals($vase->daysSincePotting, $deserialized->daysSincePotting); - // self::assertEquals('huge', $deserialized->size); - // self::assertEquals(40, $deserialized->weight); - // } - // } - - // public function testUninitializedTypedProperties() - // { - // $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); - // $builder->includeInterfaceMetadata(true); - // $this->serializer = $builder->build(); - - // $user = new TypedProperties\User(); - // $user->id = 1; - // $role = new TypedProperties\Role(); - // $user->role = $role; - - // // Ensure uninitialized typed property exists - // $reflectionProp = new \ReflectionProperty($user, 'vehicle'); - // $this->assertFalse($reflectionProp->isInitialized($user)); - - // $result = $this->serialize($user); - - // self::assertEquals(static::getContent('uninitialized_typed_props'), $result); - // } + public function testList(): void + { + if ('xml' === $this->getFormat()) { + $this->markTestSkipped('XML can\'t be tested for list without value type'); + } - // /** - // * @doesNotPerformAssertions - // */ - // #[DoesNotPerformAssertions] - // public function testCustomHandlerVisitingNull() - // { - // $handler = static function ($visitor, $attachment, array $type, Context $context) { - // return $context->getNavigator()->accept(null); - // }; - - // $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, Author::class, $this->getFormat(), $handler); - - // $author = new Author('me'); - // $comment = new Comment($author, 'too'); - // $this->serializer->serialize($comment, $this->getFormat()); - // } - - // public function testInput() - // { - // self::assertEquals(static::getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat())); - // } - - // public function testObjectWithEmptyHash() - // { - // self::assertEquals(static::getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat())); - // } - - // public function testSerializeObjectWhenNull() - // { - // self::assertEquals( - // static::getContent('object_when_null'), - // $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false)), - // ); - - // self::assertEquals( - // static::getContent('object_when_null_and_serialized'), - // $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true)), - // ); - // } - - // public function testPolymorphicObjectsWithGroup() - // { - // $context = SerializationContext::create(); - // $context->setGroups(['foo']); - - // self::assertEquals( - // static::getContent('car'), - // $this->serialize(new DiscriminatorGroupCar(5), $context), - // ); - // } + $data = [1, 3, 4]; + self::assertEquals(static::getContent('list'), $this->serialize($data)); - public static function getDiscrimatorObjectsSamples(): array + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('list'), 'list')); + } + } + + public function testListEmpty(): void { - $u1 = new User(5, 'userName', 'userDesc'); - $u2 = new ExtendedUser(5, 'userName', 'userDesc', 'extednedContent'); - $arr = new ArrayCollection([$u1, $u2]); + if ('xml' === $this->getFormat()) { + $this->markTestSkipped('XML can\'t be tested for empty list'); + } - return [ - [$u1, 'user_discriminator'], - [$u2, 'user_discriminator_extended'], - [$arr, 'user_discriminator_array'], + $data = []; + self::assertEquals(static::getContent('list_empty'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('list_empty'), 'list')); + } + } + + public function testListIntegers(): void + { + $data = [1, 3, 4]; + self::assertEquals(static::getContent('list_integers'), $this->serialize($data)); + + if ($this->hasDeserializer()) { + self::assertEquals($data, $this->deserialize(static::getContent('list_integers'), 'list')); + } + } + + public function testCustomDateObject() + { + $data = new DateTimeContainer(new DateTimeCustomObject('2021-09-07')); + + self::assertEquals(static::getContent('custom_datetimeinterface'), $this->serialize($data)); + } + + public function testDateTimeArrays() + { + $data = [ + new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), + ]; + + $object = new DateTimeArraysObject($data, $data); + $serializedObject = $this->serialize($object); + + self::assertEquals(static::getContent('array_datetimes_object'), $serializedObject); + + if ($this->hasDeserializer()) { + $deserializedObject = $this->deserialize(static::getContent('array_datetimes_object'), 'Jms\Serializer\Tests\Fixtures\DateTimeArraysObject'); + assert($deserializedObject instanceof DateTimeArraysObject); + + /** 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. */ + foreach ($deserializedObject->getArrayWithDefaultDateTime() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + foreach ($deserializedObject->getArrayWithFormattedDateTime() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + self::assertEquals($object, $deserializedObject); + } + } + + public function testNamedDateTimeArrays() + { + $data = [ + new \DateTime('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + new \DateTime('2016-12-05 00:00:00', new \DateTimeZone('UTC')), + ]; + + $object = new NamedDateTimeArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); + $serializedObject = $this->serialize($object); + + self::assertEquals(static::getContent('array_named_datetimes_object'), $serializedObject); + + if ($this->hasDeserializer()) { + // skip XML deserialization + if ('xml' === $this->getFormat()) { + return; + } + + $deserializedObject = $this->deserialize(static::getContent('array_named_datetimes_object'), NamedDateTimeArraysObject::class); + assert($deserializedObject instanceof NamedDateTimeArraysObject); + + /** 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. */ + foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + self::assertEquals($object, $deserializedObject); + } + } + + public function testNamedDateTimeImmutableArrays() + { + $data = [ + new \DateTimeImmutable('2047-01-01 12:47:47', new \DateTimeZone('UTC')), + new \DateTimeImmutable('2016-12-05 00:00:00', new \DateTimeZone('UTC')), ]; + + $object = new NamedDateTimeImmutableArraysObject(['testdate1' => $data[0], 'testdate2' => $data[1]]); + $serializedObject = $this->serialize($object); + + self::assertEquals(static::getContent('array_named_datetimeimmutables_object'), $serializedObject); + + if ($this->hasDeserializer()) { + if ('xml' === $this->getFormat()) { + $this->markTestSkipped('XML deserialization does not support key-val pairs mode'); + } + + $deserializedObject = $this->deserialize(static::getContent('array_named_datetimeimmutables_object'), NamedDateTimeImmutableArraysObject::class); + assert($deserializedObject instanceof NamedDateTimeImmutableArraysObject); + + /** 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. */ + foreach ($deserializedObject->getNamedArrayWithFormattedDate() as $dateTime) { + $dateTime->setTimezone(new \DateTimeZone('UTC')); + } + + self::assertEquals($object, $deserializedObject); + } + } + + public function testArrayMixed() + { + self::assertEquals(static::getContent('array_mixed'), $this->serialize(['foo', 1, true, new SimpleObject('foo', 'bar'), [1, 3, true]])); } /** - * Test serializing entity that uses Discriminator and extends some base model class - * - * @dataProvider getDiscrimatorObjectsSamples + * @dataProvider getDateTime + * @group datetime */ - // #[DataProvider('getDiscrimatorObjectsSamples')] - // public function testDiscrimatorObjects($data, $contentId) - // { - // $context = SerializationContext::create()->setGroups(['entity.identification']); - // self::assertEquals( - // static::getContent($contentId), - // $this->serialize($data, $context), - // ); - // } - - // public function testPolymorphicObjects() - // { - // self::assertEquals( - // static::getContent('car'), - // $this->serialize(new Car(5)), - // ); - // self::assertEquals( - // static::getContent('post'), - // $this->serialize(new Post('Post Title')), - // ); - // self::assertEquals( - // static::getContent('image_post'), - // $this->serialize(new ImagePost('Image Post Title')), - // ); - - // if ($this->hasDeserializer()) { - // self::assertEquals( - // new Car(5), - // $this->deserialize( - // static::getContent('car'), - // Car::class, - // ), - // 'Class is resolved correctly when concrete sub-class is used.', - // ); - - // self::assertEquals( - // new Car(5), - // $this->deserialize( - // static::getContent('car'), - // Vehicle::class, - // ), - // 'Class is resolved correctly when least supertype is used.', - // ); - - // self::assertEquals( - // new Car(5), - // $this->deserialize( - // static::getContent('car_without_type'), - // Car::class, - // ), - // 'Class is resolved correctly when concrete sub-class is used and no type is defined.', - // ); - - // self::assertEquals( - // new Post('Post Title'), - // $this->deserialize( - // static::getContent('post'), - // Post::class, - // ), - // 'Class is resolved correctly when parent class is used and type is set.', - // ); - - // self::assertEquals( - // new ImagePost('Image Post Title'), - // $this->deserialize( - // static::getContent('image_post'), - // Post::class, - // ), - // 'Class is resolved correctly when least supertype is used.', - // ); - - // self::assertEquals( - // new ImagePost('Image Post Title'), - // $this->deserialize( - // static::getContent('image_post'), - // ImagePost::class, - // ), - // 'Class is resolved correctly when concrete sub-class is used and no type is defined.', - // ); - // } - // } - - // public function testNestedPolymorphicObjects() - // { - // $garage = new Garage([new Car(3), new Moped(1)]); - // self::assertEquals( - // static::getContent('garage'), - // $this->serialize($garage), - // ); - - // if ($this->hasDeserializer()) { - // self::assertEquals( - // $garage, - // $this->deserialize( - // static::getContent('garage'), - // Garage::class, - // ), - // ); - // } - // } - - // public function testNestedPolymorphicInterfaces() - // { - // $garage = new VehicleInterfaceGarage([new Car(3), new Moped(1)]); - // self::assertEquals( - // static::getContent('garage'), - // $this->serialize($garage), - // ); - - // if ($this->hasDeserializer()) { - // self::assertEquals( - // $garage, - // $this->deserialize( - // static::getContent('garage'), - // VehicleInterfaceGarage::class, - // ), - // ); - // } - // } - - // public function testPolymorphicObjectsInvalidDeserialization() - // { - // $this->expectException(\LogicException::class); - - // if (!$this->hasDeserializer()) { - // throw new \LogicException('No deserializer'); - // } - - // $this->deserialize( - // static::getContent('car_without_type'), - // Vehicle::class, - // ); - // } - - // public function testDepthExclusionStrategy() - // { - // $context = SerializationContext::create() - // ->addExclusionStrategy(new DepthExclusionStrategy()); - - // $data = new Tree( - // new Node([ - // new Node([ - // new Node([ - // new Node([ - // new Node(), - // ]), - // ]), - // ]), - // ]), - // ); - - // self::assertEquals(static::getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context)); - // } - - // public function testMaxDepthWithSkippableObject() - // { - // $data = new Gh236Foo(); - - // $context = SerializationContext::create()->enableMaxDepthChecks(); - // $serialized = $this->serialize($data, $context); - - // self::assertEquals(static::getContent('maxdepth_skippabe_object'), $serialized); - // } - - // public function testMaxDepthWithZeroDepthObject() - // { - // $data = new Gh1382Foo(); - - // $context = SerializationContext::create()->enableMaxDepthChecks(); - // $serialized = $this->serialize($data, $context); - - // self::assertEquals(static::getContent('maxdepth_0'), $serialized); - // } - - // public function testMaxDepthWithOneDepthObject() - // { - // $data = new Gh1382Baz(); - - // $context = SerializationContext::create()->enableMaxDepthChecks(); - // $serialized = $this->serialize($data, $context); - - // self::assertEquals(static::getContent('maxdepth_1'), $serialized); - // } - - // public function testDeserializingIntoExistingObject() - // { - // if (!$this->hasDeserializer()) { - // return; - // } - - // $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor()); - - // $builder = SerializerBuilder::create(); - // $builder->setObjectConstructor($objectConstructor); - // $serializer = $builder->build(); - - // $order = new Order(new Price(12)); - - // $context = new DeserializationContext(); - // $context->setAttribute('target', $order); - - // $deseralizedOrder = $serializer->deserialize( - // static::getContent('order'), - // get_class($order), - // $this->getFormat(), - // $context, - // ); - - // self::assertSame($order, $deseralizedOrder); - // self::assertEquals(new Order(new Price(12.34)), $deseralizedOrder); - // self::assertInstanceOf(Price::class, $this->getField($deseralizedOrder, 'cost')); - // } - - // public function testObjectWithNullableArrays() - // { - // $object = new ObjectWithEmptyNullableAndEmptyArrays(); - // self::assertEquals(static::getContent('nullable_arrays'), $this->serializer->serialize($object, $this->getFormat())); - // } + #[DataProvider('getDateTime')] + public function testDateTime($key, $value, $type) + { + self::assertEquals(static::getContent($key), $this->serialize($value)); - // /** - // * @dataProvider getSerializeNullCases - // */ - // #[DataProvider('getSerializeNullCases')] - // public function testSerializeNullArrayObjectWithExclusionStrategy(bool $serializeNull) - // { - // $arr = [ - // new SimpleObject('foo1', 'bar1'), - // ]; - - // $serializationContext = SerializationContext::create(); - // $serializationContext->setSerializeNull($serializeNull); - // $serializationContext->setInitialType('array<' . SimpleObject::class . '>'); - // $serializationContext->addExclusionStrategy(new AlwaysExcludeExclusionStrategy()); - // self::assertEquals( - // static::getContent('array_objects_nullable'), - // $this->serializer->serialize($arr, $this->getFormat(), $serializationContext), - // ); - // } - - // public function testHandlerInvokedOnPrimitives() - // { - // $invoked = false; - // $this->handlerRegistry->registerHandler( - // GraphNavigatorInterface::DIRECTION_SERIALIZATION, - // 'Virtual', - // $this->getFormat(), - // static function ($visitor, $data) use (&$invoked) { - // $invoked = true; - // self::assertEquals('foo', $data); - - // return null; - // }, - // ); - - // $this->serializer->serialize('foo', $this->getFormat(), null, 'Virtual'); - // self::assertTrue($invoked); - // } + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent($key), $type); - public static function getFirstClassListCollectionsValues() - { - $collection = new FirstClassListCollection([1, 2]); + self::assertIsObject($deserialized); + self::assertInstanceOf(get_class($value), $deserialized); + self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); + } + } + public static function getDateTime() + { return [ - [[1, 2, 3], static::getContent('inline_list_collection')], - [[], static::getContent('inline_empty_list_collection')], - [[1, 'a' => 2], static::getContent('inline_deserialization_list_collection'), $collection], + ['date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'], + ['date_time_multi_format', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), "DateTime<'Y-m-d', '', ['Y-m-d','Y-m-d\TH:i:sP']>"], ]; } /** - * @param array $items - * @param array $expected - * - * @dataProvider getFirstClassListCollectionsValues + * @dataProvider getDateTimeImmutable + * @group datetime */ - // #[DataProvider('getFirstClassListCollectionsValues')] - // public function testFirstClassListCollections($items, $expected, ?FirstClassListCollection $expectedDeserializatrion = null) - // { - // $collection = new FirstClassListCollection($items); - - // self::assertSame($expected, $this->serialize($collection)); - // self::assertEquals( - // $expectedDeserializatrion ?: $collection, - // $this->deserialize($expected, get_class($collection)), - // ); - // } - - // public function testInlineCollection() - // { - // $list = new AuthorsInline(new Author('foo'), new Author('bar')); - // self::assertEquals(static::getContent('authors_inline'), $this->serialize($list)); - // self::assertEquals($list, $this->deserialize(static::getContent('authors_inline'), AuthorsInline::class)); - // } - - // public function testSerializingUnionTypedProperties() - // { - // if (PHP_VERSION_ID < 80000) { - // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - // } - - // $object = new TypedProperties\UnionTypedProperties(10000); - - // self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); - // } - - // public function testSerializingUnionDocBlockTypesProperties() - // { - // $object = new UnionTypedDocBlockProperty(10000); - - // self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); - - // $object = new UnionTypedDocBlockProperty(1.236); - - // self::assertEquals(static::getContent('data_float'), $this->serialize($object)); - - // } - - // public function testThrowingExceptionWhenDeserializingUnionDocBlockTypes() - // { - // if (PHP_VERSION_ID < 80000) { - // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - - // return; - // } - - // $this->expectException(RuntimeException::class); - - // $object = new UnionTypedDocBlockProperty(10000); - // $deserialized = $this->deserialize(static::getContent('data_integer'), UnionTypedDocBlockProperty::class); - // } - - // public function testIterable(): void - // { - // $generator = static function (): iterable { - // yield 'foo' => 'bar'; - // yield 'bar' => 'foo'; - // }; - // $withIterable = new ObjectWithIterable($generator()); - // self::assertEquals(static::getContent('iterable'), $this->serialize($withIterable)); - - // if (!$this->hasDeserializer()) { - // return; - // } - - // self::assertEquals( - // new ObjectWithIterable(Functions::iterableToArray($generator())), - // $this->deserialize(static::getContent('iterable'), get_class($withIterable)), - // ); - // } - - // public function testGenerator(): void - // { - // $generator = static function (): \Generator { - // yield 'foo' => 'bar'; - // yield 'bar' => 'foo'; - // }; - // $withGenerator = new ObjectWithGenerator($generator()); - // self::assertEquals(static::getContent('generator'), $this->serialize($withGenerator)); - - // if (!$this->hasDeserializer()) { - // return; - // } - - // self::assertEquals( - // $withGenerator, - // $this->deserialize(static::getContent('generator'), get_class($withGenerator)), - // ); - // } - - // public function testIterator(): void - // { - // $iterator = new \ArrayIterator([ - // 'foo' => 'bar', - // 'bar' => 'foo', - // ]); - // $withIterator = new ObjectWithIterator($iterator); - // self::assertEquals(static::getContent('iterator'), $this->serialize($withIterator)); - - // if (!$this->hasDeserializer()) { - // return; - // } - - // self::assertEquals( - // $withIterator, - // $this->deserialize(static::getContent('iterator'), get_class($withIterator)), - // ); - // } - - // public function testArrayIterator(): void - // { - // $iterator = new \ArrayIterator([ - // 'foo' => 'bar', - // 'bar' => 'foo', - // ]); - // $withArrayIterator = new ObjectWithArrayIterator($iterator); - // self::assertEquals(static::getContent('iterator'), $this->serialize($withArrayIterator)); - - // if (!$this->hasDeserializer()) { - // return; - // } - - // self::assertEquals( - // $withArrayIterator, - // $this->deserialize(static::getContent('iterator'), get_class($withArrayIterator)), - // ); - // } + #[DataProvider('getDateTimeImmutable')] + public function testDateTimeImmutable($key, $value, $type) + { + self::assertEquals(static::getContent($key), $this->serialize($value)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent($key), $type); + + self::assertIsObject($deserialized); + self::assertInstanceOf(get_class($value), $deserialized); + self::assertEquals($value->getTimestamp(), $deserialized->getTimestamp()); + } + } + + public static function getDateTimeImmutable() + { + return [ + ['date_time_immutable', new \DateTimeImmutable('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTimeImmutable'], + ]; + } + + public function testTimestamp() + { + $value = new Timestamp(new \DateTime('2016-02-11 00:00:00', new \DateTimeZone('UTC'))); + self::assertEquals(static::getContent('timestamp'), $this->serialize($value)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('timestamp'), Timestamp::class); + self::assertEquals($value, $deserialized); + self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); + + $deserialized = $this->deserialize(static::getContent('timestamp_prev'), Timestamp::class); + self::assertEquals($value, $deserialized); + self::assertEquals($value->getTimestamp()->getTimestamp(), $deserialized->getTimestamp()->getTimestamp()); + } + } + + public function testDateInterval() + { + $duration = new \DateInterval('PT45M'); + + self::assertEquals(static::getContent('date_interval'), $this->serializer->serialize($duration, $this->getFormat())); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('date_interval'), \DateInterval::class); + self::assertEquals($duration, $deserialized); + self::assertEquals($duration->i, $deserialized->i); + } + } + + public function testBlogPost() + { + $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')); + $post->addComment($comment = new Comment($author, 'foo')); + + $post->addTag($tag1 = new Tag('tag1')); + $post->addTag($tag2 = new Tag('tag2')); + + self::assertEquals(static::getContent('blog_post'), $this->serialize($post)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('blog_post'), get_class($post)); + self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); + self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); + self::assertFalse($this->getField($deserialized, 'published')); + self::assertFalse($this->getField($deserialized, 'reviewed')); + self::assertSame('e86ce85cdb1253e4fc6352f5cf297248bceec62b', $this->getField($deserialized, 'etag')); + self::assertEquals(new ArrayCollection([$comment]), $this->getField($deserialized, 'comments')); + self::assertEquals([$comment], $this->getField($deserialized, 'comments2')); + self::assertEquals($author, $this->getField($deserialized, 'author')); + self::assertEquals([$tag1, $tag2], $this->getField($deserialized, 'tag')); + } + } + + public function testDeserializingNull() + { + $objectConstructor = new InitializedBlogPostConstructor(); + + $builder = SerializerBuilder::create(); + $builder->setObjectConstructor($objectConstructor); + $this->serializer = $builder->build(); + + $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')); + + $this->setField($post, 'author', null); + $this->setField($post, 'publisher', null); + + self::assertEquals(static::getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true))); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create()); + + self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM)); + self::assertSame('This is a nice title.', $this->getField($deserialized, 'title')); + self::assertFalse($this->getField($deserialized, 'published')); + self::assertFalse($this->getField($deserialized, 'reviewed')); + self::assertEquals(new ArrayCollection(), $this->getField($deserialized, 'comments')); + self::assertEquals(null, $this->getField($deserialized, 'author')); + } + } + + public function testSymfonyUid() + { + $uid = Uuid::fromString('66b3177c-e03b-4a22-9dee-ddd7d37a04d5'); + + self::assertEquals(static::getContent('uid'), $this->serialize($uid)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('uid'), UuidV4::class); + + self::assertInstanceOf(UuidV4::class, $deserialized); + self::assertTrue($uid->equals($deserialized)); + } + } + + public function testExpressionAuthor() + { + $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); + + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator($evaluator); + $serializer = $builder->build(); + + $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); + self::assertEquals(static::getContent('author_expression'), $serializer->serialize($author, $this->getFormat())); + } + + public function testExpressionAuthorWithContextVars() + { + $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); + + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator($evaluator); + $serializer = $builder->build(); + + $author = new AuthorExpressionAccessContext('Ruud'); + self::assertEquals(static::getContent('author_expression_context'), $serializer->serialize($author, $this->getFormat())); + } + + public function testExpressionAccessorStrategNotEnabled() + { + $author = new AuthorExpressionAccess(123, 'Ruud', 'Kamphuis'); + + $this->expectException(ExpressionLanguageRequiredException::class); + $this->expectExceptionMessage('The property firstName on JMS\Serializer\Tests\Fixtures\AuthorExpressionAccess requires the expression accessor strategy to be enabled.'); + + $this->serialize($author); + } + + public function testReadOnly() + { + $author = new AuthorReadOnly(123, 'Ruud Kamphuis'); + self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + self::assertNull($this->getField($deserialized, 'id')); + self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + } + } + + public function testDeprecatedReadOnly() + { + $author = new AuthorDeprecatedReadOnly(123, 'Ruud Kamphuis'); + self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + self::assertNull($this->getField($deserialized, 'id')); + self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + } + } + + public function testReadOnlyClass() + { + $author = new AuthorReadOnlyPerClass(123, 'Ruud Kamphuis'); + self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + self::assertNull($this->getField($deserialized, 'id')); + self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + } + } + + public function testDeprecatedReadOnlyClass() + { + $author = new AuthorDeprecatedReadOnlyPerClass(123, 'Ruud Kamphuis'); + self::assertEquals(static::getContent('readonly'), $this->serialize($author)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('readonly'), get_class($author)); + self::assertNull($this->getField($deserialized, 'id')); + self::assertEquals('Ruud Kamphuis', $this->getField($deserialized, 'name')); + } + } + + public function testPrice() + { + $price = new Price(3); + self::assertEquals(static::getContent('price'), $this->serialize($price)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('price'), get_class($price)); + self::assertEquals(3, $this->getField($deserialized, 'price')); + } + } + + public function testOrder() + { + $order = new Order(new Price(12.34)); + self::assertEquals(static::getContent('order'), $this->serialize($order)); + + if ($this->hasDeserializer()) { + self::assertEquals($order, $this->deserialize(static::getContent('order'), get_class($order))); + } + } + + public function testCurrencyAwarePrice() + { + $price = new CurrencyAwarePrice(2.34); + self::assertEquals(static::getContent('currency_aware_price'), $this->serialize($price)); + + if ($this->hasDeserializer()) { + self::assertEquals($price, $this->deserialize(static::getContent('currency_aware_price'), get_class($price))); + } + } + + public function testOrderWithCurrencyAwarePrice() + { + $order = new CurrencyAwareOrder(new CurrencyAwarePrice(1.23)); + self::assertEquals(static::getContent('order_with_currency_aware_price'), $this->serialize($order)); + + if ($this->hasDeserializer()) { + self::assertEquals($order, $this->deserialize(static::getContent('order_with_currency_aware_price'), get_class($order))); + } + } + + public function testInline() + { + $inline = new InlineParent(); + + $result = $this->serialize($inline); + self::assertEquals(static::getContent('inline'), $result); + + if ($this->hasDeserializer()) { + self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); + } + } + + public function testInlineEmptyChild() + { + $inline = new InlineParentWithEmptyChild(new InlineChildEmpty()); + $result = $this->serialize($inline); + self::assertEquals(static::getContent('inline_child_empty'), $result); + if ($this->hasDeserializer()) { + self::assertEquals($inline, $this->deserialize(static::getContent('inline'), get_class($inline))); + } + } + + public function testEmptyChild() + { + // by empty object + $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildEmpty()); + self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); + + // by nulls + $inner = new InlineChild(); + $inner->a = null; + $inner->b = null; + $inline = new ParentDoNotSkipWithEmptyChild($inner); + self::assertEquals(static::getContent('empty_child'), $this->serialize($inline)); + + // by exclusion strategy + $context = SerializationContext::create()->setGroups(['Default']); + $inline = new ParentDoNotSkipWithEmptyChild(new InlineChildWithGroups()); + self::assertEquals(static::getContent('empty_child'), $this->serialize($inline, $context)); + } + + public function testSkipEmptyChild() + { + // by empty object + $inline = new ParentSkipWithEmptyChild(new InlineChildEmpty()); + self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); + + // by nulls + $inner = new InlineChild(); + $inner->a = null; + $inner->b = null; + $inline = new ParentSkipWithEmptyChild($inner); + self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline)); + + // by exclusion strategy + $context = SerializationContext::create()->setGroups(['Default']); + $inline = new ParentSkipWithEmptyChild(new InlineChildWithGroups()); + self::assertEquals(static::getContent('empty_child_skip'), $this->serialize($inline, $context)); + } + + public function testLog() + { + self::assertEquals(static::getContent('log'), $this->serialize($log = new Log())); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('log'), get_class($log)); + self::assertEquals($log, $deserialized); + } + } + + public function testSelfCircularReferenceCollection() + { + $object = new CircularReferenceCollection(); + $object->collection[] = $object; + self::assertEquals(static::getContent('circular_reference_collection'), $this->serialize($object)); + } + + public function testCircularReference() + { + $object = new CircularReferenceParent(); + self::assertEquals(static::getContent('circular_reference'), $this->serialize($object)); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('circular_reference'), get_class($object)); + + $col = $this->getField($deserialized, 'collection'); + self::assertCount(2, $col); + self::assertEquals('child1', $col[0]->getName()); + self::assertEquals('child2', $col[1]->getName()); + self::assertSame($deserialized, $col[0]->getParent()); + self::assertSame($deserialized, $col[1]->getParent()); + + $col = $this->getField($deserialized, 'anotherCollection'); + self::assertCount(2, $col); + self::assertEquals('child1', $col[0]->getName()); + self::assertEquals('child2', $col[1]->getName()); + self::assertSame($deserialized, $col[0]->getParent()); + self::assertSame($deserialized, $col[1]->getParent()); + } + } + + public function testLifecycleCallbacks() + { + $object = new ObjectWithLifecycleCallbacks(); + self::assertEquals(static::getContent('lifecycle_callbacks'), $this->serialize($object)); + self::assertNull($this->getField($object, 'name')); + + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('lifecycle_callbacks'), get_class($object)); + self::assertEquals($object, $deserialized); + } + } + + public function testFormErrors() + { + $errors = [ + new FormError('This is the form error'), + new FormError('Another error'), + ]; + + self::assertEquals(static::getContent('form_errors'), $this->serialize($errors)); + } + + // /** + // * @dataProvider initialFormTypeProvider + // */ + #[DataProvider('initialFormTypeProvider')] + public function testNestedFormErrors($type) + { + $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); + + $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); + $formConfigBuilder->setCompound(true); + $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); + $fooConfig = $formConfigBuilder->getFormConfig(); + + $form = new Form($fooConfig); + $form->addError(new FormError('This is the form error')); + + $formConfigBuilder = new FormConfigBuilder('bar', null, $dispatcher); + $barConfig = $formConfigBuilder->getFormConfig(); + $child = new Form($barConfig); + $child->addError(new FormError('Error of the child form')); + $form->add($child); + + $context = SerializationContext::create(); + $context->setInitialType($type); + + self::assertEquals(static::getContent('nested_form_errors'), $this->serialize($form, $context)); + } + + // /** + // * @doesNotPerformAssertions + // * @dataProvider initialFormTypeProvider + // */ + #[DataProvider('initialFormTypeProvider')] + #[DoesNotPerformAssertions] + public function testFormErrorsWithNonFormComponents($type) + { + $dispatcher = $this->getMockBuilder(EventDispatcherInterface::class)->getMock(); + + $factoryBuilder = new FormFactoryBuilder(); + $factoryBuilder->addType(new SubmitType()); + $factoryBuilder->addType(new ButtonType()); + $factory = $factoryBuilder->getFormFactory(); + + $formConfigBuilder = new FormConfigBuilder('foo', null, $dispatcher); + $formConfigBuilder->setFormFactory($factory); + $formConfigBuilder->setCompound(true); + $formConfigBuilder->setDataMapper($this->getMockBuilder(DataMapperInterface::class)->getMock()); + $fooConfig = $formConfigBuilder->getFormConfig(); + + $form = new Form($fooConfig); + $form->add('save', SubmitType::class); + + $context = SerializationContext::create(); + $context->setInitialType($type); + + try { + $this->serialize($form, $context); + } catch (\Throwable $e) { + self::assertTrue(false, 'Serialization should not throw an exception'); + } + } + + public static function initialFormTypeProvider() + { + return [ + [Form::class], + [FormInterface::class], + ]; + } + + public function testConstraintViolation() + { + $violation = new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null); + + self::assertEquals(static::getContent('constraint_violation'), $this->serialize($violation)); + } + + public function testConstraintViolationList() + { + $violations = new ConstraintViolationList(); + $violations->add(new ConstraintViolation('Message of violation', 'Message of violation', [], null, 'foo', null)); + $violations->add(new ConstraintViolation('Message of another violation', 'Message of another violation', [], null, 'bar', null)); + + self::assertEquals(static::getContent('constraint_violation_list'), $this->serialize($violations)); + } + + public function testDoctrineProxy() + { + if (!class_exists(Version::class)) { + $this->markTestSkipped('Doctrine is not available.'); + } + + $object = new SimpleObjectProxy('foo', 'bar'); + + self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); + } + + public function testInitializedDoctrineProxy() + { + if (!class_exists(Version::class)) { + $this->markTestSkipped('Doctrine is not available.'); + } + + $object = new SimpleObjectProxy('foo', 'bar'); + $object->__load(); + + self::assertEquals(static::getContent('orm_proxy'), $this->serialize($object)); + } + + public function testCustomAccessor() + { + $post = new IndexedCommentsBlogPost(); + + self::assertEquals(static::getContent('custom_accessor'), $this->serialize($post)); + } + + public function testMixedAccessTypes() + { + $object = new GetSetObject(); + + self::assertEquals(static::getContent('mixed_access_types'), $this->serialize($object)); + + if ($this->hasDeserializer()) { + $object = $this->deserialize(static::getContent('mixed_access_types'), GetSetObject::class); + self::assertSame(1, $this->getField($object, 'id')); + self::assertSame('Johannes', $this->getField($object, 'name')); + self::assertSame(42, $this->getField($object, 'readOnlyProperty')); + } + } + + public function testAccessorOrder() + { + self::assertEquals(static::getContent('accessor_order_child'), $this->serialize(new AccessorOrderChild())); + self::assertEquals(static::getContent('accessor_order_parent'), $this->serialize(new AccessorOrderParent())); + self::assertEquals(static::getContent('accessor_order_methods'), $this->serialize(new AccessorOrderMethod())); + } + + public function testGroups() + { + $groupsObject = new GroupsObject(); + + self::assertEquals(static::getContent('groups_all'), $this->serializer->serialize($groupsObject, $this->getFormat())); + + self::assertEquals( + static::getContent('groups_foo'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo'])), + ); + + self::assertEquals( + static::getContent('groups_foobar'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups(['foo', 'bar'])), + ); + + self::assertEquals( + static::getContent('groups_all'), + $this->serializer->serialize($groupsObject, $this->getFormat()), + ); + + self::assertEquals( + static::getContent('groups_default'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), + ); + + self::assertEquals( + static::getContent('groups_default'), + $this->serializer->serialize($groupsObject, $this->getFormat(), SerializationContext::create()->setGroups([GroupsExclusionStrategy::DEFAULT_GROUP])), + ); + } + + public function testAdvancedGroups() + { + $adrien = new GroupsUser( + 'John', + new GroupsUser( + 'John Manager', + null, + [ + new GroupsUser( + 'John Manager friend 1', + new GroupsUser('John Manager friend 1 manager'), + ), + new GroupsUser('John Manager friend 2'), + ], + ), + [ + new GroupsUser( + 'John friend 1', + new GroupsUser('John friend 1 manager'), + ), + new GroupsUser( + 'John friend 2', + new GroupsUser('John friend 2 manager'), + ), + ], + ); + + self::assertEquals( + static::getContent('groups_advanced'), + $this->serializer->serialize( + $adrien, + $this->getFormat(), + SerializationContext::create()->setGroups([ + GroupsExclusionStrategy::DEFAULT_GROUP, + 'manager_group', + 'friends_group', + + 'manager' => [ + GroupsExclusionStrategy::DEFAULT_GROUP, + 'friends_group', + + 'friends' => ['nickname_group'], + ], + 'friends' => [ + 'manager_group', + 'nickname_group', + ], + ]), + ), + ); + } + + public function testInvalidGroupName() + { + $groupsObject = new InvalidGroupsObject(); + + $this->expectException(InvalidMetadataException::class); + $this->expectExceptionMessage('Invalid group name "foo, bar" on "JMS\Serializer\Tests\Fixtures\InvalidGroupsObject->foo", did you mean to create multiple groups?'); + + $this->serializer->serialize($groupsObject, $this->getFormat()); + } + + public function testVirtualProperty() + { + self::assertEquals(static::getContent('virtual_properties'), $this->serialize(new ObjectWithVirtualProperties())); + } + + public function testVirtualVersions() + { + $evaluator = new ExpressionEvaluator(new ExpressionLanguage()); + + $builder = SerializerBuilder::create(); + $builder->setExpressionEvaluator($evaluator); + $serializer = $builder->build(); + + self::assertEquals( + static::getContent('virtual_properties_low'), + $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('2')), + ); + + self::assertEquals( + static::getContent('virtual_properties_all'), + $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('7')), + ); + + self::assertEquals( + static::getContent('virtual_properties_high'), + $serializer->serialize(new ObjectWithVersionedVirtualProperties(), $this->getFormat(), SerializationContext::create()->setVersion('9')), + ); + } + + public function testCustomHandler() + { + if (!$this->hasDeserializer()) { + return; + } + + $handler = static function () { + return new CustomDeserializationObject('customly_unserialized_value'); + }; + + $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_DESERIALIZATION, 'CustomDeserializationObject', $this->getFormat(), $handler); + + $serialized = $this->serializer->serialize(new CustomDeserializationObject('sometext'), $this->getFormat()); + $object = $this->serializer->deserialize($serialized, 'CustomDeserializationObject', $this->getFormat()); + self::assertEquals('customly_unserialized_value', $object->someProperty); + } + + public function testTypedProperties() + { + $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + $builder->includeInterfaceMetadata(true); + $this->serializer = $builder->build(); + + $user = new TypedProperties\User(); + $user->id = 1; + $user->created = new \DateTime('2010-10-01 00:00:00'); + $user->updated = new \DateTime('2011-10-01 00:00:00'); + $user->tags = ['a', 'b']; + $role = new TypedProperties\Role(); + $role->id = 5; + $user->role = $role; + $user->vehicle = new TypedProperties\Car(); + + $result = $this->serialize($user); + + self::assertEquals(static::getContent('typed_props'), $result); + + if ($this->hasDeserializer()) { + // updated is read only + $user->updated = null; + $user->tags = []; + + self::assertEquals($user, $this->deserialize(static::getContent('typed_props'), get_class($user))); + } + } + + public function testConstructorPromotionWithDefaultValues() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', __METHOD__)); + } + + $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + $builder->includeInterfaceMetadata(true); + $this->serializer = $builder->build(); + + $vase = new TypedProperties\ConstructorPromotion\Vase('blue', 'big'); + $result = $this->serialize($vase); + self::assertEquals(static::getContent('typed_props_constructor_promotion_with_default_values'), $result); + if ($this->hasDeserializer()) { + $deserialized = $this->deserialize(static::getContent('typed_props_constructor_promotion_with_default_values'), get_class($vase)); + self::assertEquals($vase->color, $deserialized->color); + self::assertEquals($vase->plant, $deserialized->plant); + self::assertEquals($vase->typeOfSoil, $deserialized->typeOfSoil); + self::assertEquals($vase->daysSincePotting, $deserialized->daysSincePotting); + self::assertEquals('huge', $deserialized->size); + self::assertEquals(40, $deserialized->weight); + } + } + + public function testUninitializedTypedProperties() + { + $builder = SerializerBuilder::create($this->handlerRegistry, $this->dispatcher); + $builder->includeInterfaceMetadata(true); + $this->serializer = $builder->build(); + + $user = new TypedProperties\User(); + $user->id = 1; + $role = new TypedProperties\Role(); + $user->role = $role; + + // Ensure uninitialized typed property exists + $reflectionProp = new \ReflectionProperty($user, 'vehicle'); + $this->assertFalse($reflectionProp->isInitialized($user)); + + $result = $this->serialize($user); + + self::assertEquals(static::getContent('uninitialized_typed_props'), $result); + } + + /** + * @doesNotPerformAssertions + */ + #[DoesNotPerformAssertions] + public function testCustomHandlerVisitingNull() + { + $handler = static function ($visitor, $attachment, array $type, Context $context) { + return $context->getNavigator()->accept(null); + }; + + $this->handlerRegistry->registerHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, Author::class, $this->getFormat(), $handler); + + $author = new Author('me'); + $comment = new Comment($author, 'too'); + $this->serializer->serialize($comment, $this->getFormat()); + } + + public function testInput() + { + self::assertEquals(static::getContent('input'), $this->serializer->serialize(new Input(), $this->getFormat())); + } + + public function testObjectWithEmptyHash() + { + self::assertEquals(static::getContent('hash_empty'), $this->serializer->serialize(new ObjectWithEmptyHash(), $this->getFormat())); + } + + public function testSerializeObjectWhenNull() + { + self::assertEquals( + static::getContent('object_when_null'), + $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(false)), + ); + + self::assertEquals( + static::getContent('object_when_null_and_serialized'), + $this->serialize(new Comment(null, 'foo'), SerializationContext::create()->setSerializeNull(true)), + ); + } + + public function testPolymorphicObjectsWithGroup() + { + $context = SerializationContext::create(); + $context->setGroups(['foo']); + + self::assertEquals( + static::getContent('car'), + $this->serialize(new DiscriminatorGroupCar(5), $context), + ); + } + + public static function getDiscrimatorObjectsSamples(): array + { + $u1 = new User(5, 'userName', 'userDesc'); + $u2 = new ExtendedUser(5, 'userName', 'userDesc', 'extednedContent'); + $arr = new ArrayCollection([$u1, $u2]); + + return [ + [$u1, 'user_discriminator'], + [$u2, 'user_discriminator_extended'], + [$arr, 'user_discriminator_array'], + ]; + } + + /** + * Test serializing entity that uses Discriminator and extends some base model class + * + * @dataProvider getDiscrimatorObjectsSamples + */ + #[DataProvider('getDiscrimatorObjectsSamples')] + public function testDiscrimatorObjects($data, $contentId) + { + $context = SerializationContext::create()->setGroups(['entity.identification']); + self::assertEquals( + static::getContent($contentId), + $this->serialize($data, $context), + ); + } + + public function testPolymorphicObjects() + { + self::assertEquals( + static::getContent('car'), + $this->serialize(new Car(5)), + ); + self::assertEquals( + static::getContent('post'), + $this->serialize(new Post('Post Title')), + ); + self::assertEquals( + static::getContent('image_post'), + $this->serialize(new ImagePost('Image Post Title')), + ); + + if ($this->hasDeserializer()) { + self::assertEquals( + new Car(5), + $this->deserialize( + static::getContent('car'), + Car::class, + ), + 'Class is resolved correctly when concrete sub-class is used.', + ); + + self::assertEquals( + new Car(5), + $this->deserialize( + static::getContent('car'), + Vehicle::class, + ), + 'Class is resolved correctly when least supertype is used.', + ); + + self::assertEquals( + new Car(5), + $this->deserialize( + static::getContent('car_without_type'), + Car::class, + ), + 'Class is resolved correctly when concrete sub-class is used and no type is defined.', + ); + + self::assertEquals( + new Post('Post Title'), + $this->deserialize( + static::getContent('post'), + Post::class, + ), + 'Class is resolved correctly when parent class is used and type is set.', + ); + + self::assertEquals( + new ImagePost('Image Post Title'), + $this->deserialize( + static::getContent('image_post'), + Post::class, + ), + 'Class is resolved correctly when least supertype is used.', + ); + + self::assertEquals( + new ImagePost('Image Post Title'), + $this->deserialize( + static::getContent('image_post'), + ImagePost::class, + ), + 'Class is resolved correctly when concrete sub-class is used and no type is defined.', + ); + } + } + + public function testNestedPolymorphicObjects() + { + $garage = new Garage([new Car(3), new Moped(1)]); + self::assertEquals( + static::getContent('garage'), + $this->serialize($garage), + ); + + if ($this->hasDeserializer()) { + self::assertEquals( + $garage, + $this->deserialize( + static::getContent('garage'), + Garage::class, + ), + ); + } + } + + public function testNestedPolymorphicInterfaces() + { + $garage = new VehicleInterfaceGarage([new Car(3), new Moped(1)]); + self::assertEquals( + static::getContent('garage'), + $this->serialize($garage), + ); + + if ($this->hasDeserializer()) { + self::assertEquals( + $garage, + $this->deserialize( + static::getContent('garage'), + VehicleInterfaceGarage::class, + ), + ); + } + } + + public function testPolymorphicObjectsInvalidDeserialization() + { + $this->expectException(\LogicException::class); + + if (!$this->hasDeserializer()) { + throw new \LogicException('No deserializer'); + } + + $this->deserialize( + static::getContent('car_without_type'), + Vehicle::class, + ); + } + + public function testDepthExclusionStrategy() + { + $context = SerializationContext::create() + ->addExclusionStrategy(new DepthExclusionStrategy()); + + $data = new Tree( + new Node([ + new Node([ + new Node([ + new Node([ + new Node(), + ]), + ]), + ]), + ]), + ); + + self::assertEquals(static::getContent('tree'), $this->serializer->serialize($data, $this->getFormat(), $context)); + } + + public function testMaxDepthWithSkippableObject() + { + $data = new Gh236Foo(); + + $context = SerializationContext::create()->enableMaxDepthChecks(); + $serialized = $this->serialize($data, $context); + + self::assertEquals(static::getContent('maxdepth_skippabe_object'), $serialized); + } + + public function testMaxDepthWithZeroDepthObject() + { + $data = new Gh1382Foo(); + + $context = SerializationContext::create()->enableMaxDepthChecks(); + $serialized = $this->serialize($data, $context); + + self::assertEquals(static::getContent('maxdepth_0'), $serialized); + } + + public function testMaxDepthWithOneDepthObject() + { + $data = new Gh1382Baz(); + + $context = SerializationContext::create()->enableMaxDepthChecks(); + $serialized = $this->serialize($data, $context); + + self::assertEquals(static::getContent('maxdepth_1'), $serialized); + } + + public function testDeserializingIntoExistingObject() + { + if (!$this->hasDeserializer()) { + return; + } + + $objectConstructor = new InitializedObjectConstructor(new UnserializeObjectConstructor()); + + $builder = SerializerBuilder::create(); + $builder->setObjectConstructor($objectConstructor); + $serializer = $builder->build(); + + $order = new Order(new Price(12)); + + $context = new DeserializationContext(); + $context->setAttribute('target', $order); + + $deseralizedOrder = $serializer->deserialize( + static::getContent('order'), + get_class($order), + $this->getFormat(), + $context, + ); + + self::assertSame($order, $deseralizedOrder); + self::assertEquals(new Order(new Price(12.34)), $deseralizedOrder); + self::assertInstanceOf(Price::class, $this->getField($deseralizedOrder, 'cost')); + } + + public function testObjectWithNullableArrays() + { + $object = new ObjectWithEmptyNullableAndEmptyArrays(); + self::assertEquals(static::getContent('nullable_arrays'), $this->serializer->serialize($object, $this->getFormat())); + } + + /** + * @dataProvider getSerializeNullCases + */ + #[DataProvider('getSerializeNullCases')] + public function testSerializeNullArrayObjectWithExclusionStrategy(bool $serializeNull) + { + $arr = [ + new SimpleObject('foo1', 'bar1'), + ]; + + $serializationContext = SerializationContext::create(); + $serializationContext->setSerializeNull($serializeNull); + $serializationContext->setInitialType('array<' . SimpleObject::class . '>'); + $serializationContext->addExclusionStrategy(new AlwaysExcludeExclusionStrategy()); + self::assertEquals( + static::getContent('array_objects_nullable'), + $this->serializer->serialize($arr, $this->getFormat(), $serializationContext), + ); + } + + public function testHandlerInvokedOnPrimitives() + { + $invoked = false; + $this->handlerRegistry->registerHandler( + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'Virtual', + $this->getFormat(), + static function ($visitor, $data) use (&$invoked) { + $invoked = true; + self::assertEquals('foo', $data); + + return null; + }, + ); + + $this->serializer->serialize('foo', $this->getFormat(), null, 'Virtual'); + self::assertTrue($invoked); + } + + public static function getFirstClassListCollectionsValues() + { + $collection = new FirstClassListCollection([1, 2]); + + return [ + [[1, 2, 3], static::getContent('inline_list_collection')], + [[], static::getContent('inline_empty_list_collection')], + [[1, 'a' => 2], static::getContent('inline_deserialization_list_collection'), $collection], + ]; + } + + /** + * @param array $items + * @param array $expected + * + * @dataProvider getFirstClassListCollectionsValues + */ + #[DataProvider('getFirstClassListCollectionsValues')] + public function testFirstClassListCollections($items, $expected, ?FirstClassListCollection $expectedDeserializatrion = null) + { + $collection = new FirstClassListCollection($items); + + self::assertSame($expected, $this->serialize($collection)); + self::assertEquals( + $expectedDeserializatrion ?: $collection, + $this->deserialize($expected, get_class($collection)), + ); + } + + public function testInlineCollection() + { + $list = new AuthorsInline(new Author('foo'), new Author('bar')); + self::assertEquals(static::getContent('authors_inline'), $this->serialize($list)); + self::assertEquals($list, $this->deserialize(static::getContent('authors_inline'), AuthorsInline::class)); + } + + public function testSerializingUnionTypedProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + } + + $object = new TypedProperties\UnionTypedProperties(10000); + + self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); + } + + public function testSerializingUnionDocBlockTypesProperties() + { + $object = new UnionTypedDocBlockProperty(10000); + + self::assertEquals(static::getContent('data_integer'), $this->serialize($object)); + + $object = new UnionTypedDocBlockProperty(1.236); + + self::assertEquals(static::getContent('data_float'), $this->serialize($object)); + + } + + public function testThrowingExceptionWhenDeserializingUnionDocBlockTypes() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } + + $this->expectException(RuntimeException::class); + + $object = new UnionTypedDocBlockProperty(10000); + $deserialized = $this->deserialize(static::getContent('data_integer'), UnionTypedDocBlockProperty::class); + } + + public function testIterable(): void + { + $generator = static function (): iterable { + yield 'foo' => 'bar'; + yield 'bar' => 'foo'; + }; + $withIterable = new ObjectWithIterable($generator()); + self::assertEquals(static::getContent('iterable'), $this->serialize($withIterable)); + + if (!$this->hasDeserializer()) { + return; + } + + self::assertEquals( + new ObjectWithIterable(Functions::iterableToArray($generator())), + $this->deserialize(static::getContent('iterable'), get_class($withIterable)), + ); + } + + public function testGenerator(): void + { + $generator = static function (): \Generator { + yield 'foo' => 'bar'; + yield 'bar' => 'foo'; + }; + $withGenerator = new ObjectWithGenerator($generator()); + self::assertEquals(static::getContent('generator'), $this->serialize($withGenerator)); + + if (!$this->hasDeserializer()) { + return; + } + + self::assertEquals( + $withGenerator, + $this->deserialize(static::getContent('generator'), get_class($withGenerator)), + ); + } + + public function testIterator(): void + { + $iterator = new \ArrayIterator([ + 'foo' => 'bar', + 'bar' => 'foo', + ]); + $withIterator = new ObjectWithIterator($iterator); + self::assertEquals(static::getContent('iterator'), $this->serialize($withIterator)); + + if (!$this->hasDeserializer()) { + return; + } + + self::assertEquals( + $withIterator, + $this->deserialize(static::getContent('iterator'), get_class($withIterator)), + ); + } + + public function testArrayIterator(): void + { + $iterator = new \ArrayIterator([ + 'foo' => 'bar', + 'bar' => 'foo', + ]); + $withArrayIterator = new ObjectWithArrayIterator($iterator); + self::assertEquals(static::getContent('iterator'), $this->serialize($withArrayIterator)); + + if (!$this->hasDeserializer()) { + return; + } + + self::assertEquals( + $withArrayIterator, + $this->deserialize(static::getContent('iterator'), get_class($withArrayIterator)), + ); + } public static function getSerializeNullCases() { diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index b389aa258..502a7028d 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -13,6 +13,7 @@ use JMS\Serializer\Metadata\Driver\TypedPropertiesDriver; use JMS\Serializer\SerializationContext; use JMS\Serializer\Tests\Fixtures\Author; +use JMS\Serializer\Tests\Fixtures\MoreSpecificAuthor; use JMS\Serializer\Tests\Fixtures\AuthorList; use JMS\Serializer\Tests\Fixtures\Comment; use JMS\Serializer\Tests\Fixtures\FirstClassMapCollection; @@ -153,9 +154,10 @@ protected static function getContent($key) $outputs['data_bool'] = '{"data":false}'; $outputs['data_string'] = '{"data":"foo"}'; $outputs['data_author'] = '{"data":{"full_name":"foo"}}'; + $outputs['data_more_specific_author'] = '{"data":{"full_name":"foo", "is_more_specific": true}}'; $outputs['data_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar"}}'; - $outputs['data_discriminated_author'] = '{"data":{"type":"author","full_name":"foo"}}'; - $outputs['data_discriminated_comment'] = '{"data":{"type":"comment","full_name":"foo"}}'; + $outputs['data_discriminated_author'] = '{"data":{"type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedAuthor","full_name":"foo"}}'; + $outputs['data_discriminated_comment'] = '{"data":{"type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedComment","author":{"full_name":"foo"},"text":"bar"}}'; $outputs['uid'] = '"66b3177c-e03b-4a22-9dee-ddd7d37a04d5"'; $outputs['object_with_enums'] = '{"ordinary":"Clubs","backed_value":"C","backed_without_param":"C","ordinary_array":["Clubs","Spades"],"backed_array":["C","H"],"backed_array_without_param":["C","H"],"ordinary_auto_detect":"Clubs","backed_auto_detect":"C","backed_int_auto_detect":3,"backed_int":3,"backed_name":"C","backed_int_forced_str":3}'; $outputs['object_with_autodetect_enums'] = '{"ordinary_array_auto_detect":["Clubs","Spades"],"backed_array_auto_detect":["C","H"],"mixed_array_auto_detect":["Clubs","H"]}'; @@ -169,307 +171,309 @@ protected static function getContent($key) return $outputs[$key]; } - // public function testSkipEmptyArrayAndHash() - // { - // $object = new ObjectWithEmptyArrayAndHash(); - - // self::assertEquals('{}', $this->serialize($object)); - // } - - // public static function getFirstClassMapCollectionsValues() - // { - // return [ - // [['a' => '1', 'b' => '2', 'c' => '3'], self::getContent('inline_map')], - // [[], self::getContent('inline_empty_map')], - // [['a' => 'b', 'c' => 'd', 'e' => '5'], self::getContent('inline_deserialization_map')], - // ]; - // } - - - // #[DataProvider('getFirstClassMapCollectionsValues')] - // public function testFirstClassMapCollections(array $items, string $expected): void - // { - // $collection = new FirstClassMapCollection($items); - - // self::assertSame($expected, $this->serialize($collection)); - // self::assertEquals( - // $collection, - // $this->deserialize($expected, get_class($collection)), - // ); - // } - - // public function testAddLinksToOutput() - // { - // $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { - // self::assertFalse($event->getVisitor()->hasData('_links')); - // }, Author::class, 'json'); - - // $this->dispatcher->addSubscriber(new LinkAddingSubscriber()); - - // $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { - // self::assertTrue($event->getVisitor()->hasData('_links')); - // }, Author::class, 'json'); - - // $this->handlerRegistry->registerHandler( - // GraphNavigatorInterface::DIRECTION_SERIALIZATION, - // AuthorList::class, - // 'json', - // static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { - // return $visitor->visitArray(iterator_to_array($data), $type); - // }, - // ); - - // $list = new AuthorList(); - // $list->add(new Author('foo')); - // $list->add(new Author('bar')); - - // self::assertEquals('[{"full_name":"foo","_links":{"details":"http:\/\/foo.bar\/details\/foo","comments":"http:\/\/foo.bar\/details\/foo\/comments"}},{"full_name":"bar","_links":{"details":"http:\/\/foo.bar\/details\/bar","comments":"http:\/\/foo.bar\/details\/bar\/comments"}}]', $this->serialize($list)); - // } - - // public function testReplaceNameInOutput() - // { - // $this->dispatcher->addSubscriber(new ReplaceNameSubscriber()); - // $this->handlerRegistry->registerHandler( - // GraphNavigatorInterface::DIRECTION_SERIALIZATION, - // AuthorList::class, - // 'json', - // static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { - // return $visitor->visitArray(iterator_to_array($data), $type); - // }, - // ); - - // $list = new AuthorList(); - // $list->add(new Author('foo')); - // $list->add(new Author('bar')); - - // self::assertEquals('[{"full_name":"new name"},{"full_name":"new name"}]', $this->serialize($list)); - // } - - // public function testDeserializingObjectWithObjectPropertyWithNoArrayToObject() - // { - // $content = self::getContent('object_with_object_property_no_array_to_author'); - - // $this->expectException(\RuntimeException::class); - // $this->expectExceptionMessage('Invalid data "baz" (string), expected "JMS\Serializer\Tests\Fixtures\Author".'); - - // $this->deserialize($content, ObjectWithObjectProperty::class); - // } - - // public function testDeserializingObjectWithObjectProperty() - // { - // $content = self::getContent('object_with_object_property'); - // $object = $this->deserialize($content, ObjectWithObjectProperty::class); - // self::assertEquals('bar', $object->getFoo()); - // self::assertInstanceOf(Author::class, $object->getAuthor()); - // self::assertEquals('baz', $object->getAuthor()->getName()); - // } - - // public static function getPrimitiveTypes() - // { - // return [ - // [ - // 'type' => 'boolean', - // 'data' => true, - // ], - // [ - // 'type' => 'integer', - // 'data' => 123, - // ], - // [ - // 'type' => 'string', - // 'data' => 'hello', - // ], - // [ - // 'type' => 'double', - // 'data' => 0.1234, - // ], - // ]; - // } - - // /** - // * @dataProvider getPrimitiveTypes - // */ - // #[DataProvider('getPrimitiveTypes')] - // public function testPrimitiveTypes(string $type, $data) - // { - // $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock(); - - // $factory = new JsonSerializationVisitorFactory(); - // $visitor = $factory->getVisitor(); - // $visitor->setNavigator($navigator); - // $functionToCall = 'visit' . ucfirst($type); - // $result = $visitor->$functionToCall($data, [], $this->getMockBuilder(SerializationContext::class)->getMock()); - // self::{'assertIs' . (['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'][$type] ?? $type)}($result); - // } - - // public function testSerializeEmptyObject() - // { - // self::assertEquals('{}', $this->serialize(new Author(null))); - // } - - // public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOff() - // { - // ini_set('display_errors', '1'); - - // $this->expectException(\RuntimeException::class); - // $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); - - // $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); - // } - - // public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOn() - // { - // ini_set('display_errors', '0'); - - // $this->expectException(\RuntimeException::class); - // $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); - - // $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); - // } - - // public function testSerializeArrayWithEmptyObject() - // { - // self::assertEquals('[{}]', $this->serialize([new \stdClass()])); - // } - - // public function testInlineArray() - // { - // $object = new ObjectWithInlineArray(['a' => 'b', 'c' => 'd']); - // $serialized = $this->serialize($object); - // self::assertEquals('{"a":"b","c":"d"}', $serialized); - // self::assertEquals($object, $this->deserialize($serialized, ObjectWithInlineArray::class)); - // } - - // public function testSerializeRootArrayWithDefinedKeys() - // { - // $author1 = new Author('Jim'); - // $author2 = new Author('Mark'); - - // $data = [ - // 'jim' => $author1, - // 'mark' => $author2, - // ]; - - // self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - // self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - // self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - - // $data = [ - // $author1, - // $author2, - // ]; - // self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - // self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - // self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - // } - - // public static function getTypeHintedArrays() - // { - // return [ - - // [[1, 2], '[1,2]', null], - // [['a', 'b'], '["a","b"]', null], - // [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', null], - - // [[], '[]', null], - // [[], '[]', SerializationContext::create()->setInitialType('array')], - // [[], '[]', SerializationContext::create()->setInitialType('array')], - // [[], '{}', SerializationContext::create()->setInitialType('array')], - - // [[1, 2], '[1,2]', SerializationContext::create()->setInitialType('array')], - // [[1 => 1, 2 => 2], '{"1":1,"2":2}', SerializationContext::create()->setInitialType('array')], - // [[1 => 1, 2 => 2], '[1,2]', SerializationContext::create()->setInitialType('array')], - // [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], - // [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('list')], - - // [[1 => 'a', 2 => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], - // [['a' => 'a', 'b' => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], - - // [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], - // [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], - // [[1, 2], '{"0":"1","1":"2"}', SerializationContext::create()->setInitialType('array')], - - // [['a', 'b'], '{"0":"a","1":"b"}', SerializationContext::create()->setInitialType('array')], - // [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', SerializationContext::create()->setInitialType('array')], + public function testSkipEmptyArrayAndHash() + { + $object = new ObjectWithEmptyArrayAndHash(); + + self::assertEquals('{}', $this->serialize($object)); + } + + public static function getFirstClassMapCollectionsValues() + { + return [ + [['a' => '1', 'b' => '2', 'c' => '3'], self::getContent('inline_map')], + [[], self::getContent('inline_empty_map')], + [['a' => 'b', 'c' => 'd', 'e' => '5'], self::getContent('inline_deserialization_map')], + ]; + } + + + #[DataProvider('getFirstClassMapCollectionsValues')] + public function testFirstClassMapCollections(array $items, string $expected): void + { + $collection = new FirstClassMapCollection($items); + + self::assertSame($expected, $this->serialize($collection)); + self::assertEquals( + $collection, + $this->deserialize($expected, get_class($collection)), + ); + } + + public function testAddLinksToOutput() + { + $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { + self::assertFalse($event->getVisitor()->hasData('_links')); + }, Author::class, 'json'); + + $this->dispatcher->addSubscriber(new LinkAddingSubscriber()); + + $this->dispatcher->addListener('serializer.post_serialize', static function (Event $event) { + self::assertTrue($event->getVisitor()->hasData('_links')); + }, Author::class, 'json'); + + $this->handlerRegistry->registerHandler( + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + AuthorList::class, + 'json', + static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { + return $visitor->visitArray(iterator_to_array($data), $type); + }, + ); + + $list = new AuthorList(); + $list->add(new Author('foo')); + $list->add(new Author('bar')); + + self::assertEquals('[{"full_name":"foo","_links":{"details":"http:\/\/foo.bar\/details\/foo","comments":"http:\/\/foo.bar\/details\/foo\/comments"}},{"full_name":"bar","_links":{"details":"http:\/\/foo.bar\/details\/bar","comments":"http:\/\/foo.bar\/details\/bar\/comments"}}]', $this->serialize($list)); + } + + public function testReplaceNameInOutput() + { + $this->dispatcher->addSubscriber(new ReplaceNameSubscriber()); + $this->handlerRegistry->registerHandler( + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + AuthorList::class, + 'json', + static function (SerializationVisitorInterface $visitor, AuthorList $data, array $type, Context $context) { + return $visitor->visitArray(iterator_to_array($data), $type); + }, + ); + + $list = new AuthorList(); + $list->add(new Author('foo')); + $list->add(new Author('bar')); + + self::assertEquals('[{"full_name":"new name"},{"full_name":"new name"}]', $this->serialize($list)); + } + + public function testDeserializingObjectWithObjectPropertyWithNoArrayToObject() + { + $content = self::getContent('object_with_object_property_no_array_to_author'); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Invalid data "baz" (string), expected "JMS\Serializer\Tests\Fixtures\Author".'); + + $this->deserialize($content, ObjectWithObjectProperty::class); + } + + public function testDeserializingObjectWithObjectProperty() + { + $content = self::getContent('object_with_object_property'); + $object = $this->deserialize($content, ObjectWithObjectProperty::class); + self::assertEquals('bar', $object->getFoo()); + self::assertInstanceOf(Author::class, $object->getAuthor()); + self::assertEquals('baz', $object->getAuthor()->getName()); + } - // [[15.6, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], - // [[5.2 * 3, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], - // ]; - // } + public static function getPrimitiveTypes() + { + return [ + [ + 'type' => 'boolean', + 'data' => true, + ], + [ + 'type' => 'integer', + 'data' => 123, + ], + [ + 'type' => 'string', + 'data' => 'hello', + ], + [ + 'type' => 'double', + 'data' => 0.1234, + ], + ]; + } + /** + * @dataProvider getPrimitiveTypes + */ + #[DataProvider('getPrimitiveTypes')] + public function testPrimitiveTypes(string $type, $data) + { + $navigator = $this->getMockBuilder(GraphNavigatorInterface::class)->getMock(); + + $factory = new JsonSerializationVisitorFactory(); + $visitor = $factory->getVisitor(); + $visitor->setNavigator($navigator); + $functionToCall = 'visit' . ucfirst($type); + $result = $visitor->$functionToCall($data, [], $this->getMockBuilder(SerializationContext::class)->getMock()); + self::{'assertIs' . (['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'][$type] ?? $type)}($result); + } + + public function testSerializeEmptyObject() + { + self::assertEquals('{}', $this->serialize(new Author(null))); + } - // #[DataProvider('getTypeHintedArrays')] - // public function testTypeHintedArraySerialization(array $array, string $expected, ?SerializationContext $context = null) - // { - // self::assertEquals($expected, $this->serialize($array, $context)); - // } + public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOff() + { + ini_set('display_errors', '1'); - // public static function getTypeHintedArraysAndStdClass() - // { - // $c1 = new \stdClass(); - // $c2 = new \stdClass(); - // $c2->foo = 'bar'; + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); - // $tag = new Tag('tag'); + $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); + } - // $c3 = new \stdClass(); - // $c3->foo = $tag; + public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOn() + { + ini_set('display_errors', '0'); - // return [ + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Your data could not be encoded because it contains invalid UTF8 characters.'); - // [[$c1], '[{}]', SerializationContext::create()->setInitialType('array')], + $this->serialize(['foo' => 'bar', 'bar' => pack('H*', 'c32e')]); + } - // [[$c2], '[{"foo":"bar"}]', SerializationContext::create()->setInitialType('array')], + public function testSerializeArrayWithEmptyObject() + { + self::assertEquals('[{}]', $this->serialize([new \stdClass()])); + } - // [[$tag], '[{"name":"tag"}]', SerializationContext::create()->setInitialType('array')], + public function testInlineArray() + { + $object = new ObjectWithInlineArray(['a' => 'b', 'c' => 'd']); + $serialized = $this->serialize($object); + self::assertEquals('{"a":"b","c":"d"}', $serialized); + self::assertEquals($object, $this->deserialize($serialized, ObjectWithInlineArray::class)); + } - // [[$c1], '{"0":{}}', SerializationContext::create()->setInitialType('array')], - // [[$c2], '{"0":{"foo":"bar"}}', SerializationContext::create()->setInitialType('array')], + public function testSerializeRootArrayWithDefinedKeys() + { + $author1 = new Author('Jim'); + $author2 = new Author('Mark'); - // [[$c3], '{"0":{"foo":{"name":"tag"}}}', SerializationContext::create()->setInitialType('array')], - // [[$c3], '[{"foo":{"name":"tag"}}]', SerializationContext::create()->setInitialType('array')], + $data = [ + 'jim' => $author1, + 'mark' => $author2, + ]; - // [[$tag], '{"0":{"name":"tag"}}', SerializationContext::create()->setInitialType('array')], - // ]; - // } + self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + self::assertEquals('{"jim":{"full_name":"Jim"},"mark":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); - // public function testDeserializingUnionProperties() - // { - // if (PHP_VERSION_ID < 80000) { - // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + $data = [ + $author1, + $author2, + ]; + self::assertEquals('[{"full_name":"Jim"},{"full_name":"Mark"}]', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + self::assertEquals('{"0":{"full_name":"Jim"},"1":{"full_name":"Mark"}}', $this->serializer->serialize($data, $this->getFormat(), SerializationContext::create()->setInitialType('array'))); + } - // return; - // } + public static function getTypeHintedArrays() + { + return [ - // $object = new UnionTypedProperties(10000); - // self::assertEquals($object, $this->deserialize(static::getContent('data_integer'), UnionTypedProperties::class)); + [[1, 2], '[1,2]', null], + [['a', 'b'], '["a","b"]', null], + [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', null], - // $object = new UnionTypedProperties(1.236); - // self::assertEquals($object, $this->deserialize(static::getContent('data_float'), UnionTypedProperties::class)); + [[], '[]', null], + [[], '[]', SerializationContext::create()->setInitialType('array')], + [[], '[]', SerializationContext::create()->setInitialType('array')], + [[], '{}', SerializationContext::create()->setInitialType('array')], - // $object = new UnionTypedProperties(false); - // self::assertEquals($object, $this->deserialize(static::getContent('data_bool'), UnionTypedProperties::class)); + [[1, 2], '[1,2]', SerializationContext::create()->setInitialType('array')], + [[1 => 1, 2 => 2], '{"1":1,"2":2}', SerializationContext::create()->setInitialType('array')], + [[1 => 1, 2 => 2], '[1,2]', SerializationContext::create()->setInitialType('array')], + [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], + [['a', 'b'], '["a","b"]', SerializationContext::create()->setInitialType('list')], - // $object = new UnionTypedProperties('foo'); - // self::assertEquals($object, $this->deserialize(static::getContent('data_string'), UnionTypedProperties::class)); - // } + [[1 => 'a', 2 => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], + [['a' => 'a', 'b' => 'b'], '["a","b"]', SerializationContext::create()->setInitialType('array')], - // public function testDeserializingComplexUnionProperties() - // { - // if (PHP_VERSION_ID < 80000) { - // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], + [[1, 2], '{"0":1,"1":2}', SerializationContext::create()->setInitialType('array')], + [[1, 2], '{"0":"1","1":"2"}', SerializationContext::create()->setInitialType('array')], - // return; - // } + [['a', 'b'], '{"0":"a","1":"b"}', SerializationContext::create()->setInitialType('array')], + [['a' => 'a', 'b' => 'b'], '{"a":"a","b":"b"}', SerializationContext::create()->setInitialType('array')], - // $authorUnion = new ComplexUnionTypedProperties(new Author('foo')); - // self::assertEquals($authorUnion, $this->deserialize(static::getContent('data_author'), ComplexUnionTypedProperties::class)); + [[15.6, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], + [[5.2 * 3, 2], '[15.6,2.0]', SerializationContext::create()->setInitialType('array')], + ]; + } + + + #[DataProvider('getTypeHintedArrays')] + public function testTypeHintedArraySerialization(array $array, string $expected, ?SerializationContext $context = null) + { + self::assertEquals($expected, $this->serialize($array, $context)); + } + + public static function getTypeHintedArraysAndStdClass() + { + $c1 = new \stdClass(); + $c2 = new \stdClass(); + $c2->foo = 'bar'; + + $tag = new Tag('tag'); + + $c3 = new \stdClass(); + $c3->foo = $tag; + + return [ + + [[$c1], '[{}]', SerializationContext::create()->setInitialType('array')], + + [[$c2], '[{"foo":"bar"}]', SerializationContext::create()->setInitialType('array')], + + [[$tag], '[{"name":"tag"}]', SerializationContext::create()->setInitialType('array')], + + [[$c1], '{"0":{}}', SerializationContext::create()->setInitialType('array')], + [[$c2], '{"0":{"foo":"bar"}}', SerializationContext::create()->setInitialType('array')], + + [[$c3], '{"0":{"foo":{"name":"tag"}}}', SerializationContext::create()->setInitialType('array')], + [[$c3], '[{"foo":{"name":"tag"}}]', SerializationContext::create()->setInitialType('array')], + + [[$tag], '{"0":{"name":"tag"}}', SerializationContext::create()->setInitialType('array')], + ]; + } + + public function testDeserializingUnionProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } + + $object = new UnionTypedProperties(10000); + self::assertEquals($object, $this->deserialize(static::getContent('data_integer'), UnionTypedProperties::class)); + + $object = new UnionTypedProperties(1.236); + self::assertEquals($object, $this->deserialize(static::getContent('data_float'), UnionTypedProperties::class)); + + $object = new UnionTypedProperties(false); + self::assertEquals($object, $this->deserialize(static::getContent('data_bool'), UnionTypedProperties::class)); + + $object = new UnionTypedProperties('foo'); + self::assertEquals($object, $this->deserialize(static::getContent('data_string'), UnionTypedProperties::class)); + } + + public function testDeserializingNonDiscriminatedComplexUnionProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } + + $authorUnion = new ComplexUnionTypedProperties(new Author('foo')); + self::assertEquals($authorUnion, $this->deserialize(static::getContent('data_author'), ComplexUnionTypedProperties::class)); - // $commentUnion = new ComplexUnionTypedProperties(new Comment(new Author('foo'), 'bar')); - // $deserialized = $this->deserialize(static::getContent('data_comment'), ComplexUnionTypedProperties::class); + $commentUnion = new ComplexUnionTypedProperties(new Comment(new Author('foo'), 'bar')); + self::assertEquals($commentUnion, $this->deserialize(static::getContent('data_comment'), ComplexUnionTypedProperties::class)); + + $moreSpecificAuthor = new ComplexUnionTypedProperties(new MoreSpecificAuthor('foo', true), 'bar'); + self::assertEquals($moreSpecificAuthor, $this->deserialize(static::getContent('data_more_specific_author'), ComplexUnionTypedProperties::class)); - // self::assertEquals($commentUnion, $deserialized); - // } + } public function testDeserializingComplexDiscriminatedUnionProperties() { @@ -483,30 +487,30 @@ public function testDeserializingComplexDiscriminatedUnionProperties() self::assertEquals($authorUnion, $this->deserialize(static::getContent('data_discriminated_author'), ComplexDiscriminatedUnion::class)); $commentUnion = new ComplexDiscriminatedUnion(new DiscriminatedComment(new Author('foo'), 'bar')); - $deserialized = $this->deserialize(static::getContent('data_discriminated_comment'), ComplexDiscriminatedUnion::class); - self::assertEquals($commentUnion, $deserialized); + self::assertEquals($commentUnion, $this->deserialize(static::getContent('data_discriminated_comment'), ComplexDiscriminatedUnion::class)); } - // public function testSerializeUnionProperties() - // { - // if (PHP_VERSION_ID < 80000) { - // $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - // return; - // } + public function testSerializeUnionProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } - // $serialized = $this->serialize(new UnionTypedProperties(10000)); - // self::assertEquals(static::getContent('data_integer'), $serialized); - // } + $serialized = $this->serialize(new UnionTypedProperties(10000)); + self::assertEquals(static::getContent('data_integer'), $serialized); + } /** * @dataProvider getTypeHintedArraysAndStdClass */ - // #[DataProvider('getTypeHintedArraysAndStdClass')] - // public function testTypeHintedArrayAncdtdClassSerialization(array $array, string $expected, ?SerializationContext $context = null) - // { - // self::assertEquals($expected, $this->serialize($array, $context)); - // } + #[DataProvider('getTypeHintedArraysAndStdClass')] + public function testTypeHintedArrayAncdtdClassSerialization(array $array, string $expected, ?SerializationContext $context = null) + { + self::assertEquals($expected, $this->serialize($array, $context)); + } protected function getFormat() { From 5a4128ca0eaf68c226ff98212e1c4555799fc85a Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Mon, 8 Jul 2024 17:04:10 -0400 Subject: [PATCH 03/14] phpcs --- phpcs.xml.dist | 4 ++- .../DeserializationGraphNavigator.php | 21 +++++++------ src/Handler/UnionHandler.php | 29 ++++++++--------- src/JsonDeserializationStrictVisitor.php | 2 ++ src/JsonDeserializationVisitor.php | 3 +- .../Driver/AnnotationOrAttributeDriver.php | 2 +- src/Metadata/Driver/TypedPropertiesDriver.php | 31 ++++++++++++------- src/Metadata/PropertyMetadata.php | 1 + src/XmlDeserializationVisitor.php | 3 +- 9 files changed, 57 insertions(+), 39 deletions(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index fec7fd4ce..e2ed35f0c 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -68,7 +68,9 @@ - + + + diff --git a/src/GraphNavigator/DeserializationGraphNavigator.php b/src/GraphNavigator/DeserializationGraphNavigator.php index 4ee9b2ba0..2c662b7a4 100644 --- a/src/GraphNavigator/DeserializationGraphNavigator.php +++ b/src/GraphNavigator/DeserializationGraphNavigator.php @@ -14,9 +14,9 @@ use JMS\Serializer\Exception\ExpressionLanguageRequiredException; use JMS\Serializer\Exception\LogicException; use JMS\Serializer\Exception\NotAcceptableException; +use JMS\Serializer\Exception\PropertyMissingException; use JMS\Serializer\Exception\RuntimeException; use JMS\Serializer\Exception\SkipHandlerException; -use JMS\Serializer\Exception\PropertyMissingException; use JMS\Serializer\Exclusion\ExpressionLanguageExclusionStrategy; use JMS\Serializer\Expression\ExpressionEvaluatorInterface; use JMS\Serializer\GraphNavigator; @@ -198,7 +198,7 @@ public function accept($data, ?array $type = null) $this->visitor->startVisitingObject($metadata, $object, $type); foreach ($metadata->propertyMetadata as $propertyMetadata) { - $allowsNull = $propertyMetadata->type == null ? true : $this->allowsNull($propertyMetadata->type); + $allowsNull = null === $propertyMetadata->type ? true : $this->allowsNull($propertyMetadata->type); if (null !== $this->exclusionStrategy && $this->exclusionStrategy->shouldSkipProperty($propertyMetadata, $this->context)) { continue; } @@ -223,31 +223,34 @@ public function accept($data, ?array $type = null) $this->accessor->setValue($object, $cloned->defaultValue, $cloned, $this->context); } elseif (!$allowsNull && $this->visitor->getRequireAllRequiredProperties()) { $this->visitor->endVisitingObject($metadata, $data, $type); + throw new PropertyMissingException("Property $propertyMetadata->name is missing from data "); } } - + $this->context->popPropertyMetadata(); } - $rs = $this->visitor->endVisitingObject($metadata, $data, $type); $this->afterVisitingObject($metadata, $rs, $type); + return $rs; } } - private function allowsNull(array $type) { + private function allowsNull(array $type) + { $allowsNull = false; - if ($type['name'] === 'union') { - foreach($type['params'] as $param) { - if ($param['name'] === 'NULL') { + if ('union' === $type['name']) { + foreach ($type['params'] as $param) { + if ('NULL' === $param['name']) { $allowsNull = true; } } - } elseif($type['name'] === 'NULL') { + } elseif ('NULL' === $type['name']) { $allowsNull = true; } + return $allowsNull; } diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 54678a143..4c759b650 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -6,13 +6,13 @@ use JMS\Serializer\Context; use JMS\Serializer\DeserializationContext; +use JMS\Serializer\Exception\NonVisitableTypeException; +use JMS\Serializer\Exception\PropertyMissingException; use JMS\Serializer\Exception\RuntimeException; use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\SerializationContext; use JMS\Serializer\Visitor\DeserializationVisitorInterface; use JMS\Serializer\Visitor\SerializationVisitorInterface; -use JMS\Serializer\Exception\NonVisitableTypeException; -use JMS\Serializer\Exception\PropertyMissingException; final class UnionHandler implements SubscribingHandlerInterface { @@ -64,13 +64,14 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed if ($data instanceof \SimpleXMLElement) { throw new RuntimeException('XML deserialisation into union types is not supported yet.'); } + return $this->deserializeType($visitor, $data, $type, $context); } private function deserializeType(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context) { $alternativeName = null; - + foreach ($type['params'] as $possibleType) { if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { continue; @@ -78,11 +79,12 @@ private function deserializeType(DeserializationVisitorInterface $visitor, mixed $propertyMetadata = $context->getMetadataStack()->top(); $discriminatorAttribute = $propertyMetadata->unionDiscriminatorAttribute; - if ($discriminatorAttribute !== null) { + if (null !== $discriminatorAttribute) { $finalType = [ 'name' => $data[$discriminatorAttribute], - 'params' => [] + 'params' => [], ]; + return $context->getNavigator()->accept($data, $finalType); } else { try { @@ -103,37 +105,36 @@ private function deserializeType(DeserializationVisitorInterface $visitor, mixed continue; } } - } } private function matchSimpleType(mixed $data, array $type, Context $context) { $alternativeName = null; - + foreach ($type['params'] as $possibleType) { if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { continue; } - try { - $accept = $context->getNavigator()->accept($data, $possibleType); - return $accept; + try { + return $context->getNavigator()->accept($data, $possibleType); } catch (NonVisitableTypeException $e) { continue; } catch (PropertyMissingException $e) { continue; } - } } - private function isPrimitiveType(string $type): bool { + private function isPrimitiveType(string $type): bool + { return in_array($type, ['int', 'integer', 'float', 'double', 'bool', 'boolean', 'string']); } - private function testPrimitive(mixed $data, string $type, string $format): bool { - switch($type) { + private function testPrimitive(mixed $data, string $type, string $format): bool + { + switch ($type) { case 'integer': case 'int': return (string) (int) $data === (string) $data; diff --git a/src/JsonDeserializationStrictVisitor.php b/src/JsonDeserializationStrictVisitor.php index 3fc1ca205..ecae984a9 100644 --- a/src/JsonDeserializationStrictVisitor.php +++ b/src/JsonDeserializationStrictVisitor.php @@ -21,6 +21,7 @@ final class JsonDeserializationStrictVisitor extends AbstractVisitor implements /** * THIS IS ONLY USED FOR UNION DESERIALIZATION WHICH IS NOT SUPPORTED IN XML + * * @var bool */ private $requireAllRequiredProperties = false; @@ -31,6 +32,7 @@ public function __construct( ) { $this->wrappedDeserializationVisitor = new JsonDeserializationVisitor($options, $depth); } + public function setRequireAllRequiredProperties(bool $requireAllRequiredProperties): void { $this->requireAllRequiredProperties = $requireAllRequiredProperties; diff --git a/src/JsonDeserializationVisitor.php b/src/JsonDeserializationVisitor.php index 76b3bd5ee..c6e22bb98 100644 --- a/src/JsonDeserializationVisitor.php +++ b/src/JsonDeserializationVisitor.php @@ -168,7 +168,7 @@ public function visitDiscriminatorMapProperty($data, ClassMetadata $metadata): s */ public function startVisitingObject(ClassMetadata $metadata, object $object, array $type): void { - $cur = $this->getCurrentObject() ? get_class($this->getCurrentObject()): 'null'; + $cur = $this->getCurrentObject() ? get_class($this->getCurrentObject()) : 'null'; $objtype = $object ? get_class($object) : 'null'; $stacksize = $this->objectStack->count(); $this->setCurrentObject($object); @@ -219,6 +219,7 @@ public function endVisitingObject(ClassMetadata $metadata, $data, array $type): $this->revertCurrentObject(); $stacksize = $this->objectStack->count(); + return $obj; } diff --git a/src/Metadata/Driver/AnnotationOrAttributeDriver.php b/src/Metadata/Driver/AnnotationOrAttributeDriver.php index 0b55c30a4..dd6fbfd59 100644 --- a/src/Metadata/Driver/AnnotationOrAttributeDriver.php +++ b/src/Metadata/Driver/AnnotationOrAttributeDriver.php @@ -24,6 +24,7 @@ use JMS\Serializer\Annotation\Since; use JMS\Serializer\Annotation\SkipWhenEmpty; use JMS\Serializer\Annotation\Type; +use JMS\Serializer\Annotation\UnionDiscriminator; use JMS\Serializer\Annotation\Until; use JMS\Serializer\Annotation\VirtualProperty; use JMS\Serializer\Annotation\XmlAttribute; @@ -36,7 +37,6 @@ use JMS\Serializer\Annotation\XmlNamespace; use JMS\Serializer\Annotation\XmlRoot; use JMS\Serializer\Annotation\XmlValue; -use JMS\Serializer\Annotation\UnionDiscriminator; use JMS\Serializer\Exception\InvalidMetadataException; use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface; use JMS\Serializer\Metadata\ClassMetadata; diff --git a/src/Metadata/Driver/TypedPropertiesDriver.php b/src/Metadata/Driver/TypedPropertiesDriver.php index f5900f5f0..d4a5105ec 100644 --- a/src/Metadata/Driver/TypedPropertiesDriver.php +++ b/src/Metadata/Driver/TypedPropertiesDriver.php @@ -50,7 +50,7 @@ public function __construct(DriverInterface $delegate, ?ParserInterface $typePar /** * In order to deserialize non-discriminated unions, each possible type is attempted in turn. * Therefore, the types must be ordered from most specific to least specific, so that the most specific type is attempted first. - * + * * ReflectionUnionType::getTypes() does not return types in that order, so we need to reorder them. * * This method reorders the types in the following order: @@ -61,30 +61,35 @@ private function reorderTypes(array $type): array { $self = $this; if ($type['params']) { - uasort($type['params'], function ($a, $b) use ($self) { + uasort($type['params'], static function ($a, $b) use ($self) { if (\class_exists($a['name']) && \class_exists($b['name'])) { $aMetadata = $self->loadMetadataForClass(new \ReflectionClass($a['name'])); $bMetadata = $self->loadMetadataForClass(new \ReflectionClass($b['name'])); $aRequiredPropertyCount = 0; $bRequiredPropertyCount = 0; foreach ($aMetadata->propertyMetadata as $propertyMetadata) { - if (!$self->allowsNull($propertyMetadata->type)) { + if ($propertyMetadata->type && !$self->allowsNull($propertyMetadata->type)) { $aRequiredPropertyCount++; } } + foreach ($bMetadata->propertyMetadata as $propertyMetadata) { - if (!$self->allowsNull($propertyMetadata->type)) { + if ($propertyMetadata->type && !$self->allowsNull($propertyMetadata->type)) { $bRequiredPropertyCount++; } } + return $bRequiredPropertyCount <=> $aRequiredPropertyCount; } - if(\class_exists($a['name'])) { + + if (\class_exists($a['name'])) { return 1; } - if(\class_exists($b['name'])) { + + if (\class_exists($b['name'])) { return -1; } + $order = ['null' => 0, 'true' => 1, 'false' => 2, 'bool' => 3, 'int' => 4, 'float' => 5, 'string' => 6]; return ($order[$a['name']] ?? 7) <=> ($order[$b['name']] ?? 7); @@ -93,18 +98,20 @@ private function reorderTypes(array $type): array return $type; } - - private function allowsNull(array $type) { + + private function allowsNull(array $type) + { $allowsNull = false; - if ($type['name'] === 'union') { - foreach($type['params'] as $param) { - if ($param['name'] === 'NULL') { + if ('union' === $type['name']) { + foreach ($type['params'] as $param) { + if ('NULL' === $param['name']) { $allowsNull = true; } } - } elseif($type['name'] === 'NULL') { + } elseif ('NULL' === $type['name']) { $allowsNull = true; } + return $allowsNull; } diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index f6f9c703d..776eb7f29 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -201,6 +201,7 @@ public function setAccessor(string $type, ?string $getter = null, ?string $sette $this->getter = $getter; $this->setter = $setter; } + public function setUnionDiscriminator(string $field): void { $this->unionDiscriminatorAttribute = $field; diff --git a/src/XmlDeserializationVisitor.php b/src/XmlDeserializationVisitor.php index 64b803421..b9cacfed0 100644 --- a/src/XmlDeserializationVisitor.php +++ b/src/XmlDeserializationVisitor.php @@ -56,6 +56,7 @@ final class XmlDeserializationVisitor extends AbstractVisitor implements NullAwa /** * THIS IS ONLY USED FOR UNION DESERIALIZATION WHICH IS NOT SUPPORTED IN XML + * * @var bool */ private $requireAllRequiredProperties = false; @@ -71,8 +72,8 @@ public function __construct( $this->disableExternalEntities = $disableExternalEntities; $this->doctypeAllowList = $doctypeAllowList; $this->options = $options; - } + public function setRequireAllRequiredProperties(bool $requireAllRequiredProperties): void { $this->requireAllRequiredProperties = $requireAllRequiredProperties; From 1013bd85621a25d85a23fe861fadacd2219aba12 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Tue, 9 Jul 2024 09:25:01 -0400 Subject: [PATCH 04/14] iter --- src/Handler/UnionHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 4c759b650..6718bdcc7 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -108,7 +108,7 @@ private function deserializeType(DeserializationVisitorInterface $visitor, mixed } } - private function matchSimpleType(mixed $data, array $type, Context $context) + private function matchSimpleType(mixed $data, array $type, Context $context): mixed { $alternativeName = null; From 27adca89ca8bb856fa45ea3009b7175943b500c3 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Tue, 9 Jul 2024 10:29:01 -0400 Subject: [PATCH 05/14] add tests for serialization --- .../SerializationGraphNavigator.php | 13 +++++ src/Handler/UnionHandler.php | 12 ++++- tests/Serializer/JsonSerializationTest.php | 53 ++++++++++++++++--- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/GraphNavigator/SerializationGraphNavigator.php b/src/GraphNavigator/SerializationGraphNavigator.php index 1de6e3bb7..beb50e307 100644 --- a/src/GraphNavigator/SerializationGraphNavigator.php +++ b/src/GraphNavigator/SerializationGraphNavigator.php @@ -177,6 +177,19 @@ public function accept($data, ?array $type = null) throw new RuntimeException($msg); + case 'union': + if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, $type['name'], $this->format)) { + try { + $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context); + return $rs; + } catch (SkipHandlerException $e) { + // Skip handler, fallback to default behavior + } catch (NotAcceptableException $e) { + $this->context->stopVisiting($data); + + throw $e; + } + } default: if (null !== $data) { if ($this->context->isVisiting($data)) { diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 6718bdcc7..ade3faa68 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -56,7 +56,17 @@ public function serializeUnion( array $type, SerializationContext $context ) { - return $this->matchSimpleType($data, $type, $context); + if ($this->isPrimitiveType(gettype($data))) { + + return $this->matchSimpleType($data, $type, $context); + } else { + $resolvedType = [ + 'name' => get_class($data), + 'params' => [] + ]; + + return $context->getNavigator()->accept($data, $resolvedType); + } } public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context) diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index 502a7028d..c1e64aafb 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -154,10 +154,10 @@ protected static function getContent($key) $outputs['data_bool'] = '{"data":false}'; $outputs['data_string'] = '{"data":"foo"}'; $outputs['data_author'] = '{"data":{"full_name":"foo"}}'; - $outputs['data_more_specific_author'] = '{"data":{"full_name":"foo", "is_more_specific": true}}'; + $outputs['data_more_specific_author'] = '{"data":{"full_name":"foo","is_more_specific":true}}'; $outputs['data_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar"}}'; - $outputs['data_discriminated_author'] = '{"data":{"type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedAuthor","full_name":"foo"}}'; - $outputs['data_discriminated_comment'] = '{"data":{"type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedComment","author":{"full_name":"foo"},"text":"bar"}}'; + $outputs['data_discriminated_author'] = '{"data":{"full_name":"foo","type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedAuthor"}}'; + $outputs['data_discriminated_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar","type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedComment"}}'; $outputs['uid'] = '"66b3177c-e03b-4a22-9dee-ddd7d37a04d5"'; $outputs['object_with_enums'] = '{"ordinary":"Clubs","backed_value":"C","backed_without_param":"C","ordinary_array":["Clubs","Spades"],"backed_array":["C","H"],"backed_array_without_param":["C","H"],"ordinary_auto_detect":"Clubs","backed_auto_detect":"C","backed_int_auto_detect":3,"backed_int":3,"backed_name":"C","backed_int_forced_str":3}'; $outputs['object_with_autodetect_enums'] = '{"ordinary_array_auto_detect":["Clubs","Spades"],"backed_array_auto_detect":["C","H"],"mixed_array_auto_detect":["Clubs","H"]}'; @@ -456,6 +456,27 @@ public function testDeserializingUnionProperties() self::assertEquals($object, $this->deserialize(static::getContent('data_string'), UnionTypedProperties::class)); } + public function testSerializingUnionProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } + + $serialized = $this->serialize(new UnionTypedProperties(10000)); + self::assertEquals(static::getContent('data_integer'), $serialized); + + $serialized = $this->serialize(new UnionTypedProperties(1.236)); + self::assertEquals(static::getContent('data_float'), $serialized); + + $serialized = $this->serialize(new UnionTypedProperties(false)); + self::assertEquals(static::getContent('data_bool'), $serialized); + + $serialized = $this->serialize(new UnionTypedProperties('foo')); + self::assertEquals(static::getContent('data_string'), $serialized); + } + public function testDeserializingNonDiscriminatedComplexUnionProperties() { if (PHP_VERSION_ID < 80000) { @@ -472,7 +493,24 @@ public function testDeserializingNonDiscriminatedComplexUnionProperties() $moreSpecificAuthor = new ComplexUnionTypedProperties(new MoreSpecificAuthor('foo', true), 'bar'); self::assertEquals($moreSpecificAuthor, $this->deserialize(static::getContent('data_more_specific_author'), ComplexUnionTypedProperties::class)); + } + public function testSerializingNonDiscriminatedComplexUnionProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } + + $serialized = $this->serialize(new ComplexUnionTypedProperties(new Author('foo'))); + self::assertEquals(static::getContent('data_author'), $serialized); + + $serialized = $this->serialize(new ComplexUnionTypedProperties(new Comment(new Author('foo'), 'bar'))); + self::assertEquals(static::getContent('data_comment'), $serialized); + + $serialized = $this->serialize(new ComplexUnionTypedProperties(new MoreSpecificAuthor('foo', true), 'bar')); + self::assertEquals(static::getContent('data_more_specific_author'), $serialized); } public function testDeserializingComplexDiscriminatedUnionProperties() @@ -491,7 +529,7 @@ public function testDeserializingComplexDiscriminatedUnionProperties() self::assertEquals($commentUnion, $this->deserialize(static::getContent('data_discriminated_comment'), ComplexDiscriminatedUnion::class)); } - public function testSerializeUnionProperties() + public function testSerializeingComplexDiscriminatedUnionProperties() { if (PHP_VERSION_ID < 80000) { $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); @@ -499,8 +537,11 @@ public function testSerializeUnionProperties() return; } - $serialized = $this->serialize(new UnionTypedProperties(10000)); - self::assertEquals(static::getContent('data_integer'), $serialized); + $serialized = $this->serialize(new ComplexDiscriminatedUnion(new DiscriminatedAuthor('foo'))); + self::assertEquals(static::getContent('data_discriminated_author'), $serialized); + + $serialized = $this->serialize(new ComplexDiscriminatedUnion(new DiscriminatedComment(new Author('foo'), 'bar'))); + self::assertEquals(static::getContent('data_discriminated_comment'), $serialized); } /** From 8fe8a7cd8d7cde3404f75dd8728b8d7bc2907757 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Tue, 9 Jul 2024 11:17:51 -0400 Subject: [PATCH 06/14] iter --- .../SerializationGraphNavigator.php | 3 +-- src/Handler/UnionHandler.php | 18 +++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/GraphNavigator/SerializationGraphNavigator.php b/src/GraphNavigator/SerializationGraphNavigator.php index beb50e307..f6fbed112 100644 --- a/src/GraphNavigator/SerializationGraphNavigator.php +++ b/src/GraphNavigator/SerializationGraphNavigator.php @@ -180,8 +180,7 @@ public function accept($data, ?array $type = null) case 'union': if (null !== $handler = $this->handlerRegistry->getHandler(GraphNavigatorInterface::DIRECTION_SERIALIZATION, $type['name'], $this->format)) { try { - $rs = \call_user_func($handler, $this->visitor, $data, $type, $this->context); - return $rs; + return \call_user_func($handler, $this->visitor, $data, $type, $this->context); } catch (SkipHandlerException $e) { // Skip handler, fallback to default behavior } catch (NotAcceptableException $e) { diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index ade3faa68..9a0ada2d7 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -55,36 +55,30 @@ public function serializeUnion( mixed $data, array $type, SerializationContext $context - ) { + ): mixed { if ($this->isPrimitiveType(gettype($data))) { - return $this->matchSimpleType($data, $type, $context); } else { $resolvedType = [ 'name' => get_class($data), - 'params' => [] + 'params' => [], ]; return $context->getNavigator()->accept($data, $resolvedType); } } - public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context) + public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context): mixed { if ($data instanceof \SimpleXMLElement) { throw new RuntimeException('XML deserialisation into union types is not supported yet.'); } - return $this->deserializeType($visitor, $data, $type, $context); - } - - private function deserializeType(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context) - { $alternativeName = null; foreach ($type['params'] as $possibleType) { - if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { - continue; + if ($this->isPrimitiveType($possibleType['name']) && $this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { + return $context->getNavigator()->accept($data, $possibleType); } $propertyMetadata = $context->getMetadataStack()->top(); @@ -135,6 +129,8 @@ private function matchSimpleType(mixed $data, array $type, Context $context): mi continue; } } + + return null; } private function isPrimitiveType(string $type): bool From 08f91522a5b9300371f811fb5793ac0e88524304 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Wed, 10 Jul 2024 15:45:44 -0400 Subject: [PATCH 07/14] iter --- src/Annotation/Discriminator.php | 2 +- src/Annotation/UnionDiscriminator.php | 5 ++++- src/Handler/UnionHandler.php | 17 ++++++++++++++--- .../Driver/AnnotationOrAttributeDriver.php | 2 +- src/Metadata/PropertyMetadata.php | 18 +++++++++++++----- .../ComplexDiscriminatedUnion.php | 1 + 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Annotation/Discriminator.php b/src/Annotation/Discriminator.php index f010036f8..0dbaafd2a 100644 --- a/src/Annotation/Discriminator.php +++ b/src/Annotation/Discriminator.php @@ -13,7 +13,7 @@ class Discriminator implements SerializerAttribute { use AnnotationUtilsTrait; - /** @var array */ + /** @var array */ public $map = []; /** @var string */ diff --git a/src/Annotation/UnionDiscriminator.php b/src/Annotation/UnionDiscriminator.php index f062b3064..57f33fc38 100644 --- a/src/Annotation/UnionDiscriminator.php +++ b/src/Annotation/UnionDiscriminator.php @@ -13,10 +13,13 @@ final class UnionDiscriminator implements SerializerAttribute { use AnnotationUtilsTrait; + /** @var array */ + public $map = []; + /** @var string */ public $field = 'type'; - public function __construct(array $values = [], string $field = 'type') + public function __construct(array $values = [], string $field = 'type', array $map = []) { $this->loadAnnotationParameters(get_defined_vars()); } diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 9a0ada2d7..adf4304ef 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -82,13 +82,24 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed } $propertyMetadata = $context->getMetadataStack()->top(); - $discriminatorAttribute = $propertyMetadata->unionDiscriminatorAttribute; - if (null !== $discriminatorAttribute) { + if (null !== $propertyMetadata->unionDiscriminatorMap) { + if (array_key_exists($propertyMetadata->unionDiscriminatorField, $data) && array_key_exists($data[$propertyMetadata->unionDiscriminatorField], $propertyMetadata->unionDiscriminatorMap)) { + $lkup = $data[$propertyMetadata->unionDiscriminatorField]; + $finalType = [ + 'name' => $propertyMetadata->unionDiscriminatorMap[$lkup], + 'params' => [] + ]; + } else { + throw new NonVisitableTypeException("Union Discriminator Map does not contain field '$propertyMetadata->unionDiscriminatorField'"); + } + } else { $finalType = [ - 'name' => $data[$discriminatorAttribute], + 'name' => $data[$propertyMetadata->unionDiscriminatorField], 'params' => [], ]; + } + if (null !== $finalType) { return $context->getNavigator()->accept($data, $finalType); } else { try { diff --git a/src/Metadata/Driver/AnnotationOrAttributeDriver.php b/src/Metadata/Driver/AnnotationOrAttributeDriver.php index dd6fbfd59..61589aadf 100644 --- a/src/Metadata/Driver/AnnotationOrAttributeDriver.php +++ b/src/Metadata/Driver/AnnotationOrAttributeDriver.php @@ -260,7 +260,7 @@ public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadat } elseif ($annot instanceof MaxDepth) { $propertyMetadata->maxDepth = $annot->depth; } elseif ($annot instanceof UnionDiscriminator) { - $propertyMetadata->setUnionDiscriminator($annot->field); + $propertyMetadata->setUnionDiscriminator($annot->field, $annot->map); } } diff --git a/src/Metadata/PropertyMetadata.php b/src/Metadata/PropertyMetadata.php index 776eb7f29..aeb88b952 100644 --- a/src/Metadata/PropertyMetadata.php +++ b/src/Metadata/PropertyMetadata.php @@ -37,7 +37,12 @@ class PropertyMetadata extends BasePropertyMetadata /** * @var string|null */ - public $unionDiscriminatorAttribute; + public $unionDiscriminatorField; + + /** + * @var array|null + */ + public $unionDiscriminatorMap; /** * @var array|null @@ -202,9 +207,10 @@ public function setAccessor(string $type, ?string $getter = null, ?string $sette $this->setter = $setter; } - public function setUnionDiscriminator(string $field): void + public function setUnionDiscriminator(string $field, ?array $map): void { - $this->unionDiscriminatorAttribute = $field; + $this->unionDiscriminatorField = $field; + $this->unionDiscriminatorMap = $map; } public function setType(array $type): void @@ -235,7 +241,8 @@ protected function serializeToArray(): array $this->untilVersion, $this->groups, $this->serializedName, - $this->unionDiscriminatorAttribute, + $this->unionDiscriminatorField, + $this->unionDiscriminatorMap, $this->type, $this->xmlCollection, $this->xmlCollectionInline, @@ -270,7 +277,8 @@ protected function unserializeFromArray(array $data): void $this->untilVersion, $this->groups, $this->serializedName, - $this->unionDiscriminatorAttribute, + $this->unionDiscriminatorField, + $this->unionDiscriminatorMap, $this->type, $this->xmlCollection, $this->xmlCollectionInline, diff --git a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php index b60227b49..f0a158120 100644 --- a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php +++ b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php @@ -10,6 +10,7 @@ class ComplexDiscriminatedUnion { + #[Type('JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor|JMS\Serializer\Tests\Fixtures\DiscriminatedComment')] #[UnionDiscriminator(field: 'type')] private DiscriminatedAuthor|DiscriminatedComment $data; From 72a2124d8cdb63d6a572c4929daee8b0ea0f4190 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Mon, 15 Jul 2024 16:23:22 -0400 Subject: [PATCH 08/14] handle deserialization failures. --- .../DeserializationGraphNavigator.php | 4 ++++ src/Handler/UnionHandler.php | 16 ++++++++++------ src/SerializerBuilder.php | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/GraphNavigator/DeserializationGraphNavigator.php b/src/GraphNavigator/DeserializationGraphNavigator.php index 2c662b7a4..271354478 100644 --- a/src/GraphNavigator/DeserializationGraphNavigator.php +++ b/src/GraphNavigator/DeserializationGraphNavigator.php @@ -225,6 +225,10 @@ public function accept($data, ?array $type = null) $this->visitor->endVisitingObject($metadata, $data, $type); throw new PropertyMissingException("Property $propertyMetadata->name is missing from data "); + } else { + $this->visitor->endVisitingObject($metadata, $data, $type); + + throw $e; } } diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index adf4304ef..0cd89c73a 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -9,6 +9,9 @@ use JMS\Serializer\Exception\NonVisitableTypeException; use JMS\Serializer\Exception\PropertyMissingException; use JMS\Serializer\Exception\RuntimeException; +use JMS\Serializer\Exception\NonStringCastableTypeException; +use JMS\Serializer\Exception\NonIntCastableTypeException; +use JMS\Serializer\Exception\NonFloatCastableTypeException; use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\SerializationContext; use JMS\Serializer\Visitor\DeserializationVisitorInterface; @@ -77,10 +80,6 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $alternativeName = null; foreach ($type['params'] as $possibleType) { - if ($this->isPrimitiveType($possibleType['name']) && $this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { - return $context->getNavigator()->accept($data, $possibleType); - } - $propertyMetadata = $context->getMetadataStack()->top(); if (null !== $propertyMetadata->unionDiscriminatorMap) { if (array_key_exists($propertyMetadata->unionDiscriminatorField, $data) && array_key_exists($data[$propertyMetadata->unionDiscriminatorField], $propertyMetadata->unionDiscriminatorMap)) { @@ -99,7 +98,7 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed ]; } - if (null !== $finalType) { + if (null !== $finalType && null !== $finalType['name']) { return $context->getNavigator()->accept($data, $finalType); } else { try { @@ -107,7 +106,6 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($this->requireAllProperties); } - $accept = $context->getNavigator()->accept($data, $possibleType); if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); @@ -118,6 +116,12 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed continue; } catch (PropertyMissingException $e) { continue; + } catch (NonStringCastableTypeException $e) { + continue; + } catch (NonIntCastableTypeException $e) { + continue; + } catch (NonFloatCastableTypeException $e) { + continue; } } } diff --git a/src/SerializerBuilder.php b/src/SerializerBuilder.php index f5d9c5404..8bc5bab7f 100644 --- a/src/SerializerBuilder.php +++ b/src/SerializerBuilder.php @@ -285,7 +285,7 @@ public function addDefaultHandlers(): self } if (PHP_VERSION_ID >= 80000) { - $this->handlerRegistry->registerSubscribingHandler(new UnionHandler()); + $this->handlerRegistry->registerSubscribingHandler(new UnionHandler(requireAllProperties: true)); } return $this; From a86562c86cc32276a86779aeafaa9a9c207499dd Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Tue, 16 Jul 2024 12:42:42 -0400 Subject: [PATCH 09/14] better handle non discriminated unions, and primitives --- src/Handler/UnionHandler.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 0cd89c73a..3a0957f9e 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -91,13 +91,12 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed } else { throw new NonVisitableTypeException("Union Discriminator Map does not contain field '$propertyMetadata->unionDiscriminatorField'"); } - } else { + } else if(null != $propertyMetadata->unionDiscriminatorField) { $finalType = [ 'name' => $data[$propertyMetadata->unionDiscriminatorField], 'params' => [], ]; } - if (null !== $finalType && null !== $finalType['name']) { return $context->getNavigator()->accept($data, $finalType); } else { @@ -106,6 +105,9 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($this->requireAllProperties); } + if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { + continue; + } $accept = $context->getNavigator()->accept($data, $possibleType); if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); From 0cd13a666cad83f18a933d3c49918ca8a4b965d7 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Tue, 16 Jul 2024 16:50:00 -0400 Subject: [PATCH 10/14] better handling of primitives --- src/Handler/UnionHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 3a0957f9e..09cef88b5 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -81,6 +81,7 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed foreach ($type['params'] as $possibleType) { $propertyMetadata = $context->getMetadataStack()->top(); + $finalType = null; if (null !== $propertyMetadata->unionDiscriminatorMap) { if (array_key_exists($propertyMetadata->unionDiscriminatorField, $data) && array_key_exists($data[$propertyMetadata->unionDiscriminatorField], $propertyMetadata->unionDiscriminatorMap)) { $lkup = $data[$propertyMetadata->unionDiscriminatorField]; @@ -105,14 +106,13 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($this->requireAllProperties); } - if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) { + if ($this->isPrimitiveType($possibleType['name']) && (is_array($data) || !$this->testPrimitive($data, $possibleType['name'], $context->getFormat()))) { continue; } $accept = $context->getNavigator()->accept($data, $possibleType); if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); } - return $accept; } catch (NonVisitableTypeException $e) { continue; From f96f7684a252e59c6e0aae37a11b2d143b3100c6 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Wed, 17 Jul 2024 15:11:36 -0400 Subject: [PATCH 11/14] style --- src/Annotation/UnionDiscriminator.php | 2 +- src/Handler/UnionHandler.php | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Annotation/UnionDiscriminator.php b/src/Annotation/UnionDiscriminator.php index 57f33fc38..ddaa9ba97 100644 --- a/src/Annotation/UnionDiscriminator.php +++ b/src/Annotation/UnionDiscriminator.php @@ -15,7 +15,7 @@ final class UnionDiscriminator implements SerializerAttribute /** @var array */ public $map = []; - + /** @var string */ public $field = 'type'; diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 09cef88b5..9dfa35424 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -6,12 +6,12 @@ use JMS\Serializer\Context; use JMS\Serializer\DeserializationContext; +use JMS\Serializer\Exception\NonFloatCastableTypeException; +use JMS\Serializer\Exception\NonIntCastableTypeException; +use JMS\Serializer\Exception\NonStringCastableTypeException; use JMS\Serializer\Exception\NonVisitableTypeException; use JMS\Serializer\Exception\PropertyMissingException; use JMS\Serializer\Exception\RuntimeException; -use JMS\Serializer\Exception\NonStringCastableTypeException; -use JMS\Serializer\Exception\NonIntCastableTypeException; -use JMS\Serializer\Exception\NonFloatCastableTypeException; use JMS\Serializer\GraphNavigatorInterface; use JMS\Serializer\SerializationContext; use JMS\Serializer\Visitor\DeserializationVisitorInterface; @@ -87,17 +87,18 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $lkup = $data[$propertyMetadata->unionDiscriminatorField]; $finalType = [ 'name' => $propertyMetadata->unionDiscriminatorMap[$lkup], - 'params' => [] + 'params' => [], ]; } else { throw new NonVisitableTypeException("Union Discriminator Map does not contain field '$propertyMetadata->unionDiscriminatorField'"); } - } else if(null != $propertyMetadata->unionDiscriminatorField) { + } elseif (null !== $propertyMetadata->unionDiscriminatorField) { $finalType = [ 'name' => $data[$propertyMetadata->unionDiscriminatorField], 'params' => [], ]; } + if (null !== $finalType && null !== $finalType['name']) { return $context->getNavigator()->accept($data, $finalType); } else { @@ -106,13 +107,16 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($this->requireAllProperties); } + if ($this->isPrimitiveType($possibleType['name']) && (is_array($data) || !$this->testPrimitive($data, $possibleType['name'], $context->getFormat()))) { continue; } + $accept = $context->getNavigator()->accept($data, $possibleType); if ($this->requireAllProperties) { $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting); } + return $accept; } catch (NonVisitableTypeException $e) { continue; From 4f4e238d4d6cd7b5ac063feecf7ee2ad2579d586 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Wed, 17 Jul 2024 15:12:41 -0400 Subject: [PATCH 12/14] stan --- src/Handler/UnionHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index 9dfa35424..f0c6cd78f 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -131,6 +131,7 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed } } } + return null; } private function matchSimpleType(mixed $data, array $type, Context $context): mixed From 75c2aff4c3d9862261771e405482a6586d8c2756 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Wed, 17 Jul 2024 14:09:04 -0600 Subject: [PATCH 13/14] fix tests --- .../DeserializationGraphNavigator.php | 2 +- src/Handler/UnionHandler.php | 29 ++++++++++++------- tests/Fixtures/DiscriminatedAuthor.php | 1 - .../ComplexDiscriminatedUnion.php | 1 - 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/GraphNavigator/DeserializationGraphNavigator.php b/src/GraphNavigator/DeserializationGraphNavigator.php index 271354478..b6e230b49 100644 --- a/src/GraphNavigator/DeserializationGraphNavigator.php +++ b/src/GraphNavigator/DeserializationGraphNavigator.php @@ -225,7 +225,7 @@ public function accept($data, ?array $type = null) $this->visitor->endVisitingObject($metadata, $data, $type); throw new PropertyMissingException("Property $propertyMetadata->name is missing from data "); - } else { + } elseif ($this->visitor->getRequireAllRequiredProperties()) { $this->visitor->endVisitingObject($metadata, $data, $type); throw $e; diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index f0c6cd78f..01d0e2b18 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -82,21 +82,27 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed foreach ($type['params'] as $possibleType) { $propertyMetadata = $context->getMetadataStack()->top(); $finalType = null; - if (null !== $propertyMetadata->unionDiscriminatorMap) { - if (array_key_exists($propertyMetadata->unionDiscriminatorField, $data) && array_key_exists($data[$propertyMetadata->unionDiscriminatorField], $propertyMetadata->unionDiscriminatorMap)) { - $lkup = $data[$propertyMetadata->unionDiscriminatorField]; + if (null !== $propertyMetadata->unionDiscriminatorField) { + if (!array_key_exists($propertyMetadata->unionDiscriminatorField, $data)) { + throw new NonVisitableTypeException("Union Discriminator Field '$propertyMetadata->unionDiscriminatorField' not found in data1"); + } + + $lkup = $data[$propertyMetadata->unionDiscriminatorField]; + if (!empty($propertyMetadata->unionDiscriminatorMap)) { + if (array_key_exists($lkup, $propertyMetadata->unionDiscriminatorMap)) { + $finalType = [ + 'name' => $propertyMetadata->unionDiscriminatorMap[$lkup], + 'params' => [], + ]; + } else { + throw new NonVisitableTypeException("Union Discriminator Map does not contain key '$lkup'"); + } + } else { $finalType = [ - 'name' => $propertyMetadata->unionDiscriminatorMap[$lkup], + 'name' => $lkup, 'params' => [], ]; - } else { - throw new NonVisitableTypeException("Union Discriminator Map does not contain field '$propertyMetadata->unionDiscriminatorField'"); } - } elseif (null !== $propertyMetadata->unionDiscriminatorField) { - $finalType = [ - 'name' => $data[$propertyMetadata->unionDiscriminatorField], - 'params' => [], - ]; } if (null !== $finalType && null !== $finalType['name']) { @@ -131,6 +137,7 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed } } } + return null; } diff --git a/tests/Fixtures/DiscriminatedAuthor.php b/tests/Fixtures/DiscriminatedAuthor.php index ea0ddb1ae..d3d13d4b3 100644 --- a/tests/Fixtures/DiscriminatedAuthor.php +++ b/tests/Fixtures/DiscriminatedAuthor.php @@ -6,7 +6,6 @@ use JMS\Serializer\Annotation\SerializedName; use JMS\Serializer\Annotation\Type; -use JMS\Serializer\Annotation\Discriminator; class DiscriminatedAuthor { diff --git a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php index f0a158120..b60227b49 100644 --- a/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php +++ b/tests/Fixtures/TypedProperties/ComplexDiscriminatedUnion.php @@ -10,7 +10,6 @@ class ComplexDiscriminatedUnion { - #[Type('JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor|JMS\Serializer\Tests\Fixtures\DiscriminatedComment')] #[UnionDiscriminator(field: 'type')] private DiscriminatedAuthor|DiscriminatedComment $data; From f92f276a3891113f85bdd7d49ac8d9d61c84d531 Mon Sep 17 00:00:00 2001 From: Ian Bentley Date: Wed, 17 Jul 2024 14:23:17 -0600 Subject: [PATCH 14/14] add tests for mapped union discriminators --- tests/Fixtures/MappedDiscriminatedAuthor.php | 40 +++++++++++++++++ tests/Fixtures/MappedDiscriminatedComment.php | 45 +++++++++++++++++++ .../MappedComplexDiscriminatedUnion.php | 26 +++++++++++ tests/Serializer/JsonSerializationTest.php | 21 +++++++++ 4 files changed, 132 insertions(+) create mode 100644 tests/Fixtures/MappedDiscriminatedAuthor.php create mode 100644 tests/Fixtures/MappedDiscriminatedComment.php create mode 100644 tests/Fixtures/TypedProperties/MappedComplexDiscriminatedUnion.php diff --git a/tests/Fixtures/MappedDiscriminatedAuthor.php b/tests/Fixtures/MappedDiscriminatedAuthor.php new file mode 100644 index 000000000..76e06b1fa --- /dev/null +++ b/tests/Fixtures/MappedDiscriminatedAuthor.php @@ -0,0 +1,40 @@ +name = $name; + } + + public function getName() + { + return $this->name; + } + + public function getObjectType() + { + return $this->objectType; + } +} diff --git a/tests/Fixtures/MappedDiscriminatedComment.php b/tests/Fixtures/MappedDiscriminatedComment.php new file mode 100644 index 000000000..984017971 --- /dev/null +++ b/tests/Fixtures/MappedDiscriminatedComment.php @@ -0,0 +1,45 @@ +author = $author; + $this->text = $text; + } + + public function getAuthor() + { + return $this->author; + } + + public function getObjectType() + { + return $this->objectType; + } +} diff --git a/tests/Fixtures/TypedProperties/MappedComplexDiscriminatedUnion.php b/tests/Fixtures/TypedProperties/MappedComplexDiscriminatedUnion.php new file mode 100644 index 000000000..e7e17f52a --- /dev/null +++ b/tests/Fixtures/TypedProperties/MappedComplexDiscriminatedUnion.php @@ -0,0 +1,26 @@ + 'JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor', 'comment' => 'JMS\Serializer\Tests\Fixtures\DiscriminatedComment'])] + private DiscriminatedAuthor|DiscriminatedComment $data; + + public function __construct($data) + { + $this->data = $data; + } + + public function getData(): Author|Comment + { + return $this->data; + } +} + diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index c1e64aafb..581dcd69b 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -23,9 +23,12 @@ use JMS\Serializer\Tests\Fixtures\Tag; use JMS\Serializer\Tests\Fixtures\DiscriminatedAuthor; use JMS\Serializer\Tests\Fixtures\DiscriminatedComment; +use JMS\Serializer\Tests\Fixtures\MappedDiscriminatedAuthor; +use JMS\Serializer\Tests\Fixtures\MappedDiscriminatedComment; use JMS\Serializer\Tests\Fixtures\TypedProperties\UnionTypedProperties; use JMS\Serializer\Tests\Fixtures\TypedProperties\ComplexUnionTypedProperties; use JMS\Serializer\Tests\Fixtures\TypedProperties\ComplexDiscriminatedUnion; +use JMS\Serializer\Tests\Fixtures\TypedProperties\MappedComplexDiscriminatedUnion; use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory; use JMS\Serializer\Visitor\SerializationVisitorInterface; use PHPUnit\Framework\Attributes\DataProvider; @@ -158,6 +161,8 @@ protected static function getContent($key) $outputs['data_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar"}}'; $outputs['data_discriminated_author'] = '{"data":{"full_name":"foo","type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedAuthor"}}'; $outputs['data_discriminated_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar","type":"JMS\\\Serializer\\\Tests\\\Fixtures\\\DiscriminatedComment"}}'; + $outputs['data_mapped_discriminated_author'] = '{"data":{"full_name":"foo","type":"author"}}'; + $outputs['data_mapped_discriminated_comment'] = '{"data":{"author":{"full_name":"foo"},"text":"bar","type":"comment"}}'; $outputs['uid'] = '"66b3177c-e03b-4a22-9dee-ddd7d37a04d5"'; $outputs['object_with_enums'] = '{"ordinary":"Clubs","backed_value":"C","backed_without_param":"C","ordinary_array":["Clubs","Spades"],"backed_array":["C","H"],"backed_array_without_param":["C","H"],"ordinary_auto_detect":"Clubs","backed_auto_detect":"C","backed_int_auto_detect":3,"backed_int":3,"backed_name":"C","backed_int_forced_str":3}'; $outputs['object_with_autodetect_enums'] = '{"ordinary_array_auto_detect":["Clubs","Spades"],"backed_array_auto_detect":["C","H"],"mixed_array_auto_detect":["Clubs","H"]}'; @@ -529,6 +534,22 @@ public function testDeserializingComplexDiscriminatedUnionProperties() self::assertEquals($commentUnion, $this->deserialize(static::getContent('data_discriminated_comment'), ComplexDiscriminatedUnion::class)); } + public function testDeserializingMappedComplexDiscriminatedUnionProperties() + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); + + return; + } + + $authorUnion = new MappedComplexDiscriminatedUnion(new MappedDiscriminatedAuthor('foo')); + self::assertEquals($authorUnion, $this->deserialize(static::getContent('data_mapped_discriminated_author'), MappedComplexDiscriminatedUnion::class)); + + $commentUnion = new MappedComplexDiscriminatedUnion(new MappedDiscriminatedComment(new Author('foo'), 'bar')); + + self::assertEquals($commentUnion, $this->deserialize(static::getContent('data_mapped_discriminated_comment'), MappedComplexDiscriminatedUnion::class)); + } + public function testSerializeingComplexDiscriminatedUnionProperties() { if (PHP_VERSION_ID < 80000) {