Skip to content

Commit f7312a4

Browse files
romaricdrigonnicolas-grekas
authored andcommitted
[Form] Added "html5" option to both MoneyType and PercentType
1 parent aa5d0ea commit f7312a4

File tree

8 files changed

+188
-8
lines changed

8 files changed

+188
-8
lines changed

src/Symfony/Component/Form/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Added support for using the `{{ label }}` placeholder in constraint messages, which is replaced in the `ViolationMapper` by the corresponding field form label.
88
* Added `DataMapper`, `ChainAccessor`, `PropertyPathAccessor` and `CallbackAccessor` with new callable `getter` and `setter` options for each form type
99
* Deprecated `PropertyPathMapper` in favor of `DataMapper` and `PropertyPathAccessor`
10+
* Added a `html5` option to `MoneyType` and `PercentType`, to use `<input type="number" />`
1011

1112
5.1.0
1213
-----

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

+2-2
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

+14-4
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 an HTML5 specific format, see 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

+20-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
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;
@@ -28,12 +29,15 @@ class MoneyType extends AbstractType
2829
*/
2930
public function buildForm(FormBuilderInterface $builder, array $options)
3031
{
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
3134
$builder
3235
->addViewTransformer(new MoneyToLocalizedStringTransformer(
3336
$options['scale'],
3437
$options['grouping'],
3538
$options['rounding_mode'],
36-
$options['divisor']
39+
$options['divisor'],
40+
$options['html5'] ? 'en' : null
3741
))
3842
;
3943
}
@@ -44,6 +48,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4448
public function buildView(FormView $view, FormInterface $form, array $options)
4549
{
4650
$view->vars['money_pattern'] = self::getPattern($options['currency']);
51+
52+
if ($options['html5']) {
53+
$view->vars['type'] = 'number';
54+
}
4755
}
4856

4957
/**
@@ -58,6 +66,7 @@ public function configureOptions(OptionsResolver $resolver)
5866
'divisor' => 1,
5967
'currency' => 'EUR',
6068
'compound' => false,
69+
'html5' => false,
6170
'invalid_message' => function (Options $options, $previousValue) {
6271
return ($options['legacy_error_messages'] ?? true)
6372
? $previousValue
@@ -76,6 +85,16 @@ public function configureOptions(OptionsResolver $resolver)
7685
]);
7786

7887
$resolver->setAllowedTypes('scale', 'int');
88+
89+
$resolver->setAllowedTypes('html5', 'bool');
90+
91+
$resolver->setNormalizer('grouping', function (Options $options, $value) {
92+
if ($value && $options['html5']) {
93+
throw new LogicException('Cannot use the "grouping" option when the "html5" option is enabled.');
94+
}
95+
96+
return $value;
97+
});
7998
}
8099

81100
/**

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

+7-1
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
'invalid_message' => function (Options $options, $previousValue) {
6166
return ($options['legacy_error_messages'] ?? true)
6267
? $previousValue
@@ -87,6 +92,7 @@ public function configureOptions(OptionsResolver $resolver)
8792

8893
return '';
8994
});
95+
$resolver->setAllowedTypes('html5', 'bool');
9096
}
9197

9298
/**

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

+80
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

+14
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

+50
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)