[Form] Fixed DateType to use "format" for creating the year and day choices
This commit is contained in:
parent
7a181002d5
commit
5b057f89b0
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
Reference in New Issue
Block a user