diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index ea4b4b6635e53..aa5816e07591f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -15,7 +15,9 @@ use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -379,6 +381,61 @@ private function validateAndDenormalize(string $currentClass, string $attribute, $data = [$data]; } + // In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine, + // if a value is meant to be a string, float, int or a boolean value from the serialized representation. + // That's why we have to transform the values, if one of these non-string basic datatypes is expected. + // + // This is special to xml and csv format + if ( + \is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format) + ) { + if ( + '' === $data && $type->isNullable() && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true) + ) { + return null; + } + + switch ($type->getBuiltinType()) { + case Type::BUILTIN_TYPE_BOOL: + // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1" + if ('false' === $data || '0' === $data) { + $data = false; + } elseif ('true' === $data || '1' === $data) { + $data = true; + } else { + throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data)); + } + break; + case Type::BUILTIN_TYPE_INT: + if ( + ctype_digit($data) || + '-' === $data[0] && ctype_digit(substr($data, 1)) + ) { + $data = (int) $data; + } else { + throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data)); + } + break; + case Type::BUILTIN_TYPE_FLOAT: + if (is_numeric($data)) { + return (float) $data; + } + + switch ($data) { + case 'NaN': + return NAN; + case 'INF': + return INF; + case '-INF': + return -INF; + default: + throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data)); + } + + break; + } + } + if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) { $builtinType = Type::BUILTIN_TYPE_OBJECT; $class = $collectionValueType->getClassName().'[]'; diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index 155ee139248c4..b08e74660cd7e 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -273,6 +273,79 @@ public function getTypeForMappedObject($object): ?string $this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData); } + public function testDenormalizeBasicTypePropertiesFromXml() + { + $denormalizer = $this->getDenormalizerForObjectWithBasicProperties(); + + // bool + $objectWithBooleanProperties = $denormalizer->denormalize( + [ + 'boolTrue1' => 'true', + 'boolFalse1' => 'false', + 'boolTrue2' => '1', + 'boolFalse2' => '0', + 'int1' => '4711', + 'int2' => '-4711', + 'float1' => '123.456', + 'float2' => '-1.2344e56', + 'float3' => '45E-6', + 'floatNaN' => 'NaN', + 'floatInf' => 'INF', + 'floatNegInf' => '-INF', + ], + ObjectWithBasicProperties::class, + 'xml' + ); + + $this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBooleanProperties); + + // Bool Properties + $this->assertTrue($objectWithBooleanProperties->boolTrue1); + $this->assertFalse($objectWithBooleanProperties->boolFalse1); + $this->assertTrue($objectWithBooleanProperties->boolTrue2); + $this->assertFalse($objectWithBooleanProperties->boolFalse2); + + // Integer Properties + $this->assertEquals(4711, $objectWithBooleanProperties->int1); + $this->assertEquals(-4711, $objectWithBooleanProperties->int2); + + // Float Properties + $this->assertEqualsWithDelta(123.456, $objectWithBooleanProperties->float1, 0.01); + $this->assertEqualsWithDelta(-1.2344e56, $objectWithBooleanProperties->float2, 1); + $this->assertEqualsWithDelta(45E-6, $objectWithBooleanProperties->float3, 1); + $this->assertNan($objectWithBooleanProperties->floatNaN); + $this->assertInfinite($objectWithBooleanProperties->floatInf); + $this->assertEquals(-INF, $objectWithBooleanProperties->floatNegInf); + } + + private function getDenormalizerForObjectWithBasicProperties() + { + $extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock(); + $extractor->method('getTypes') + ->will($this->onConsecutiveCalls( + [new Type('bool')], + [new Type('bool')], + [new Type('bool')], + [new Type('bool')], + [new Type('int')], + [new Type('int')], + [new Type('float')], + [new Type('float')], + [new Type('float')], + [new Type('float')], + [new Type('float')], + [new Type('float')] + )); + + $denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor); + $arrayDenormalizer = new ArrayDenormalizerDummy(); + $serializer = new SerializerCollectionDummy([$arrayDenormalizer, $denormalizer]); + $arrayDenormalizer->setSerializer($serializer); + $denormalizer->setSerializer($serializer); + + return $denormalizer; + } + /** * Test that additional attributes throw an exception if no metadata factory is specified. */ @@ -359,6 +432,45 @@ protected function setAttributeValue(object $object, string $attribute, $value, } } +class ObjectWithBasicProperties +{ + /** @var bool */ + public $boolTrue1; + + /** @var bool */ + public $boolFalse1; + + /** @var bool */ + public $boolTrue2; + + /** @var bool */ + public $boolFalse2; + + /** @var int */ + public $int1; + + /** @var int */ + public $int2; + + /** @var float */ + public $float1; + + /** @var float */ + public $float2; + + /** @var float */ + public $float3; + + /** @var float */ + public $floatNaN; + + /** @var float */ + public $floatInf; + + /** @var float */ + public $floatNegInf; +} + class StringCollection { /** @var string[] */ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy