Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4702e99

Browse filesBrowse files
committed
add ScalarDenormalizer
1 parent ff97b5f commit 4702e99
Copy full SHA for 4702e99

File tree

8 files changed

+332
-17
lines changed
Filter options

8 files changed

+332
-17
lines changed

‎UPGRADE-5.3.md

Copy file name to clipboard
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
UPGRADE FROM 5.2 to 5.3
2+
=======================
3+
4+
Serializer
5+
----------
6+
7+
* Deprecated denormalizing scalar values without registering the `ScalarDenormalizer`

‎UPGRADE-6.0.md

Copy file name to clipboardExpand all lines: UPGRADE-6.0.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ Validator
219219
->addDefaultDoctrineAnnotationReader();
220220
```
221221

222+
Serializer
223+
----------
224+
225+
* Removed the denormalization of scalar values without normalizer, add the `ScalarDenormalizer` to the `Serializer`
226+
222227
Yaml
223228
----
224229

‎src/Symfony/Component/Serializer/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3.0
5+
-----
6+
7+
* [DEPRECATION] denormalizing scalar values without registering the `ScalarDenormalizer`
8+
49
5.2.0
510
-----
611

+111Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Normalizer;
13+
14+
use Symfony\Component\Serializer\Encoder\CsvEncoder;
15+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
16+
use Symfony\Component\Serializer\Encoder\XmlEncoder;
17+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
18+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
19+
20+
final class ScalarDenormalizer implements DenormalizerInterface, CacheableSupportsMethodInterface
21+
{
22+
private const TYPE_INT = 'int';
23+
private const TYPE_FLOAT = 'float';
24+
private const TYPE_STRING = 'string';
25+
private const TYPE_BOOL = 'bool';
26+
27+
public const SCALAR_TYPES = [
28+
self::TYPE_INT => true,
29+
self::TYPE_BOOL => true,
30+
self::TYPE_FLOAT => true,
31+
self::TYPE_STRING => true,
32+
];
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function denormalize($data, string $type, string $format = null, array $context = [])
38+
{
39+
if (!isset(self::SCALAR_TYPES[get_debug_type($data)])) {
40+
throw new InvalidArgumentException(sprintf('Data expected to be of one of the types in "%s" ("%s" given).', implode(', ', array_keys(self::SCALAR_TYPES)), get_debug_type($data)));
41+
}
42+
43+
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
44+
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
45+
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
46+
if (\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)) {
47+
switch ($type) {
48+
case self::TYPE_BOOL:
49+
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
50+
if ('false' === $data || '0' === $data) {
51+
return false;
52+
}
53+
if ('true' === $data || '1' === $data) {
54+
return true;
55+
}
56+
57+
throw new NotNormalizableValueException(sprintf('Data expected to be of type "%s" ("%s" given).', $type, $data));
58+
case self::TYPE_INT:
59+
if (ctype_digit($data) || '-' === $data[0] && ctype_digit(substr($data, 1))) {
60+
return (int) $data;
61+
}
62+
63+
throw new NotNormalizableValueException(sprintf('Data expected to be of type "%s" ("%s" given).', $type, $data));
64+
case self::TYPE_FLOAT:
65+
if (is_numeric($data)) {
66+
return (float) $data;
67+
}
68+
69+
switch ($data) {
70+
case 'NaN':
71+
return \NAN;
72+
case 'INF':
73+
return \INF;
74+
case '-INF':
75+
return -\INF;
76+
default:
77+
throw new NotNormalizableValueException(sprintf('Data expected to be of type "%s" ("%s" given).', $type, $data));
78+
}
79+
}
80+
}
81+
82+
// JSON only has a Number type corresponding to both int and float PHP types.
83+
// PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert
84+
// floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible).
85+
// PHP's json_decode automatically converts Numbers without a decimal part to integers.
86+
// To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when
87+
// a float is expected.
88+
if (self::TYPE_FLOAT === $type && \is_int($data) && false !== strpos($format, JsonEncoder::FORMAT)) {
89+
return (float) $data;
90+
}
91+
92+
if (!('is_'.$type)($data)) {
93+
throw new NotNormalizableValueException(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)));
94+
}
95+
96+
return $data;
97+
}
98+
99+
/**
100+
* {@inheritdoc}
101+
*/
102+
public function supportsDenormalization($data, string $type, string $format = null)
103+
{
104+
return isset(self::SCALAR_TYPES[$type]);
105+
}
106+
107+
public function hasCacheableSupportsMethod(): bool
108+
{
109+
return true;
110+
}
111+
}

‎src/Symfony/Component/Serializer/Serializer.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Serializer.php
+5-9Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
3030
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
3131
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
32+
use Symfony\Component\Serializer\Normalizer\ScalarDenormalizer;
3233

3334
/**
3435
* Serializer serializes and deserializes data.
@@ -47,13 +48,6 @@
4748
*/
4849
class Serializer implements SerializerInterface, ContextAwareNormalizerInterface, ContextAwareDenormalizerInterface, ContextAwareEncoderInterface, ContextAwareDecoderInterface
4950
{
50-
private const SCALAR_TYPES = [
51-
'int' => true,
52-
'bool' => true,
53-
'float' => true,
54-
'string' => true,
55-
];
56-
5751
/**
5852
* @var Encoder\ChainEncoder
5953
*/
@@ -192,7 +186,9 @@ public function denormalize($data, string $type, string $format = null, array $c
192186
$normalizer = $this->getDenormalizer($data, $type, $format, $context);
193187

194188
// Check for a denormalizer first, e.g. the data is wrapped
195-
if (!$normalizer && isset(self::SCALAR_TYPES[$type])) {
189+
if (!$normalizer && isset(ScalarDenormalizer::SCALAR_TYPES[$type])) {
190+
trigger_deprecation('symfony/serializer', '5.2', 'Denormalizing scalar values without registering the "%s" is deprecated.', ScalarDenormalizer::class);
191+
196192
if (!('is_'.$type)($data)) {
197193
throw new NotNormalizableValueException(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)));
198194
}
@@ -224,7 +220,7 @@ public function supportsNormalization($data, string $format = null, array $conte
224220
*/
225221
public function supportsDenormalization($data, string $type, string $format = null, array $context = [])
226222
{
227-
return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context);
223+
return isset(ScalarDenormalizer::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data, $type, $format, $context);
228224
}
229225

230226
/**
+123Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Normalizer;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
7+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
8+
use Symfony\Component\Serializer\Normalizer\ScalarDenormalizer;
9+
10+
class ScalarDenormalizerTest extends TestCase
11+
{
12+
/**
13+
* @var ScalarDenormalizer
14+
*/
15+
private $denormalizer;
16+
17+
protected function setUp(): void
18+
{
19+
$this->denormalizer = new ScalarDenormalizer();
20+
}
21+
22+
/**
23+
* @dataProvider provideSupportedTypes
24+
*/
25+
public function testSupportsDenormalization(string $supportedType): void
26+
{
27+
$this->assertTrue($this->denormalizer->supportsDenormalization(null, $supportedType));
28+
}
29+
30+
public function provideSupportedTypes(): iterable
31+
{
32+
return [['int'], ['float'], ['string'], ['bool']];
33+
}
34+
35+
/**
36+
* @dataProvider provideUnsupportedTypes
37+
*/
38+
public function testUnsupportsDenormalization(string $unsupportedType): void
39+
{
40+
$this->assertFalse($this->denormalizer->supportsDenormalization(null, $unsupportedType));
41+
}
42+
43+
public function provideUnsupportedTypes(): iterable
44+
{
45+
return [['null'], ['array'], ['iterable'], ['object'], ['resource'], ['callable'], ['int[]']];
46+
}
47+
48+
/**
49+
* @dataProvider provideInvalidData
50+
*/
51+
public function testDenormalizeInvalidDataThrowsException($invalidData): void
52+
{
53+
$this->expectException(InvalidArgumentException::class);
54+
$this->denormalizer->denormalize($invalidData, 'int');
55+
}
56+
57+
public function provideInvalidData(): iterable
58+
{
59+
return [
60+
'callable' => [[$this, 'provideInvalidData']],
61+
'resource' => [fopen(__FILE__, 'r')],
62+
'array' => [[1, 2]],
63+
'object' => [new \stdClass()],
64+
'null' => [null],
65+
];
66+
}
67+
68+
/**
69+
* @dataProvider provideNotNormalizableData
70+
*/
71+
public function testDenormalizeNotNormalizableDataThrowsException($data, string $type, string $format): void
72+
{
73+
$this->expectException(NotNormalizableValueException::class);
74+
$this->denormalizer->denormalize($data, $type, $format);
75+
}
76+
77+
public function provideNotNormalizableData(): iterable
78+
{
79+
return [
80+
'not a string' => [true, 'string', 'json'],
81+
'not an integer' => [3.1, 'int', 'json'],
82+
'not an integer (xml/csv)' => ['+12', 'int', 'xml'],
83+
'not a float' => [false, 'float', 'json'],
84+
'not a float (xml/csv)' => ['nan', 'float', 'xml'],
85+
'not a boolean (json)' => [0, 'bool', 'json'],
86+
'not a boolean (xml/csv)' => ['test', 'bool', 'xml'],
87+
];
88+
}
89+
90+
/**
91+
* @dataProvider provideNormalizableData
92+
*/
93+
public function testDenormalize($expectedResult, $data, string $type, string $format): void
94+
{
95+
$result = $this->denormalizer->denormalize($data, $type, $format);
96+
97+
if (is_nan($expectedResult)) {
98+
$this->assertNan($result);
99+
} else {
100+
$this->assertSame($expectedResult, $result);
101+
}
102+
}
103+
104+
public function provideNormalizableData(): iterable
105+
{
106+
return [
107+
'string' => ['1', '1', 'string', 'json'],
108+
'integer' => [-3, -3, 'int', 'json'],
109+
'integer (xml/csv)' => [-12, '-12', 'int', 'xml'],
110+
'float' => [3.14, 3.14, 'float', 'json'],
111+
'float without decimals' => [3.0, 3, 'float', 'json'],
112+
'NaN (xml/csv)' => [\NAN, 'NaN', 'float', 'xml'],
113+
'INF (xml/csv)' => [\INF, 'INF', 'float', 'xml'],
114+
'-INF (xml/csv)' => [-\INF, '-INF', 'float', 'xml'],
115+
'boolean: true (json)' => [true, true, 'bool', 'json'],
116+
'boolean: false (json)' => [false, false, 'bool', 'json'],
117+
"boolean: 'true' (xml/csv)" => [true, 'true', 'bool', 'xml'],
118+
"boolean: '1' (xml/csv)" => [true, '1', 'bool', 'xml'],
119+
"boolean: 'false' (xml/csv)" => [false, 'false', 'bool', 'xml'],
120+
"boolean: '0' (xml/csv)" => [false, '0', 'bool', 'xml'],
121+
];
122+
}
123+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.