[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
* deprecated `bindRequest` in `Form` and replaced it by a listener to FormEvents::PRE_BIND
* 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\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
class DateType extends AbstractType
{
const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM;
private static $acceptedFormats = array(
\IntlDateFormatter::FULL,
\IntlDateFormatter::LONG,
\IntlDateFormatter::MEDIUM,
\IntlDateFormatter::SHORT,
);
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$format = $options['format'];
$pattern = null;
$dateFormat = is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT;
$timeFormat = \IntlDateFormatter::NONE;
$calendar = \IntlDateFormatter::GREGORIAN;
$pattern = is_string($options['format']) ? $options['format'] : null;
$allowedFormats = array(
\IntlDateFormatter::FULL,
\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');
}
if (!in_array($dateFormat, self::$acceptedFormats, true)) {
throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
}
$formatter = new \IntlDateFormatter(
\Locale::getDefault(),
$format,
\IntlDateFormatter::NONE,
'UTC',
\IntlDateFormatter::GREGORIAN,
$pattern
);
$formatter->setLenient(false);
if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd'))) {
throw new InvalidOptionsException(sprintf('The "format" option should contain the patterns "y", "M" and "d". Its current value is "%s".', $pattern));
}
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 {
$yearOptions = $monthOptions = $dayOptions = array();
$formatter = new \IntlDateFormatter(
\Locale::getDefault(),
$dateFormat,
$timeFormat,
'UTC',
$calendar,
$pattern
);
$formatter->setLenient(false);
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
$yearOptions = array(
'choices' => $years,
'choices' => $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])),
'empty_value' => $options['empty_value']['year'],
);
$monthOptions = array(
'choices' => $this->formatMonths($formatter, $months),
'choices' => $this->formatTimestamps($formatter, '/M+/', $this->listMonths($options['months'])),
'empty_value' => $options['empty_value']['month'],
);
$dayOptions = array(
'choices' => $days,
'choices' => $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])),
'empty_value' => $options['empty_value']['day'],
);
}
// Append generic carry-along options
foreach (array('required', 'translation_domain') as $passOpt) {
$yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
}
// Append generic carry-along options
foreach (array('required', 'translation_domain') as $passOpt) {
$yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt];
}
$builder
@ -108,6 +104,7 @@ class DateType extends AbstractType
->addViewTransformer(new DateTimeToArrayTransformer(
$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'))
));
}
$builder->setAttribute('formatter', $formatter);
}
/**
@ -139,7 +134,7 @@ class DateType extends AbstractType
$view->setVar('type', 'date');
}
if (count($view) > 0) {
if ($form->getConfig()->hasAttribute('formatter')) {
$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)
@ -224,6 +219,10 @@ class DateType extends AbstractType
'choice',
),
));
$resolver->setAllowedTypes(array(
'format' => array('int', 'string'),
));
}
/**
@ -242,18 +241,18 @@ class DateType extends AbstractType
return 'date';
}
private function formatMonths(\IntlDateFormatter $formatter, array $months)
private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $timestamps)
{
$pattern = $formatter->getPattern();
$timezone = $formatter->getTimezoneId();
$formatter->setTimezoneId(\DateTimeZone::UTC);
if (preg_match('/M+/', $pattern, $matches)) {
if (preg_match($regex, $pattern, $matches)) {
$formatter->setPattern($matches[0]);
foreach ($months as $key => $value) {
$months[$key] = $formatter->format(gmmktime(0, 0, 0, $key, 15));
foreach ($timestamps as $key => $timestamp) {
$timestamps[$key] = $formatter->format($timestamp);
}
// I'd like to clone the formatter above, but then we get a
@ -263,6 +262,39 @@ class DateType extends AbstractType
$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
* 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',
'widget' => 'single_text',
'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,
));
}
/**
* @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(),
));
}
@ -344,21 +352,6 @@ class DateTypeTest extends LocalizedTestCase
), $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()
{
$form = $this->factory->create('date', null, array(