8000 [Form] Fixed DateType to use "format" for creating the year and day c… · defrag/symfony@5b057f8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5b057f8

Browse files
committed
[Form] Fixed DateType to use "format" for creating the year and day choices
1 parent 7a18100 commit 5b057f8

File tree

3 files changed

+109
-82
lines changed

3 files changed

+109
-82
lines changed

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,5 @@ CHANGELOG
147147
* [BC BREAK] fixed: form constraints are only validated if they belong to the validated group
148148
* deprecated `bindRequest` in `Form` and replaced it by a listener to FormEvents::PRE_BIND
149149
* fixed: the "data" option supersedes default values from the model
150+
* changed DateType to refer to the "format" option for calculating the year and day choices instead
151+
of padding them automatically

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

Lines changed: 87 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,82 +23,78 @@
2323
use Symfony\Component\Form\ReversedTransformer;
2424
use Symfony\Component\OptionsResolver\Options;
2525
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
26+
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
2627

2728
class DateType extends AbstractType
2829
{
2930
const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM;
3031

32+
private static $acceptedFormats = array(
33+
\IntlDateFormatter::FULL,
34+
\IntlDateFormatter::LONG,
35+
\IntlDateFormatter::MEDIUM,
36+
\IntlDateFormatter::SHORT,
37+
);
38+
3139
/**
3240
* {@inheritdoc}
3341
*/
3442
public function buildForm(FormBuilderInterface $builder, array $options)
3543
{
36-
$format = $options['format'];
37-
$pattern = null;
38-
39-
$allowedFormats = array(
40-
\IntlDateFormatter::FULL,
41-
\IntlDateFormatter::LONG,
42-
\IntlDateFormatter::MEDIUM,
43-
\IntlDateFormatter::SHORT,
44-
);
45-
46-
// If $format is not in the allowed options, it's considered as the pattern of the formatter if it is a string
47-
if (!in_array($format, $allowedFormats, true)) {
48-
if (is_string($format)) {
49-
$format = self::DEFAULT_FORMAT;
50-
$pattern = $options['format'];
51-
} else {
52-
throw new CreationException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom pattern');
53-
}
44+
$dateFormat = is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT;
45+
$timeFormat = \IntlDateFormatter::NONE;
46+
$calendar = \IntlDateFormatter::GREGORIAN;
47+
$pattern = is_string($options['format']) ? $options['format'] : null;
48+
49+
if (!in_array($dateFormat, self::$acceptedFormats, true)) {
50+
throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
5451
}
5552

56-
$formatter = new \IntlDateFormatter(
57-
\Locale::getDefault(),
58-
$format,
59-
\IntlDateFormatter::NONE,
60-
'UTC',
61-
\IntlDateFormatter::GREGORIAN,
62-
$pattern
63-
);
64-
$formatter->setLenient(false);
53+
if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd'))) {
54+
throw new InvalidOptionsException(sprintf('The "format" option should contain the patterns "y", "M" and "d". Its current value is "%s".', $pattern));
55+
}
6556

6657
if ('single_text' === $options['widget']) {
67-
$builder->addViewTransformer(new DateTimeToLocalizedStringTransformer($options['data_timezone'], $options['user_timezone'], $format, \IntlDateFormatter::NONE, \IntlDateFormatter::GREGORIAN, $pattern));
58+
$builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
59+
$options['data_timezone'],
60+
$options['user_timezone'],
61+
$dateFormat,
62+
$timeFormat,
63+
$calendar,
64+
$pattern
65+
));
6866
} else {
6967
$yearOptions = $monthOptions = $dayOptions = array();
7068

71-
if ('choice' === $options['widget']) {
72-
$years = $months = $days = array();
73-
74-
foreach ($options['years'] as $year) {
75-
$years[$year] = str_pad($year, 4, '0', STR_PAD_LEFT);
76-
}
77-
foreach ($options['months'] as $month) {
78-
$months[$month] = str_pad($month, 2, '0', STR_PAD_LEFT);
79-
}
80-
foreach ($options['days'] as $day) {
81-
$days[$day] = str_pad($day, 2, '0', STR_PAD_LEFT);
82-
}
69+
$formatter = new \IntlDateFormatter(
70+
\Locale::getDefault(),
71+
$dateFormat,
72+
$timeFormat,
73+
'UTC',
74+
$calendar,
75+
$pattern
76+
);
77+
$formatter->setLenient(false);
8378

79+
if ('choice' === $options['widget']) {
8480
// Only pass a subset of the options to children
8581
$yearOptions = array(
86-
'choices' => $years,
82+
'choices' => $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])),
8783
'empty_value' => $options['empty_value']['year'],
8884
);
8985
$monthOptions = array(
90-
'choices' => $this->formatMonths($formatter, $months),
86+
'choices' => $this->formatTimestamps($formatter, '/M+/', $this->listMonths($options['months'])),
9187
'empty_value' => $options['empty_value']['month'],
9288
);
9389
$dayOptions = array(
94-
'choices' => $days,
90+
'choices' => $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])),
9591
'empty_value' => $options['empty_value']['day'],
9692
);
93+
}
9794

98-
// Append generic carry-along options
99-
foreach (array('required', 'translation_domain') as $passOpt) {
100-
$yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
101-
}
95+
// Append generic carry-along options
96+
foreach (array('required', 'translation_domain') as $passOpt) {
97+
$yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
10298
}
10399

104100
$builder
@@ -108,6 +104,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
108104
->addViewTransformer(new DateTimeToArrayTransformer(
109105
$options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day')
110106
))
107+
->setAttribute('formatter', $formatter)
111108
;
112109
}
113110

