Skip to content

Commit e079a9a

Browse files
committed
Added a "html5" option to PercentType, to render an input[type="number"]. Added tests over PercentType.
1 parent 75e71e3 commit e079a9a

File tree

8 files changed

+191
-9
lines changed

8 files changed

+191
-9
lines changed

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ CHANGELOG
2121
* Deprecated `Symfony\Component\Form\Extension\Validator\Util\ServerParams` in favor of its parent class `Symfony\Component\Form\Util\ServerParams`
2222
* Added the `html5` option to the `ColorType` to validate the input
2323
* Deprecated `NumberToLocalizedStringTransformer::ROUND_*` constants, use `\NumberFormatter::ROUND_*` instead
24+
* Added a `html5` option to `MoneyType` and `PercentType`, to use `<input type="number" />`
25+
2426

2527
5.0.0
2628
-----

src/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransform
2323
{
2424
private $divisor;
2525

26-
public function __construct(?int $scale = 2, ?bool $grouping = true, ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, ?int $divisor = 1)
26+
public function __construct(?int $scale = 2, ?bool $grouping = true, ?int $roundingMode = \NumberFormatter::ROUND_HALFUP, ?int $divisor = 1, ?string $locale = null)
2727
{
2828
if (null === $grouping) {
2929
$grouping = true;
@@ -33,7 +33,7 @@ public function __construct(?int $scale = 2, ?bool $grouping = true, ?int $round
3333
$scale = 2;
3434
}
3535

36-
parent::__construct($scale, $grouping, $roundingMode);
36+
parent::__construct($scale, $grouping, $roundingMode, $locale);
3737

3838
if (null === $divisor) {
3939
$divisor = 1;

src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface
3434
private $roundingMode;
3535
private $type;
3636
private $scale;
37+
private $html5Format;
3738

3839
/**
3940
* @see self::$types for a list of supported types
4041
*
41-
* @param int $scale The scale
42-
* @param string $type One of the supported types
42+
* @param int $scale The scale
43+
* @param string $type One of the supported types
44+
* @param int|null $roundingMode a value from \NumberFormatter, such as \NumberFormatter::ROUND_HALFUP
45+
* @param bool $html5Format Use a HTML5 specific format, as per https://www.w3.org/TR/html51/sec-forms.html#date-time-and-number-formats
4346
*
4447
* @throws UnexpectedTypeException if the given value of type is unknown
4548
*/
46-
public function __construct(int $scale = null, string $type = null, ?int $roundingMode = null)
49+
public function __construct(int $scale = null, string $type = null, ?int $roundingMode = null, bool $html5Format = false)
4750
{
4851
if (null === $scale) {
4952
$scale = 0;
@@ -64,6 +67,7 @@ public function __construct(int $scale = null, string $type = null, ?int $roundi
6467
$this->type = $type;
6568
$this->scale = $scale;
6669
$this->roundingMode = $roundingMode;
70+
$this->html5Format = $html5Format;
6771
}
6872

6973
/**
@@ -182,7 +186,13 @@ public function reverseTransform($value)
182186
*/
183187
protected function getNumberFormatter()
184188
{
185-
$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL);
189+
// Values used in HTML5 number inputs should be formatted as in "1234.5", ie. 'en' format without grouping,
190+
// according to https://www.w3.org/TR/html51/sec-forms.html#date-time-and-number-formats
191+
$formatter = new \NumberFormatter($this->html5Format ? 'en' : \Locale::getDefault(), \NumberFormatter::DECIMAL);
192+
193+
if ($this->html5Format) {
194+
$formatter->setAttribute(\NumberFormatter::GROUPING_USED, 0);
195+
}
186196

187197
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->scale);
188198

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
namespace Symfony\Component\Form\Extension\Core\Type;
1313

1414
use Symfony\Component\Form\AbstractType;
15+
use Symfony\Component\Form\Exception\LogicException;
1516
use Symfony\Component\Form\Extension\Core\DataTransformer\MoneyToLocalizedStringTransformer;
1617
use Symfony\Component\Form\FormBuilderInterface;
1718
use Symfony\Component\Form\FormInterface;
1819
use Symfony\Component\Form\FormView;
20+
use Symfony\Component\OptionsResolver\Options;
1921
use Symfony\Component\OptionsResolver\OptionsResolver;
2022

2123
class MoneyType extends AbstractType
@@ -27,12 +29,15 @@ class MoneyType extends AbstractType
2729
*/
2830
public function buildForm(FormBuilderInterface $builder, array $options)
2931
{
32+
// Values used in HTML5 number inputs should be formatted as in "1234.5", ie. 'en' format without grouping,
33+
// according to https://www.w3.org/TR/html51/sec-forms.html#date-time-and-number-formats
3034
$builder
3135
->addViewTransformer(new MoneyToLocalizedStringTransformer(
3236
$options['scale'],
33-
$options['grouping'],
37+
$options['html5'] ? $options['grouping'] : false,
3438
$options['rounding_mode'],
35-
$options['divisor']
39+
$options['divisor'],
40+
$options['html5'] ? 'en' : null
3641
))
3742
;
3843
}
@@ -43,6 +48,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4348
public function buildView(FormView $view, FormInterface $form, array $options)
4449
{
4550
$view->vars['money_pattern'] = self::getPattern($options['currency']);
51+
52+
if ($options['html5']) {
53+
$view->vars['type'] = 'number';
54+
}
4655
}
4756

4857
/**
@@ -57,6 +66,7 @@ public function configureOptions(OptionsResolver $resolver)
5766
'divisor' => 1,
5867
'currency' => 'EUR',
5968
'compound' => false,
69+
'html5' => false,
6070
]);
6171

6272
$resolver->setAllowedValues('rounding_mode', [
@@ -70,6 +80,16 @@ public function configureOptions(OptionsResolver $resolver)
7080
]);
7181

7282
$resolver->setAllowedTypes('scale', 'int');
83+
84+
$resolver->setAllowedTypes('html5', 'bool');
85+
86+
$resolver->setNormalizer('grouping', function (Options $options, $value) {
87+
if (true === $value && $options['html5']) {
88+
throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.');
89+
}
90+
91+
return $value;
92+
});
7393
}
7494

7595
/**

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
3030
$options['scale'],
3131
$options['type'],
3232
$options['rounding_mode'],
33-
false
33+
$options['html5']
3434
));
3535
}
3636

@@ -40,6 +40,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4040
public function buildView(FormView $view, FormInterface $form, array $options)
4141
{
4242
$view->vars['symbol'] = $options['symbol'];
43+
44+
if ($options['html5']) {
45+
$view->vars['type'] = 'number';
46+
}
4347
}
4448

4549
/**
@@ -57,6 +61,7 @@ public function configureOptions(OptionsResolver $resolver)
5761
'symbol' => '%',
5862
'type' => 'fractional',
5963
'compound' => false,
64+
'html5' => false,
6065
]);
6166

6267
$resolver->setAllowedValues('type', [
@@ -82,6 +87,7 @@ public function configureOptions(OptionsResolver $resolver)
8287

8388
return '';
8489
});
90+
$resolver->setAllowedTypes('html5', 'bool');
8591
}
8692

8793
/**

src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/PercentToLocalizedStringTransformerTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,84 @@ public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte()
412412

413413
$transformer->reverseTransform("12\xc2\xa0345,678foo");
414414
}
415+
416+
public function testTransformForHtml5Format()
417+
{
418+
$transformer = new PercentToLocalizedStringTransformer(null, null, \NumberFormatter::ROUND_HALFUP, true);
419+
420+
// Since we test against "de_CH", we need the full implementation
421+
IntlTestHelper::requireFullIntl($this, false);
422+
423+
\Locale::setDefault('de_CH');
424+
425+
$this->assertEquals('10', $transformer->transform(0.104));
426+
$this->assertEquals('11', $transformer->transform(0.105));
427+
$this->assertEquals('200000', $transformer->transform(2000));
428+
}
429+
430+
public function testTransformForHtml5FormatWithInteger()
431+
{
432+
$transformer = new PercentToLocalizedStringTransformer(null, 'integer', \NumberFormatter::ROUND_HALFUP, true);
433+
434+
// Since we test against "de_CH", we need the full implementation
435+
IntlTestHelper::requireFullIntl($this, false);
436+
437+
\Locale::setDefault('de_CH');
438+
439+
$this->assertEquals('0', $transformer->transform(0.1));
440+
$this->assertEquals('1234', $transformer->transform(1234));
441+
}
442+
443+
public function testTransformForHtml5FormatWithScale()
444+
{
445+
// Since we test against "de_CH", we need the full implementation
446+
IntlTestHelper::requireFullIntl($this, false);
447+
448+
\Locale::setDefault('de_CH');
449+
450+
$transformer = new PercentToLocalizedStringTransformer(2, null, \NumberFormatter::ROUND_HALFUP, true);
451+
452+
$this->assertEquals('12.34', $transformer->transform(0.1234));
453+
}
454+
455+
public function testReverseTransformForHtml5Format()
456+
{
457+
// Since we test against "de_CH", we need the full implementation
458+
IntlTestHelper::requireFullIntl($this, false);
459+
460+
\Locale::setDefault('de_CH');
461+
462+
$transformer = new PercentToLocalizedStringTransformer(null, null, \NumberFormatter::ROUND_HALFUP, true);
463+
464+
$this->assertEquals(0.02, $transformer->reverseTransform('1.5')); // rounded up, for 2 decimals
465+
$this->assertEquals(0.15, $transformer->reverseTransform('15'));
466+
$this->assertEquals(2000, $transformer->reverseTransform('200000'));
467+
}
468+
469+
public function testReverseTransformForHtml5FormatWithInteger()
470+
{
471+
// Since we test against "de_CH", we need the full implementation
472+
IntlTestHelper::requireFullIntl($this, false);
473+
474+
\Locale::setDefault('de_CH');
475+
476+
$transformer = new PercentToLocalizedStringTransformer(null, 'integer', \NumberFormatter::ROUND_HALFUP, true);
477+
478+
$this->assertEquals(10, $transformer->reverseTransform('10'));
479+
$this->assertEquals(15, $transformer->reverseTransform('15'));
480+
$this->assertEquals(12, $transformer->reverseTransform('12'));
481+
$this->assertEquals(200, $transformer->reverseTransform('200'));
482+
}
483+
484+
public function testReverseTransformForHtml5FormatWithScale()
485+
{
486+
// Since we test against "de_CH", we need the full implementation
487+
IntlTestHelper::requireFullIntl($this, false);
488+
489+
\Locale::setDefault('de_CH');
490+
491+
$transformer = new PercentToLocalizedStringTransformer(2, null, \NumberFormatter::ROUND_HALFUP, true);
492+
493+
$this->assertEquals(0.1234, $transformer->reverseTransform('12.34'));
494+
}
415495
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,18 @@ public function testDefaultFormattingWithSpecifiedRounding()
109109

110110
$this->assertSame('12345', $form->createView()->vars['value']);
111111
}
112+
113+
public function testHtml5EnablesSpecificFormatting()
114+
{
115+
// Since we test against "de_CH", we need the full implementation
116+
IntlTestHelper::requireFullIntl($this, false);
117+
118+
\Locale::setDefault('de_CH');
119+
120+
$form = $this->factory->create(static::TESTED_TYPE, null, ['html5' => true, 'scale' => 2]);
121+
$form->setData('12345.6');
122+
123+
$this->assertSame('12345.60', $form->createView()->vars['value']);
124+
$this->assertSame('number', $form->createView()->vars['type']);
125+
}
112126
}

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,34 @@
1414
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
1515
use Symfony\Component\Form\Extension\Core\Type\PercentType;
1616
use Symfony\Component\Form\Test\TypeTestCase;
17+
use Symfony\Component\Intl\Util\IntlTestHelper;
1718

1819
class PercentTypeTest extends TypeTestCase
1920
{
2021
use ExpectDeprecationTrait;
2122

2223
const TESTED_TYPE = PercentType::class;
2324

25+
private $defaultLocale;
26+
27+
protected function setUp(): void
28+
{
29+
// we test against different locales, so we need the full
30+
// implementation
31+
IntlTestHelper::requireFullIntl($this, false);
32+
33+
parent::setUp();
34+
35+
$this->defaultLocale = \Locale::getDefault();
36+
}
37+
38+
protected function tearDown(): void
39+
{
40+
parent::tearDown();
41+
42+
\Locale::setDefault($this->defaultLocale);
43+
}
44+
2445
public function testSubmitWithRoundingMode()
2546
{
2647
$form = $this->factory->create(self::TESTED_TYPE, null, [
@@ -33,6 +54,35 @@ public function testSubmitWithRoundingMode()
3354
$this->assertEquals(0.0124, $form->getData());
3455
}
3556

57+
public function testSubmitNullUsesDefaultEmptyData($emptyData = '10', $expectedData = 0.1)
58+
{
59+
$form = $this->factory->create(static::TESTED_TYPE, null, [
60+
'empty_data' => $emptyData,
61+
'rounding_mode' => \NumberFormatter::ROUND_UP,
62+
]);
63+
$form->submit(null);
64+
65+
$this->assertSame($emptyData, $form->getViewData());
66+
$this->assertSame($expectedData, $form->getNormData());
67+
$this->assertSame($expectedData, $form->getData());
68+
}
69+
70+
public function testHtml5EnablesSpecificFormatting()
71+
{
72+
\Locale::setDefault('de_CH');
73+
74+
$form = $this->factory->create(static::TESTED_TYPE, null, [
75+
'html5' => true,
76+
'rounding_mode' => \NumberFormatter::ROUND_UP,
77+
'scale' => 2,
78+
'type' => 'integer',
79+
]);
80+
$form->setData('1234.56');
81+
82+
$this->assertSame('1234.56', $form->createView()->vars['value']);
83+
$this->assertSame('number', $form->createView()->vars['type']);
84+
}
85+
3686
/**
3787
* @group legacy
3888
*/

0 commit comments

Comments
 (0)