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 d3e3dfe

Browse filesBrowse files
committed
Add NumberNormalizer
1 parent 7e9ecaf commit d3e3dfe
Copy full SHA for d3e3dfe

6 files changed

+242Lines changed: 242 additions & 0 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎psalm.xml‎

Copy file name to clipboardExpand all lines: psalm.xml
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
<referencedClass name="UnitEnum"/>
3333
<!-- These classes have been added in PHP 8.2 -->
3434
<referencedClass name="Random\*"/>
35+
<!-- These classes have been added in PHP 8.4 -->
36+
<referencedClass name="BcMath\Number"/>
3537
</errorLevel>
3638
</UndefinedClass>
3739
<UndefinedDocblockClass>
Collapse file

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
171171
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
172172
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
173+
use Symfony\Component\Serializer\Normalizer\NumberNormalizer;
173174
use Symfony\Component\Serializer\Serializer;
174175
use Symfony\Component\Stopwatch\Stopwatch;
175176
use Symfony\Component\String\LazyString;
@@ -1933,6 +1934,11 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
19331934
$container->removeDefinition('serializer.normalizer.mime_message');
19341935
}
19351936

1937+
// BC layer Serializer < 7.3
1938+
if (!class_exists(NumberNormalizer::class)) {
1939+
$container->removeDefinition('serializer.normalizer.number');
1940+
}
1941+
19361942
// BC layer Serializer < 7.2
19371943
if (!class_exists(SnakeCaseToCamelCaseNameConverter::class)) {
19381944
$container->removeDefinition('serializer.name_converter.snake_case_to_camel_case');
Collapse file

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php‎

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
4545
use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer;
4646
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
47+
use Symfony\Component\Serializer\Normalizer\NumberNormalizer;
4748
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
4849
use Symfony\Component\Serializer\Normalizer\ProblemNormalizer;
4950
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
@@ -221,5 +222,8 @@
221222

222223
->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class)
223224
->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915])
225+
226+
->set('serializer.normalizer.number', NumberNormalizer::class)
227+
->tag('serializer.normalizer', ['built_in' => true, 'priority' => -915])
224228
;
225229
};
Collapse file

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate the `CompiledClassMetadataFactory` and `CompiledClassMetadataCacheWarmer` classes
88
* Register `NormalizerInterface` and `DenormalizerInterface` aliases for named serializers
9+
* Add `NumberNormalizer` to normalize `BcMath\Number` and `GMP` as `string`
910

