Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4c78e60

Browse files
committed
feature #30893 Add "input" option to NumberType (fancyweb, Bernhard Schussek)
This PR was merged into the 4.3-dev branch. Discussion ---------- Add "input" option to NumberType | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #24793 | License | MIT | Doc PR | TODO This PR replaces #24793 in (partially) fixing how Doctrine's DECIMAL type is handled by the Form component. Previously, DECIMAL was mapped to the regular NumberType. That confuses Doctrine's change detection, depending on the DB platform. Examples: | DB | DB value | Doctrine entity before submit | Form input | Doctrine entity after submit | --- | --- | --- | --- | --- | SQLite | 8.000 | '8' | 8 | 8 | SQLite | 8.123 | '8.123' | 8.123 | 8.123 | PostgreSQL | 8.000 | '8.000' | 8 | 8 | PostgreSQL | 8.123 | '8.123' | 8.123 | 8.123 The value in the Doctrine entity changes before and after submit. Hence Doctrine believes an update is necessary. This PR introduces an `input` option to NumberType (similar to DateType), that can be set to `'number'` (default) or `'string'`. If set to `'string'`, the conversion is as follows: | DB | DB value | Doctrine entity before submit | Form input | Doctrine entity after submit | --- | --- | --- | --- | --- | SQLite | 8.000 | **'8'** | 8 | **'8.000'** | SQLite | 8.123 | '8.123' | 8.123 | '8.123' | PostgreSQL | 8.000 | '8.000' | 8 | '8.000' | PostgreSQL | 8.123 | '8.123' | 8.123 | '8.123' You see that this does not completely solve this issue for SQLite. However, @Ocramius and I agree that this is something to be fixed by Doctrine, since Doctrine is providing the database abstraction. That fix should be done in the SqlitePlatform object in Doctrine as part of another PR and should be backwards compatible with the current Doctrine version (i.e. opt in via configuration). Compared to #24793, this PR does not introduce a new type but instead makes the NumberType more flexible. Also, this PR does not introduce the `force_full_scale` option since that should be solved by Doctrine as described above. Commits ------- 3f25734 Added new option "input" to NumberType fb2b37a add force_full_scale option to handle all cases 40f2512 [DoctrineBridge] Add decimal form type
2 parents f3a0555 + 3f25734 commit 4c78e60

File tree

7 files changed

+243
-5
lines changed

7 files changed

+243
-5
lines changed

src/Symfony/Bridge/Doctrine/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.3.0
5+
-----
6+
7+
* changed guessing of DECIMAL to set the `input` option of `NumberType` to string
8+
49
4.2.0
510
-----
611

src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public function guessType($class, $property)
7575
case 'time_immutable':
7676
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\TimeType', ['input' => 'datetime_immutable'], Guess::HIGH_CONFIDENCE);
7777
case Type::DECIMAL:
78+
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', ['input' => 'string'], Guess::MEDIUM_CONFIDENCE);
7879
case Type::FLOAT:
7980
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\NumberType', [], Guess::MEDIUM_CONFIDENCE);
8081
case Type::INTEGER:

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ CHANGELOG
4747
* dispatch `PostSubmitEvent` on `form.post_submit`
4848
* dispatch `PreSetDataEvent` on `form.pre_set_data`
4949
* dispatch `PostSetDataEvent` on `form.post_set_data`
50+
* added an `input` option to `NumberType`
5051

5152
4.2.0
5253
-----
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Form\Extension\Core\DataTransformer;
13+
14+
use Symfony\Component\Form\DataTransformerInterface;
15+
use Symfony\Component\Form\Exception\TransformationFailedException;
16+
17+
class StringToFloatTransformer implements DataTransformerInterface
18+
{
19+
private $scale;
20+
21+
public function __construct(int $scale = null)
22+
{
23+
$this->scale = $scale;
24+
}
25+
26+
/**
27+
* @param mixed $value
28+
*
29+
* @return float|null
30+
*/
31+
public function transform($value)
32+
{
33+
if (null === $value) {
34+
return null;
35+
}
36+
37+
if (!\is_string($value) || !is_numeric($value)) {
38+
throw new TransformationFailedException('Expected a numeric string.');
39+
}
40+
41+
return (float) $value;
42+
}
43+
44+
/**
45+
* @param mixed $value
46+
*
47+
* @return string|null
48+
*/
49+
public function reverseTransform($value)
50+
{
51+
if (null === $value) {
52+
return null;
53+
}
54+
55+
if (!\is_int($value) && !\is_float($value)) {
56+
throw new TransformationFailedException('Expected a numeric.');
57+
}
58+
59+
if ($this->scale > 0) {
60+
return number_format((float) $value, $this->scale, '.', '');
61+
}
62+
63+
return (string) $value;
64+
}
65+
}