@@ -124,8 +121,6 @@ public function buildForm(FormBuilderInterface $builder, array $options)
124121
new DateTimeToArrayTransformer($options['data_timezone'], $options['data_timezone'], array('year', 'month', 'day'))
125122
));
126123
}
127-
128-
$builder->setAttribute('formatter', $formatter);
129124
}
130125

131126
/**
@@ -139,7 +134,7 @@ public function finishView(FormViewInterface $view, FormInterface $form, array $
139134
$view->setVar('type', 'date');
140135
}
141136

142-
if (count($view) > 0) {
137+
if ($form->getConfig()->hasAttribute('formatter')) {
143138
$pattern = $form->getConfig()->getAttribute('formatter')->getPattern();
144139

145140
// set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
@@ -224,6 +219,10 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
224219
'choice',
225220
),
226221
));
222+
223+
$resolver->setAllowedTypes(array(
224+
'format' => array('int', 'string'),
225+
));
227226
}
228227

229228
/**
@@ -242,18 +241,18 @@ public function getName()
242241
return 'date';
243242
}
244243

245-
private function formatMonths(\IntlDateFormatter $formatter, array $months)
244+
private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $timestamps)
246245
{
247246
$pattern = $formatter->getPattern();
248247
$timezone = $formatter->getTimezoneId();
249248

250249
$formatter->setTimezoneId(\DateTimeZone::UTC);
251250

252-
if (preg_match('/M+/', $pattern, $matches)) {
251+
if (preg_match($regex, $pattern, $matches)) {
253252
$formatter->setPattern($matches[0]);
254253

255-
foreach ($months as $key => $value) {
256-
$months[$key] = $formatter->format(gmmktime(0, 0, 0, $key, 15));
254+
foreach ($timestamps as $key => $timestamp) {
255+
$timestamps[$key] = $formatter->format($timestamp);
257256
}
258257

259258
// I'd like to clone the formatter above, but then we get a
@@ -263,6 +262,39 @@ private function formatMonths(\IntlDateFormatter $formatter, array $months)
263262

264263
$formatter->setTimezoneId($timezone);
265264

266-
return $months;
265+
return $timestamps;
266+
}
267+
268+
private function listYears(array $years)
269+
{
270+
$result = array();
271+
272+
foreach ($years as $year) {
273+
$result[$year] = gmmktime(0, 0, 0, 6, 15, $year);
274+
}
275+
276+
return $result;
277+
}
278+
279+
private function listMonths(array $months)
280+
{
281+
$result = array();
282+
283+
foreach ($months as $month) {
284+
$result[$month] = gmmktime(0, 0, 0, $month, 15);
285+
}
286+
287+
return $result;
288+
}
289+
290+
private function listDays(array $days)
291+
{
292+
$result = array();
293+
294+
foreach ($days as $day) {
295+
$result[$day] = gmmktime(0, 0, 0, 5, $day);
296+
}
297+
298+
return $result;
267299
}
268300
}

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

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -250,37 +250,45 @@ public function testSubmitFromInputRawDifferentPattern()
250250
/**
251251
* This test is to check that the strings '0', '1', '2', '3' are no accepted
252252
* as valid IntlDateFormatter constants for FULL, LONG, MEDIUM or SHORT respectively.
253+
*
254+
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
253255
*/
254-
public function testFormatOptionCustomPatternCollapsingIntlDateFormatterConstant()
256+
public function testThrowExceptionIfFormatIsNoPattern()
255257
{
256-
$form = $this->factory->create('date', null, array(
258+
$this->factory->create('date', null, array(
257259
'format' => '0',
258260
'widget' => 'single_text',
259261
'input' => 'string',
260262
));
263+
}
261264

262-
$form->setData('2010-06-02');
263-
264-
// This would be what would be outputed if '0' was mistaken for \IntlDateFormatter::FULL
265-
$this->assertNotEquals('Mittwoch, 02. Juni 2010', $form->getViewData());
265+
/**
266+
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
267+
*/
268+
public function testThrowExceptionIfFormatDoesNotContainYearMonthAndDay()
269+
{
270+
$this->factory->create('date', null, array(
271+
'months' => array(6, 7),
272+
'format' => 'yy',
273+
));
266274
}
267275

268276
/**
269-
* @expectedException Symfony\Component\Form\Exception\CreationException
277+
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
270278
*/
271-
public function testValidateFormatOptionGivenWrongConstants()
279+
public function testThrowExceptionIfFormatIsNoConstant()
272280
{
273-
$form = $this->factory->create('date', null, array(
281+
$this->factory->create('date', null, array(
274282
'format' => 105,
275283
));
276284
}
277285

278286
/**
279-
* @expectedException Symfony\Component\Form\Exception\CreationException
287+
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
280288
*/
281-
public function testValidateFormatOptionGivenArrayValue()
289+
public function testThrowExceptionIfFormatIsInvalid()
282290
{
283-
$form = $this->factory->create('date', null, array(
291+
$this->factory->create('date', null, array(
284292
'format' => array(),
285293
));
286294
}
@@ -344,21 +352,6 @@ public function testMonthsOption()
344352
), $view->get('month')->getVar('choices'));
345353
}
346354

347-
public function testMonthsOptionNumericIfFormatContainsNoMonth()
348-
{
349-
$form = $this->factory->create('date', null, array(
350-
'months' => array(6, 7),
351-
'format' => 'yy',
352-
));
353-
354-
$view = $form->createView();
355-
356-
$this->assertEquals(array(
357-
new ChoiceView('6', '06'),
358-
new ChoiceView('7', '07'),
359-
), $view->get('month')->getVar('choices'));
360-
}
361-
362355
public function testMonthsOptionShortFormat()
363356
{
364357
$form = $this->factory->create('date', null, array(

0 commit comments

Comments
 (0)
0