[Form] Fixed DateType to use "format" for creating the year and day choices

This commit is contained in:
Bernhard Schussek 2012-07-10 14:53:17 +02:00
parent 7a181002d5
commit 5b057f89b0
3 changed files with 109 additions and 82 deletions

View File

@ -147,3 +147,5 @@ CHANGELOG
* [BC BREAK] fixed: form constraints are only validated if they belong to the validated group * [BC BREAK] fixed: form constraints are only validated if they belong to the validated group
* deprecated `bindRequest` in `Form` and replaced it by a listener to FormEvents::PRE_BIND * deprecated `bindRequest` in `Form` and replaced it by a listener to FormEvents::PRE_BIND
* fixed: the "data" option supersedes default values from the model * fixed: the "data" option supersedes default values from the model
* changed DateType to refer to the "format" option for calculating the year and day choices instead
of padding them automatically

View File

@ -23,82 +23,78 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTra
use Symfony\Component\Form\ReversedTransformer; use Symfony\Component\Form\ReversedTransformer;
use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
class DateType extends AbstractType class DateType extends AbstractType
{ {
const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM; const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM;
private static $acceptedFormats = array(
\IntlDateFormatter::FULL,
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT,
);
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$format = $options['format']; $dateFormat = is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT;
$pattern = null; $timeFormat = \IntlDateFormatter::NONE;
$calendar = \IntlDateFormatter::GREGORIAN;
$pattern = is_string($options['format']) ? $options['format'] : null;
$allowedFormats = array( if (!in_array($dateFormat, self::$acceptedFormats, true)) {
\IntlDateFormatter::FULL, throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT,
);
// If $format is not in the allowed options, it's considered as the pattern of the formatter if it is a string
if (!in_array($format, $allowedFormats, true)) {
if (is_string($format)) {
$format = self::DEFAULT_FORMAT;
$pattern = $options['format'];
} else {
throw new CreationException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom pattern');
}
} }
$formatter = new \IntlDateFormatter( if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd'))) {
\Locale::getDefault(), throw new InvalidOptionsException(sprintf('The "format" option should contain the patterns "y", "M" and "d". Its current value is "%s".', $pattern));
$format, }
\IntlDateFormatter::NONE,
'UTC',
\IntlDateFormatter::GREGORIAN,
$pattern
);
$formatter->setLenient(false);
if ('single_text' === $options['widget']) { if ('single_text' === $options['widget']) {
$builder->addViewTransformer(new DateTimeToLocalizedStringTransformer($options['data_timezone'], $options['user_timezone'], $format, \IntlDateFormatter::NONE, \IntlDateFormatter::GREGORIAN, $pattern)); $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
$options['data_timezone'],
$options['user_timezone'],
$dateFormat,
$timeFormat,
$calendar,
$pattern
));
} else { } else {
$yearOptions = $monthOptions = $dayOptions = array(); $yearOptions = $monthOptions = $dayOptions = array();
$formatter = new \IntlDateFormatter(
\Locale::getDefault(),
$dateFormat,
$timeFormat,
'UTC',
$calendar,
$pattern
);
$formatter->setLenient(false);
if ('choice' === $options['widget']) { if ('choice' === $options['widget']) {
$years = $months = $days = array();
foreach ($options['years'] as $year) {
$years[$year] = str_pad($year, 4, '0', STR_PAD_LEFT);
}
foreach ($options['months'] as $month) {
$months[$month] = str_pad($month, 2, '0', STR_PAD_LEFT);
}
foreach ($options['days'] as $day) {
$days[$day] = str_pad($day, 2, '0', STR_PAD_LEFT);
}
// Only pass a subset of the options to children // Only pass a subset of the options to children
$yearOptions = array( $yearOptions = array(
'choices' => $years, 'choices' => $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])),
'empty_value' => $options['empty_value']['year'], 'empty_value' => $options['empty_value']['year'],
); );
$monthOptions = array( $monthOptions = array(
'choices' => $this->formatMonths($formatter, $months), 'choices' => $this->formatTimestamps($formatter, '/M+/', $this->listMonths($options['months'])),
'empty_value' => $options['empty_value']['month'], 'empty_value' => $options['empty_value']['month'],
); );
$dayOptions = array( $dayOptions = array(
'choices' => $days, 'choices' => $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])),
'empty_value' => $options['empty_value']['day'], 'empty_value' => $options['empty_value']['day'],
); );
}
// Append generic carry-along options // Append generic carry-along options
foreach (array('required', 'translation_domain') as $passOpt) { foreach (array('required', 'translation_domain') as $passOpt) {
$yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt]; $yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
}
} }
$builder $builder
@ -108,6 +104,7 @@ class DateType extends AbstractType
->addViewTransformer(new DateTimeToArrayTransformer( ->addViewTransformer(new DateTimeToArrayTransformer(
$options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day') $options['data_timezone'], $options['user_timezone'], array('year', 'month', 'day')
)) ))
->setAttribute('formatter', $formatter)
; ;
} }
@ -124,8 +121,6 @@ class DateType extends AbstractType
new DateTimeToArrayTransformer($options['data_timezone'], $options['data_timezone'], array('year', 'month', 'day')) new DateTimeToArrayTransformer($options['data_timezone'], $options['data_timezone'], array('year', 'month', 'day'))
)); ));
} }
$builder->setAttribute('formatter', $formatter);
} }
/** /**
@ -139,7 +134,7 @@ class DateType extends AbstractType
$view->setVar('type', 'date'); $view->setVar('type', 'date');
} }
if (count($view) > 0) { if ($form->getConfig()->hasAttribute('formatter')) {
$pattern = $form->getConfig()->getAttribute('formatter')->getPattern(); $pattern = $form->getConfig()->getAttribute('formatter')->getPattern();
// set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy) // set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy)
@ -224,6 +219,10 @@ class DateType extends AbstractType
'choice', 'choice',
), ),
)); ));
$resolver->setAllowedTypes(array(
'format' => array('int', 'string'),
));
} }
/** /**
@ -242,18 +241,18 @@ class DateType extends AbstractType
return 'date'; return 'date';
} }
private function formatMonths(\IntlDateFormatter $formatter, array $months) private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $timestamps)
{ {
$pattern = $formatter->getPattern(); $pattern = $formatter->getPattern();
$timezone = $formatter->getTimezoneId(); $timezone = $formatter->getTimezoneId();
$formatter->setTimezoneId(\DateTimeZone::UTC); $formatter->setTimezoneId(\DateTimeZone::UTC);
if (preg_match('/M+/', $pattern, $matches)) { if (preg_match($regex, $pattern, $matches)) {
$formatter->setPattern($matches[0]); $formatter->setPattern($matches[0]);
foreach ($months as $key => $value) { foreach ($timestamps as $key => $timestamp) {
$months[$key] = $formatter->format(gmmktime(0, 0, 0, $key, 15)); $timestamps[$key] = $formatter->format($timestamp);
} }
// I'd like to clone the formatter above, but then we get a // I'd like to clone the formatter above, but then we get a
@ -263,6 +262,39 @@ class DateType extends AbstractType
$formatter->setTimezoneId($timezone); $formatter->setTimezoneId($timezone);
return $months; return $timestamps;
}
private function listYears(array $years)
{
$result = array();
foreach ($years as $year) {
$result[$year] = gmmktime(0, 0, 0, 6, 15, $year);
}
return $result;
}
private function listMonths(array $months)
{
$result = array();
foreach ($months as $month) {
$result[$month] = gmmktime(0, 0, 0, $month, 15);
}
return $result;
}
private function listDays(array $days)
{
$result = array();
foreach ($days as $day) {
$result[$day] = gmmktime(0, 0, 0, 5, $day);
}
return $result;
} }
} }

View File

@ -250,37 +250,45 @@ class DateTypeTest extends LocalizedTestCase
/** /**
* This test is to check that the strings '0', '1', '2', '3' are no accepted * This test is to check that the strings '0', '1', '2', '3' are no accepted
* as valid IntlDateFormatter constants for FULL, LONG, MEDIUM or SHORT respectively. * as valid IntlDateFormatter constants for FULL, LONG, MEDIUM or SHORT respectively.
*
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/ */
public function testFormatOptionCustomPatternCollapsingIntlDateFormatterConstant() public function testThrowExceptionIfFormatIsNoPattern()
{ {
$form = $this->factory->create('date', null, array( $this->factory->create('date', null, array(
'format' => '0', 'format' => '0',
'widget' => 'single_text', 'widget' => 'single_text',
'input' => 'string', 'input' => 'string',
)); ));
$form->setData('2010-06-02');
// This would be what would be outputed if '0' was mistaken for \IntlDateFormatter::FULL
$this->assertNotEquals('Mittwoch, 02. Juni 2010', $form->getViewData());
} }
/** /**
* @expectedException Symfony\Component\Form\Exception\CreationException * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/ */
public function testValidateFormatOptionGivenWrongConstants() public function testThrowExceptionIfFormatDoesNotContainYearMonthAndDay()
{ {
$form = $this->factory->create('date', null, array( $this->factory->create('date', null, array(
'months' => array(6, 7),
'format' => 'yy',
));
}
/**
* @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function testThrowExceptionIfFormatIsNoConstant()
{
$this->factory->create('date', null, array(
'format' => 105, 'format' => 105,
)); ));
} }
/** /**
* @expectedException Symfony\Component\Form\Exception\CreationException * @expectedException Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/ */
public function testValidateFormatOptionGivenArrayValue() public function testThrowExceptionIfFormatIsInvalid()
{ {
$form = $this->factory->create('date', null, array( $this->factory->create('date', null, array(
'format' => array(), 'format' => array(),
)); ));
} }
@ -344,21 +352,6 @@ class DateTypeTest extends LocalizedTestCase
), $view->get('month')->getVar('choices')); ), $view->get('month')->getVar('choices'));
} }
public function testMonthsOptionNumericIfFormatContainsNoMonth()
{
$form = $this->factory->create('date', null, array(
'months' => array(6, 7),
'format' => 'yy',
));
$view = $form->createView();
$this->assertEquals(array(
new ChoiceView('6', '06'),
new ChoiceView('7', '07'),
), $view->get('month')->getVar('choices'));
}
public function testMonthsOptionShortFormat() public function testMonthsOptionShortFormat()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(