src/Symfony/Component/Form/Extension/Core/Type/NumberType.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Form\AbstractType;
1515
use Symfony\Component\Form\Exception\LogicException;
1616
use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer;
17+
use Symfony\Component\Form\Extension\Core\DataTransformer\StringToFloatTransformer;
1718
use Symfony\Component\Form\FormBuilderInterface;
1819
use Symfony\Component\Form\FormInterface;
1920
use Symfony\Component\Form\FormView;
@@ -33,6 +34,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
3334
$options['rounding_mode'],
3435
$options['html5'] ? 'en' : null
3536
));
37+
38+
if ('string' === $options['input']) {
39+
$builder->addModelTransformer(new StringToFloatTransformer($options['scale']));
40+
}
3641
}
3742

3843
/**
@@ -56,6 +61,7 @@ public function configureOptions(OptionsResolver $resolver)
5661
'grouping' => false,
5762
'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALF_UP,
5863
'compound' => false,
64+
'input' => 'number',
5965
'html5' => false,
6066
]);
6167

@@ -68,7 +74,7 @@ public function configureOptions(OptionsResolver $resolver)
6874
NumberToLocalizedStringTransformer::ROUND_UP,
6975
NumberToLocalizedStringTransformer::ROUND_CEILING,
7076
]);
71-
77+
$resolver->setAllowedValues('input', ['number', 'string']);
7278
$resolver->setAllowedTypes('scale', ['null', 'int']);
7379
$resolver->setAllowedTypes('html5', 'bool');
7480

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the symfony/symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Form\Tests\Extension\Core\DataTransformer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Form\Extension\Core\DataTransformer\StringToFloatTransformer;
16+
17+
class StringToFloatTransformerTest extends TestCase
18+
{
19+
private $transformer;
20+
21+
protected function setUp()
22+
{
23+
$this->transformer = new StringToFloatTransformer();
24+
}
25+
26+
protected function tearDown()
27+
{
28+
$this->transformer = null;
29+
}
30+
31+
public function provideTransformations(): array
32+
{
33+
return [
34+
[null, null],
35+
['1', 1.],
36+
['1.', 1.],
37+
['1.0', 1.],
38+
['1.23', 1.23],
39+
];
40+
}
41+
42+
/**
43+
* @dataProvider provideTransformations
44+
*/
45+
public function testTransform($from, $to): void
46+
{
47+
$transformer = new StringToFloatTransformer();
48+
49+
$this->assertSame($to, $transformer->transform($from));
50+
}
51+
52+
/**
53+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
54+
*/
55+
public function testFailIfTransformingANonString(): void
56+
{
57+
$transformer = new StringToFloatTransformer();
58+
$transformer->transform(1.0);
59+
}
60+
61+
/**
62+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
63+
*/
64+
public function testFailIfTransformingANonNumericString(): void
65+
{
66+
$transformer = new StringToFloatTransformer();
67+
$transformer->transform('foobar');
68+
}
69+
70+
public function provideReverseTransformations(): array
71+
{
72+
return [
73+
[null, null],
74+
[1, '1'],
75+
[1., '1'],
76+
[1.0, '1'],
77+
[1.23, '1.23'],
78+
[1, '1.000', 3],
79+
[1.0, '1.000', 3],
80+
[1.23, '1.230', 3],
81+
[1.2344, '1.234', 3],
82+
[1.2345, '1.235', 3],
83+
];
84+
}
85+
86+
/**
87+
* @dataProvider provideReverseTransformations
88+
*/
89+
public function testReverseTransform($from, $to, int $scale = null): void
90+
{
91+
$transformer = new StringToFloatTransformer($scale);
92+
93+
$this->assertSame($to, $transformer->reverseTransform($from));
94+
}
95+
96+
/**
97+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
98+
*/
99+
public function testFailIfReverseTransformingANonNumeric(): void
100+
{
101+
$transformer = new StringToFloatTransformer();
102+
$transformer->reverseTransform('foobar');
103+
}
104+
}