1011
7.2
1112
---
Collapse file
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 BcMath\Number;
15+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
16+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
17+
18+
/**
19+
* Normalizes {@see Number} and {@see \GMP} to a string.
20+
*/
21+
final class NumberNormalizer implements NormalizerInterface, DenormalizerInterface
22+
{
23+
public function getSupportedTypes(?string $format): array
24+
{
25+
return [
26+
Number::class => true,
27+
\GMP::class => true,
28+
];
29+
}
30+
31+
public function normalize(mixed $data, ?string $format = null, array $context = []): string
32+
{
33+
if (!$data instanceof Number && !$data instanceof \GMP) {
34+
throw new InvalidArgumentException(\sprintf('The data must be an instance of "%s" or "%s".', Number::class, \GMP::class));
35+
}
36+
37+
return (string) $data;
38+
}
39+
40+
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
41+
{
42+
return $data instanceof Number || $data instanceof \GMP;
43+
}
44+
45+
/**
46+
* @throws NotNormalizableValueException
47+
*/
48+
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): Number|\GMP
49+
{
50+
if (!\is_string($data) && !\is_int($data)) {
51+
throw $this->createNotNormalizableValueException($type, $data, $context);
52+
}
53+
54+
try {
55+
return match ($type) {
56+
Number::class => new Number($data),
57+
\GMP::class => new \GMP($data),
58+
default => throw new InvalidArgumentException(\sprintf('Only "%s" and "%s" types are supported.', Number::class, \GMP::class)),
59+
};
60+
} catch (\ValueError $e) {
61+
throw $this->createNotNormalizableValueException($type, $data, $context, $e);
62+
}
63+
}
64+
65+
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
66+
{
67+
return \in_array($type, [Number::class, \GMP::class], true) && null !== $data;
68+
}
69+
70+
private function createNotNormalizableValueException(string $type, mixed $data, array $context, ?\Throwable $previous = null): NotNormalizableValueException
71+
{
72+
$message = match ($type) {
73+
Number::class => 'The data must be a "string" representing a decimal number, or an "int".',
74+
\GMP::class => 'The data must be a "string" representing an integer, or an "int".',
75+
};
76+
77+
return NotNormalizableValueException::createForUnexpectedDataType($message, $data, ['string', 'int'], $context['deserialization_path'] ?? null, true, 0, $previous);
78+
}
79+
}
Collapse file
+150Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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\Tests\Normalizer;
13+
14+
use BcMath\Number;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
17+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
18+
use Symfony\Component\Serializer\Normalizer\NumberNormalizer;
19+
20+
/**
21+
* @requires PHP 8.4
22+
* @requires extension bcmath
23+
* @requires extension gmp
24+
*/
25+
class NumberNormalizerTest extends TestCase
26+
{
27+
private NumberNormalizer $normalizer;
28+
29+
protected function setUp(): void
30+
{
31+
$this->normalizer = new NumberNormalizer();
32+
}
33+
34+
/**
35+
* @dataProvider supportsNormalizationProvider
36+
*/
37+
public function testSupportsNormalization(mixed $data, bool $expected)
38+
{
39+
$this->assertSame($expected, $this->normalizer->supportsNormalization($data));
40+
}
41+
42+
public static function supportsNormalizationProvider(): iterable
43+
{
44+
yield 'GMP object' => [new \GMP('0b111'), true];
45+
yield 'Number object' => [new Number('1.23'), true];
46+
yield 'object with similar properties as Number' => [(object) ['value' => '1.23', 'scale' => 2], false];
47+
yield 'stdClass' => [new \stdClass(), false];
48+
yield 'string' => ['1.23', false];
49+
yield 'float' => [1.23, false];
50+
yield 'null' => [null, false];
51+
}
52+
53+
/**
54+
* @dataProvider normalizeGoodValueProvider
55+
*/
56+
public function testNormalize(mixed $data, mixed $expected)
57+
{
58+
$this->assertSame($expected, $this->normalizer->normalize($data));
59+
}
60+
61+
public static function normalizeGoodValueProvider(): iterable
62+
{
63+
yield 'Number with scale=2' => [new Number('1.23'), '1.23'];
64+
yield 'Number with scale=0' => [new Number('1'), '1'];
65+
yield 'Number with integer' => [new Number(123), '123'];
66+
yield 'GMP hex' => [new \GMP('0x10'), '16'];
67+
yield 'GMP base=10' => [new \GMP('10'), '10'];
68+
}
69+
70+
/**
71+
* @dataProvider normalizeBadValueProvider
72+
*/
73+
public function testNormalizeBadValueThrows(mixed $data)
74+
{
75+
$this->expectException(InvalidArgumentException::class);
76+
$this->expectExceptionMessage('The data must be an instance of "BcMath\Number" or "GMP".');
77+
78+
$this->normalizer->normalize($data);
79+
}
80+
81+
public static function normalizeBadValueProvider(): iterable
82+
{
83+
yield 'stdClass' => [new \stdClass()];
84+
yield 'string' => ['1.23'];
85+
yield 'null' => [null];
86+
}
87+
88+
/**
89+
* @dataProvider supportsDenormalizationProvider
90+
*/
91+
public function testSupportsDenormalization(mixed $data, string $type, bool $expected)
92+
{
93+
$this->assertSame($expected, $this->normalizer->supportsDenormalization($data, $type));
94+
}
95+
96+
public static function supportsDenormalizationProvider(): iterable
97+
{
98+
yield 'null value, Number' => [null, Number::class, false];
99+
yield 'null value, GMP' => [null, \GMP::class, false];
100+
yield 'null value, unmatching type' => [null, \stdClass::class, false];
101+
}
102+
103+
/**
104+
* @dataProvider denormalizeGoodValueProvider
105+
*/
106+
public function testDenormalize(mixed $data, string $type, mixed $expected)
107+
{
108+
$this->assertEquals($expected, $this->normalizer->denormalize($data, $type));
109+
}
110+
111+
public static function denormalizeGoodValueProvider(): iterable
112+
{
113+
yield 'Number, string with decimal point' => ['1.23', Number::class, new Number('1.23')];
114+
yield 'Number, integer as string' => ['123', Number::class, new Number('123')];
115+
yield 'Number, integer' => [123, Number::class, new Number('123')];
116+
yield 'GMP, large number' => ['9223372036854775808', \GMP::class, new \GMP('9223372036854775808')];
117+
yield 'GMP, integer' => [123, \GMP::class, new \GMP('123')];
118+
}
119+
120+
/**
121+
* @dataProvider denormalizeBadValueProvider
122+
*/
123+
public function testDenormalizeBadValueThrows(mixed $data, string $type, string $expectedException, string $expectedExceptionMessage)
124+
{
125+
$this->expectException($expectedException);
126+
$this->expectExceptionMessage($expectedExceptionMessage);
127+
128+
$this->normalizer->denormalize($data, $type);
129+
}
130+
131+
public static function denormalizeBadValueProvider(): iterable
132+
{
133+
$stringOrDecimalExpectedMessage = 'The data must be a "string" representing a decimal number, or an "int".';
134+
yield 'Number, null' => [null, Number::class, NotNormalizableValueException::class, $stringOrDecimalExpectedMessage];
135+
yield 'Number, boolean' => [true, Number::class, NotNormalizableValueException::class, $stringOrDecimalExpectedMessage];
136+
yield 'Number, object' => [new \stdClass(), Number::class, NotNormalizableValueException::class, $stringOrDecimalExpectedMessage];
137+
yield 'Number, non-numeric string' => ['foobar', Number::class, NotNormalizableValueException::class, $stringOrDecimalExpectedMessage];
138+
yield 'Number, float' => [1.23, Number::class, NotNormalizableValueException::class, $stringOrDecimalExpectedMessage];
139+
140+
$stringOrIntExpectedMessage = 'The data must be a "string" representing an integer, or an "int".';
141+
yield 'GMP, null' => [null, \GMP::class, NotNormalizableValueException::class, $stringOrIntExpectedMessage];
142+
yield 'GMP, boolean' => [true, \GMP::class, NotNormalizableValueException::class, $stringOrIntExpectedMessage];
143+
yield 'GMP, object' => [new \stdClass(), \GMP::class, NotNormalizableValueException::class, $stringOrIntExpectedMessage];
144+
yield 'GMP, non-numeric string' => ['foobar', \GMP::class, NotNormalizableValueException::class, $stringOrIntExpectedMessage];
145+
yield 'GMP, scale > 0' => ['1.23', \GMP::class, NotNormalizableValueException::class, $stringOrIntExpectedMessage];
146+
yield 'GMP, float' => [1.23, \GMP::class, NotNormalizableValueException::class, $stringOrIntExpectedMessage];
147+
148+
yield 'unsupported type' => ['1.23', \stdClass::class, InvalidArgumentException::class, 'Only "BcMath\Number" and "GMP" types are supported.'];
149+
}
150+
}

0 commit comments

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