src/Symfony/Component/Form/Tests/Extension/Core/Type/NumberTypeTest.php

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,47 @@ protected function setUp()
2727
\Locale::setDefault('de_DE');
2828
}
2929

30-
public function testDefaultFormatting()
30+
public function testDefaultFormatting(): void
3131
{
3232
$form = $this->factory->create(static::TESTED_TYPE);
3333
$form->setData('12345.67890');
3434

3535
$this->assertSame('12345,679', $form->createView()->vars['value']);
3636
}
3737

38-
public function testDefaultFormattingWithGrouping()
38+
public function testDefaultFormattingWithGrouping(): void
3939
{
4040
$form = $this->factory->create(static::TESTED_TYPE, null, ['grouping' => true]);
4141
$form->setData('12345.67890');
4242

4343
$this->assertSame('12.345,679', $form->createView()->vars['value']);
4444
}
4545

46-
public function testDefaultFormattingWithScale()
46+
public function testDefaultFormattingWithScale(): void
4747
{
4848
$form = $this->factory->create(static::TESTED_TYPE, null, ['scale' => 2]);
4949
$form->setData('12345.67890');
5050

5151
$this->assertSame('12345,68', $form->createView()->vars['value']);
5252
}
5353

54-
public function testDefaultFormattingWithRounding()
54+
public function testDefaultFormattingWithScaleFloat(): void
55+
{
56+
$form = $this->factory->create(static::TESTED_TYPE, null, ['scale' => 2]);
57+
$form->setData(12345.67890);
58+
59+
$this->assertSame('12345,68', $form->createView()->vars['value']);
60+
}
61+
62+
public function testDefaultFormattingWithScaleAndStringInput(): void
63+
{
64+
$form = $this->factory->create(static::TESTED_TYPE, null, ['scale' => 2, 'input' => 'string']);
65+
$form->setData('12345.67890');
66+
67+
$this->assertSame('12345,68', $form->createView()->vars['value']);
68+
}
69+
70+
public function testDefaultFormattingWithRounding(): void
5571
{
5672
$form = $this->factory->create(static::TESTED_TYPE, null, ['scale' => 0, 'rounding_mode' => \NumberFormatter::ROUND_UP]);
5773
$form->setData('12345.54321');
@@ -76,6 +92,46 @@ public function testSubmitNullUsesDefaultEmptyData($emptyData = '10', $expectedD
7692
$this->assertSame($expectedData, $form->getData());
7793
}
7894

95+
public function testSubmitNumericInput(): void
96+
{
97+
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'number']);
98+
$form->submit('1,234');
99+
100+
$this->assertSame(1.234, $form->getData());
101+
$this->assertSame(1.234, $form->getNormData());
102+
$this->assertSame('1,234', $form->getViewData());
103+
}
104+
105+
public function testSubmitNumericInputWithScale(): void
106+
{
107+
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'number', 'scale' => 2]);
108+
$form->submit('1,234');
109+
110+
$this->assertSame(1.23, $form->getData());
111+
$this->assertSame(1.23, $form->getNormData());
112+
$this->assertSame('1,23', $form->getViewData());
113+
}
114+
115+
public function testSubmitStringInput(): void
116+
{
117+
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'string']);
118+
$form->submit('1,234');
119+
120+
$this->assertSame('1.234', $form->getData());
121+
$this->assertSame(1.234, $form->getNormData());
122+
$this->assertSame('1,234', $form->getViewData());
123+
}
124+
125+
public function testSubmitStringInputWithScale(): void
126+
{
127+
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'string', 'scale' => 2]);
128+
$form->submit('1,234');
129+
130+
$this->assertSame('1.23', $form->getData());
131+
$this->assertSame(1.23, $form->getNormData());
132+
$this->assertSame('1,23', $form->getViewData());
133+
}
134+
79135
public function testIgnoresDefaultLocaleToRenderHtml5NumberWidgets()
80136
{
81137
$form = $this->factory->create(static::TESTED_TYPE, null, [

0 commit comments

Comments
 (0)