diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 156735b81751e..b1e6b3bf06b5c 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -74,6 +74,12 @@ public function __construct($choices, $value = null) $choices = iterator_to_array($choices); } + if (null === $value && $this->castableToString($choices)) { + $value = function ($choice) { + return (string) $choice; + }; + } + if (null !== $value) { // If a deterministic value generator was passed, use it later $this->valueCallback = $value; @@ -207,4 +213,35 @@ protected function flatten(array $choices, $value, &$choicesByValues, &$keysByVa $structuredValues[$key] = $choiceValue; } } + + /** + * Checks whether the given choices can be cast to strings without + * generating duplicates. + * + * @param array $choices The choices. + * @param array|null $cache The cache for previously checked entries. Internal + * + * @return bool Returns true if the choices can be cast to strings and + * false otherwise. + */ + private function castableToString(array $choices, array &$cache = array()) + { + foreach ($choices as $choice) { + if (is_array($choice)) { + if (!$this->castableToString($choice, $cache)) { + return false; + } + + continue; + } elseif (!is_scalar($choice)) { + return false; + } elseif (isset($cache[(string) $choice])) { + return false; + } + + $cache[(string) $choice] = true; + } + + return true; + } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 528834d1e3cb8..1b27090233cba 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -281,6 +281,14 @@ public function configureOptions(OptionsResolver $resolver) return $choiceListFactory->createListFromChoices($choices, $options['choice_value']); }; + $choicesAsValuesNormalizer = function (Options $options, $choicesAsValues) { + if (true !== $choicesAsValues) { + @trigger_error('The value "false" for the "choices_as_values" option is deprecated since version 2.8 and will not be supported anymore in 3.0. Set this option to "true" and flip the contents of the "choices" option instead.', E_USER_DEPRECATED); + } + + return $choicesAsValues; + }; + $placeholderNormalizer = function (Options $options, $placeholder) { if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) { @trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED); @@ -343,6 +351,7 @@ public function configureOptions(OptionsResolver $resolver) $resolver->setNormalizer('choice_list', $choiceListNormalizer); $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); + $resolver->setNormalizer('choices_as_values', $choicesAsValuesNormalizer); $resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface')); $resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable')); diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index 30ee0a0f9e89c..19395a82fe5af 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -23,7 +23,8 @@ class CountryType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getRegionBundle()->getCountryNames(), + 'choices' => array_flip(Intl::getRegionBundle()->getCountryNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index b473d139e6566..71b660f9bed63 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -23,7 +23,8 @@ class CurrencyType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getCurrencyBundle()->getCurrencyNames(), + 'choices' => array_flip(Intl::getCurrencyBundle()->getCurrencyNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index fb3e9a7182fc8..b1eb4382ed3ec 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -88,10 +88,13 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ('choice' === $options['widget']) { // Only pass a subset of the options to children $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])); + $yearOptions['choices_as_values'] = true; $yearOptions['placeholder'] = $options['placeholder']['year']; $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months'])); + $monthOptions['choices_as_values'] = true; $monthOptions['placeholder'] = $options['placeholder']['month']; $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])); + $dayOptions['choices_as_values'] = true; $dayOptions['placeholder'] = $options['placeholder']['day']; } @@ -262,6 +265,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ { $pattern = $formatter->getPattern(); $timezone = $formatter->getTimezoneId(); + $formattedTimestamps = array(); if ($setTimeZone = PHP_VERSION_ID >= 50500 || method_exists($formatter, 'setTimeZone')) { $formatter->setTimeZone('UTC'); @@ -272,8 +276,8 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ if (preg_match($regex, $pattern, $matches)) { $formatter->setPattern($matches[0]); - foreach ($timestamps as $key => $timestamp) { - $timestamps[$key] = $formatter->format($timestamp); + foreach ($timestamps as $timestamp => $choice) { + $formattedTimestamps[$formatter->format($timestamp)] = $choice; } // I'd like to clone the formatter above, but then we get a @@ -287,7 +291,7 @@ private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $ $formatter->setTimeZoneId($timezone); } - return $timestamps; + return $formattedTimestamps; } private function listYears(array $years) @@ -296,7 +300,7 @@ private function listYears(array $years) foreach ($years as $year) { if (false !== $y = gmmktime(0, 0, 0, 6, 15, $year)) { - $result[$year] = $y; + $result[$y] = $year; } } @@ -308,7 +312,7 @@ private function listMonths(array $months) $result = array(); foreach ($months as $month) { - $result[$month] = gmmktime(0, 0, 0, $month, 15); + $result[gmmktime(0, 0, 0, $month, 15)] = $month; } return $result; @@ -319,7 +323,7 @@ private function listDays(array $days) $result = array(); foreach ($days as $day) { - $result[$day] = gmmktime(0, 0, 0, 5, $day); + $result[gmmktime(0, 0, 0, 5, $day)] = $day; } return $result; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php index 9d071eb8b03ee..1fc0ed1b676f7 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -23,7 +23,8 @@ class LanguageType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLanguageBundle()->getLanguageNames(), + 'choices' => array_flip(Intl::getLanguageBundle()->getLanguageNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index f09f5a62f1e29..1631dc431ad7b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -23,7 +23,8 @@ class LocaleType extends AbstractType public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => Intl::getLocaleBundle()->getLocaleNames(), + 'choices' => array_flip(Intl::getLocaleBundle()->getLocaleNames()), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index 8002f0b4ee7c1..57c9a44c8f7e5 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -58,19 +58,21 @@ public function buildForm(FormBuilderInterface $builder, array $options) $hours = $minutes = array(); foreach ($options['hours'] as $hour) { - $hours[$hour] = str_pad($hour, 2, '0', STR_PAD_LEFT); + $hours[str_pad($hour, 2, '0', STR_PAD_LEFT)] = $hour; } // Only pass a subset of the options to children $hourOptions['choices'] = $hours; + $hourOptions['choices_as_values'] = true; $hourOptions['placeholder'] = $options['placeholder']['hour']; if ($options['with_minutes']) { foreach ($options['minutes'] as $minute) { - $minutes[$minute] = str_pad($minute, 2, '0', STR_PAD_LEFT); + $minutes[str_pad($minute, 2, '0', STR_PAD_LEFT)] = $minute; } $minuteOptions['choices'] = $minutes; + $minuteOptions['choices_as_values'] = true; $minuteOptions['placeholder'] = $options['placeholder']['minute']; } @@ -78,10 +80,11 @@ public function buildForm(FormBuilderInterface $builder, array $options) $seconds = array(); foreach ($options['seconds'] as $second) { - $seconds[$second] = str_pad($second, 2, '0', STR_PAD_LEFT); + $seconds[str_pad($second, 2, '0', STR_PAD_LEFT)] = $second; } $secondOptions['choices'] = $seconds; + $secondOptions['choices_as_values'] = true; $secondOptions['placeholder'] = $options['placeholder']['second']; } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 82c07e2f121ba..13c27f9da8c7f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -23,13 +23,21 @@ class TimezoneType extends AbstractType */ private static $timezones; + /** + * Stores the available timezone choices. + * + * @var array + */ + private static $flippedTimezones; + /** * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choices' => self::getTimezones(), + 'choices' => self::getFlippedTimezones(), + 'choices_as_values' => true, 'choice_translation_domain' => false, )); } @@ -85,4 +93,40 @@ public static function getTimezones() return static::$timezones; } + + /** + * Returns the timezone choices. + * + * The choices are generated from the ICU function + * \DateTimeZone::listIdentifiers(). They are cached during a single request, + * so multiple timezone fields on the same page don't lead to unnecessary + * overhead. + * + * @return array The timezone choices + */ + private static function getFlippedTimezones() + { + if (null === self::$timezones) { + self::$timezones = array(); + + foreach (\DateTimeZone::listIdentifiers() as $timezone) { + $parts = explode('/', $timezone); + + if (count($parts) > 2) { + $region = $parts[0]; + $name = $parts[1].' - '.$parts[2]; + } elseif (count($parts) > 1) { + $region = $parts[0]; + $name = $parts[1]; + } else { + $region = 'Other'; + $name = $parts[0]; + } + + self::$timezones[$region][str_replace('_', ' ', $name)] = $timezone; + } + } + + return self::$timezones; + } } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php index 50d4df8a9b7cb..03cb7fce5705a 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -65,6 +65,40 @@ public function testCreateChoiceListWithValueCallback() $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); } + public function testCreateChoiceListWithoutValueCallbackAndDuplicateFreeToStringChoices() + { + $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 123)); + + $this->assertSame(array('foo', 'bar', '123'), $choiceList->getValues()); + $this->assertSame(array('foo' => 'foo', 'bar' => 'bar', '123' => 123), $choiceList->getChoices()); + $this->assertSame(array('foo' => 2, 'bar' => 7, '123' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => 123), $choiceList->getChoicesForValues(array(1 => 'foo', 2 => '123'))); + $this->assertSame(array(1 => 'foo', 2 => '123'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 123))); + } + + public function testCreateChoiceListWithoutValueCallbackAndToStringDuplicates() + { + $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => '123', 10 => 123)); + + $this->assertSame(array('0', '1', '2'), $choiceList->getValues()); + $this->assertSame(array('0' => 'foo', '1' => '123', '2' => 123), $choiceList->getChoices()); + $this->assertSame(array('0' => 2, '1' => 7, '2' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => 123), $choiceList->getChoicesForValues(array(1 => '0', 2 => '2'))); + $this->assertSame(array(1 => '0', 2 => '2'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 123))); + } + + public function testCreateChoiceListWithoutValueCallbackAndMixedChoices() + { + $object = new \stdClass(); + $choiceList = new ArrayChoiceList(array(2 => 'foo', 5 => array(7 => '123'), 10 => $object)); + + $this->assertSame(array('0', '1', '2'), $choiceList->getValues()); + $this->assertSame(array('0' => 'foo', '1' => '123', '2' => $object), $choiceList->getChoices()); + $this->assertSame(array('0' => 2, '1' => 7, '2' => 10), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'foo', 2 => $object), $choiceList->getChoicesForValues(array(1 => '0', 2 => '2'))); + $this->assertSame(array(1 => '0', 2 => '2'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => $object))); + } + public function testCreateChoiceListWithGroupedChoices() { $choiceList = new ArrayChoiceList(array( @@ -72,15 +106,15 @@ public function testCreateChoiceListWithGroupedChoices() 'Group 2' => array('C' => 'c', 'D' => 'd'), )); - $this->assertSame(array('0', '1', '2', '3'), $choiceList->getValues()); + $this->assertSame(array('a', 'b', 'c', 'd'), $choiceList->getValues()); $this->assertSame(array( - 'Group 1' => array('A' => '0', 'B' => '1'), - 'Group 2' => array('C' => '2', 'D' => '3'), + 'Group 1' => array('A' => 'a', 'B' => 'b'), + 'Group 2' => array('C' => 'c', 'D' => 'd'), ), $choiceList->getStructuredValues()); - $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $choiceList->getChoices()); - $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $choiceList->getOriginalKeys()); - $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => '0', 2 => '1'))); - $this->assertSame(array(1 => '0', 2 => '1'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); + $this->assertSame(array('a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => 'd'), $choiceList->getChoices()); + $this->assertSame(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => 'a', 2 => 'b'))); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); } public function testCompareChoicesByIdentityByDefault() diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php index c58d072f47434..f60ef05abc507 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php @@ -20,7 +20,7 @@ class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $list = new ArrayChoiceList(array('', 0, 'X')); + $list = new ArrayChoiceList(array('', false, 'X')); $this->transformer = new ChoiceToValueTransformer($list); } @@ -35,7 +35,7 @@ public function transformProvider() return array( // more extensive test set can be found in FormUtilTest array('', '0'), - array(0, '1'), + array(false, '1'), ); } @@ -53,7 +53,7 @@ public function reverseTransformProvider() // values are expected to be valid choice keys already and stay // the same array('0', ''), - array('1', 0), + array('1', false), array('2', 'X'), ); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php index a7dc40aca225f..f7747aaccd0f1 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php @@ -20,7 +20,7 @@ class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $list = new ArrayChoiceList(array('A', 'B', 'C')); + $list = new ArrayChoiceList(array('', false, 'X')); $this->transformer = new ChoicesToValuesTransformer($list); } @@ -31,7 +31,7 @@ protected function tearDown() public function testTransform() { - $in = array('A', 'B', 'C'); + $in = array('', false, 'X'); $out = array('0', '1', '2'); $this->assertSame($out, $this->transformer->transform($in)); @@ -54,7 +54,7 @@ public function testReverseTransform() { // values are expected to be valid choices and stay the same $in = array('0', '1', '2'); - $out = array('A', 'B', 'C'); + $out = array('', false, 'X'); $this->assertSame($out, $this->transformer->reverseTransform($in)); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 215ddac936a54..523e9ecb78ad6 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -18,14 +18,14 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase { private $choices = array( - 'a' => 'Bernhard', - 'b' => 'Fabien', - 'c' => 'Kris', - 'd' => 'Jon', - 'e' => 'Roman', + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + 'Jon' => 'd', + 'Roman' => 'e', ); - private $numericChoices = array( + private $numericChoicesFlipped = array( 0 => 'Bernhard', 1 => 'Fabien', 2 => 'Kris', @@ -36,6 +36,18 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase private $objectChoices; protected $groupedChoices = array( + 'Symfony' => array( + 'Bernhard' => 'a', + 'Fabien' => 'b', + 'Kris' => 'c', + ), + 'Doctrine' => array( + 'Jon' => 'd', + 'Roman' => 'e', + ), + ); + + protected $groupedChoicesFlipped = array( 'Symfony' => array( 'a' => 'Bernhard', 'b' => 'Fabien', @@ -99,7 +111,9 @@ public function testChoiceLoaderOptionExpectsChoiceLoaderInterface() public function testChoiceListAndChoicesCanBeEmpty() { - $this->factory->create('choice'); + $this->factory->create('choice', null, array( + 'choices_as_values' => true, + )); } public function testExpandedChoicesOptionsTurnIntoChildren() @@ -107,6 +121,20 @@ public function testExpandedChoicesOptionsTurnIntoChildren() $form = $this->factory->create('choice', null, array( 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, + )); + + $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); + } + + /** + * @group legacy + */ + public function testExpandedFlippedChoicesOptionsTurnIntoChildren() + { + $form = $this->factory->create('choice', null, array( + 'expanded' => true, + 'choices' => array_flip($this->choices), )); $this->assertCount(count($this->choices), $form, 'Each choice should become a new field'); @@ -119,6 +147,7 @@ public function testPlaceholderPresentOnNonRequiredExpandedSingleChoice() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertTrue(isset($form['placeholder'])); @@ -132,6 +161,7 @@ public function testPlaceholderNotPresentIfRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -145,6 +175,7 @@ public function testPlaceholderNotPresentIfMultiple() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -158,9 +189,10 @@ public function testPlaceholderNotPresentIfEmptyChoice() 'expanded' => true, 'required' => false, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), + 'choices_as_values' => true, )); $this->assertFalse(isset($form['placeholder'])); @@ -172,6 +204,29 @@ public function testExpandedChoicesOptionsAreFlattened() $form = $this->factory->create('choice', null, array( 'expanded' => true, 'choices' => $this->groupedChoices, + 'choices_as_values' => true, + )); + + $flattened = array(); + foreach ($this->groupedChoices as $choices) { + $flattened = array_merge($flattened, array_keys($choices)); + } + + $this->assertCount($form->count(), $flattened, 'Each nested choice should become a new field, not the groups'); + + foreach ($flattened as $value => $choice) { + $this->assertTrue($form->has($value), 'Flattened choice is named after it\'s value'); + } + } + + /** + * @group legacy + */ + public function testExpandedChoicesFlippedOptionsAreFlattened() + { + $form = $this->factory->create('choice', null, array( + 'expanded' => true, + 'choices' => $this->groupedChoicesFlipped, )); $flattened = array(); @@ -219,6 +274,7 @@ public function testExpandedCheckboxesAreNeverRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -233,6 +289,7 @@ public function testExpandedRadiosAreRequiredIfChoiceChildIsRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -247,6 +304,7 @@ public function testExpandedRadiosAreNotRequiredIfChoiceChildIsNotRequired() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); foreach ($form as $child) { @@ -260,6 +318,7 @@ public function testSubmitSingleNonExpanded() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -275,6 +334,7 @@ public function testSubmitSingleNonExpandedInvalidChoice() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -290,6 +350,7 @@ public function testSubmitSingleNonExpandedNull() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -308,6 +369,7 @@ public function testSubmitSingleNonExpandedNullNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -323,6 +385,7 @@ public function testSubmitSingleNonExpandedEmpty() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -338,8 +401,9 @@ public function testSubmitSingleNonExpandedEmptyExplicitEmptyChoice() 'multiple' => false, 'expanded' => false, 'choices' => array( - 'EMPTY_CHOICE' => 'Empty', + 'Empty' => 'EMPTY_CHOICE', ), + 'choices_as_values' => true, 'choice_value' => function () { return ''; }, @@ -361,6 +425,7 @@ public function testSubmitSingleNonExpandedEmptyNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -376,6 +441,7 @@ public function testSubmitSingleNonExpandedFalse() 'multiple' => false, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -394,6 +460,7 @@ public function testSubmitSingleNonExpandedFalseNoChoices() 'multiple' => false, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -455,6 +522,7 @@ public function testSubmitMultipleNonExpanded() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'b')); @@ -470,6 +538,7 @@ public function testSubmitMultipleNonExpandedEmpty() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array()); @@ -488,6 +557,7 @@ public function testSubmitMultipleNonExpandedEmptyNoChoices() 'multiple' => true, 'expanded' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(array()); @@ -503,6 +573,7 @@ public function testSubmitMultipleNonExpandedInvalidScalarChoice() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -518,6 +589,7 @@ public function testSubmitMultipleNonExpandedInvalidArrayChoice() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'foobar')); @@ -578,6 +650,7 @@ public function testSubmitSingleExpandedRequired() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -606,6 +679,7 @@ public function testSubmitSingleExpandedRequiredInvalidChoice() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -634,6 +708,7 @@ public function testSubmitSingleExpandedNonRequired() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('b'); @@ -664,6 +739,7 @@ public function testSubmitSingleExpandedNonRequiredInvalidChoice() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -692,6 +768,7 @@ public function testSubmitSingleExpandedRequiredNull() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -723,6 +800,7 @@ public function testSubmitSingleExpandedRequiredNullNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -740,6 +818,7 @@ public function testSubmitSingleExpandedRequiredEmpty() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -771,6 +850,7 @@ public function testSubmitSingleExpandedRequiredEmptyNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -788,6 +868,7 @@ public function testSubmitSingleExpandedRequiredFalse() 'expanded' => true, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -819,6 +900,7 @@ public function testSubmitSingleExpandedRequiredFalseNoChoices() 'expanded' => true, 'required' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -836,6 +918,7 @@ public function testSubmitSingleExpandedNonRequiredNull() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(null); @@ -869,6 +952,7 @@ public function testSubmitSingleExpandedNonRequiredNullNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(null); @@ -886,6 +970,7 @@ public function testSubmitSingleExpandedNonRequiredEmpty() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(''); @@ -919,6 +1004,7 @@ public function testSubmitSingleExpandedNonRequiredEmptyNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(''); @@ -936,6 +1022,7 @@ public function testSubmitSingleExpandedNonRequiredFalse() 'expanded' => true, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(false); @@ -969,6 +1056,7 @@ public function testSubmitSingleExpandedNonRequiredFalseNoChoices() 'expanded' => true, 'required' => false, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(false); @@ -985,9 +1073,10 @@ public function testSubmitSingleExpandedWithEmptyChild() 'multiple' => false, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not empty', + 'Empty' => '', + 'Not empty' => 1, ), + 'choices_as_values' => true, )); $form->submit(''); @@ -1065,12 +1154,15 @@ public function testLegacySubmitSingleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } - public function testSubmitSingleExpandedNumericChoices() + /** + * @group legacy + */ + public function testSubmitSingleExpandedNumericChoicesFlipped() { $form = $this->factory->create('choice', null, array( 'multiple' => false, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->submit('1'); @@ -1096,6 +1188,7 @@ public function testSubmitMultipleExpanded() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'c')); @@ -1123,6 +1216,7 @@ public function testSubmitMultipleExpandedInvalidScalarChoice() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit('foobar'); @@ -1150,6 +1244,7 @@ public function testSubmitMultipleExpandedInvalidArrayChoice() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array('a', 'foobar')); @@ -1177,6 +1272,7 @@ public function testSubmitMultipleExpandedEmpty() 'multiple' => true, 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $form->submit(array()); @@ -1205,6 +1301,7 @@ public function testSubmitMultipleExpandedEmptyNoChoices() 'multiple' => true, 'expanded' => true, 'choices' => array(), + 'choices_as_values' => true, )); $form->submit(array()); @@ -1219,10 +1316,11 @@ public function testSubmitMultipleExpandedWithEmptyChild() 'multiple' => true, 'expanded' => true, 'choices' => array( - '' => 'Empty', - 1 => 'Not Empty', - 2 => 'Not Empty 2', + 'Empty' => '', + 'Not Empty' => 1, + 'Not Empty 2' => 2, ), + 'choices_as_values' => true, )); $form->submit(array('', '2')); @@ -1302,12 +1400,15 @@ public function testLegacySubmitMultipleExpandedObjectChoices() $this->assertNull($form[4]->getViewData()); } + /** + * @group legacy + */ public function testSubmitMultipleExpandedNumericChoices() { $form = $this->factory->create('choice', null, array( 'multiple' => true, 'expanded' => true, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->submit(array('1', '2')); @@ -1363,16 +1464,18 @@ public function testMultipleSelectedObjectChoices() $this->assertFalse($selectedChecker($view->vars['choices'][1]->value, $view->vars['value'])); } - /* + /** * We need this functionality to create choice fields for Boolean types, - * e.g. false => 'No', true => 'Yes' + * e.g. false => 'No', true => 'Yes'. + * + * @group legacy */ public function testSetDataSingleNonExpandedAcceptsBoolean() { $form = $this->factory->create('choice', null, array( 'multiple' => false, 'expanded' => false, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->setData(false); @@ -1382,12 +1485,15 @@ public function testSetDataSingleNonExpandedAcceptsBoolean() $this->assertTrue($form->isSynchronized()); } + /** + * @group legacy + */ public function testSetDataMultipleNonExpandedAcceptsBoolean() { $form = $this->factory->create('choice', null, array( 'multiple' => true, 'expanded' => false, - 'choices' => $this->numericChoices, + 'choices' => $this->numericChoicesFlipped, )); $form->setData(array(false, true)); @@ -1401,6 +1507,7 @@ public function testPassRequiredToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1412,6 +1519,7 @@ public function testPassNonRequiredToView() $form = $this->factory->create('choice', null, array( 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1423,6 +1531,7 @@ public function testPassMultipleToView() $form = $this->factory->create('choice', null, array( 'multiple' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1434,6 +1543,7 @@ public function testPassExpandedToView() $form = $this->factory->create('choice', null, array( 'expanded' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1444,6 +1554,7 @@ public function testPassChoiceTranslationDomainToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1454,6 +1565,7 @@ public function testChoiceTranslationDomainWithTrueValueToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, 'choice_translation_domain' => true, )); $view = $form->createView(); @@ -1465,6 +1577,7 @@ public function testDefaultChoiceTranslationDomainIsSameAsTranslationDomainToVie { $form = $this->factory->create('choice', null, array( 'choices' => $this->choices, + 'choices_as_values' => true, 'translation_domain' => 'foo', )); $view = $form->createView(); @@ -1478,7 +1591,10 @@ public function testInheritChoiceTranslationDomainFromParent() ->createNamedBuilder('parent', 'form', null, array( 'translation_domain' => 'domain', )) - ->add('child', 'choice') + ->add('child', 'choice', array( + 'choices' => array(), + 'choices_as_values' => true, + )) ->getForm() ->createView(); @@ -1491,6 +1607,7 @@ public function testPlaceholderIsNullByDefaultIfRequired() 'multiple' => false, 'required' => true, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1503,6 +1620,7 @@ public function testPlaceholderIsEmptyStringByDefaultIfNotRequired() 'multiple' => false, 'required' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1520,6 +1638,7 @@ public function testPassPlaceholderToView($multiple, $expanded, $required, $plac 'required' => $required, 'placeholder' => $placeholder, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1539,6 +1658,7 @@ public function testPassEmptyValueBC($multiple, $expanded, $required, $placehold 'required' => $required, 'empty_value' => $placeholder, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1558,7 +1678,8 @@ public function testDontPassPlaceholderIfContainedInChoices($multiple, $expanded 'expanded' => $expanded, 'required' => $required, 'placeholder' => $placeholder, - 'choices' => array('a' => 'A', '' => 'Empty'), + 'choices' => array('A' => 'a', 'Empty' => ''), + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1612,9 +1733,10 @@ public function getOptionsWithPlaceholder() public function testPassChoicesToView() { - $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'); + $choices = array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd'); $form = $this->factory->create('choice', null, array( 'choices' => $choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1628,9 +1750,10 @@ public function testPassChoicesToView() public function testPassPreferredChoicesToView() { - $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'); + $choices = array('A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd'); $form = $this->factory->create('choice', null, array( 'choices' => $choices, + 'choices_as_values' => true, 'preferred_choices' => array('b', 'd'), )); $view = $form->createView(); @@ -1649,6 +1772,7 @@ public function testPassHierarchicalChoicesToView() { $form = $this->factory->create('choice', null, array( 'choices' => $this->groupedChoices, + 'choices_as_values' => true, 'preferred_choices' => array('b', 'd'), )); $view = $form->createView(); @@ -1700,6 +1824,7 @@ public function testAdjustFullNameForMultipleNonExpanded() 'multiple' => true, 'expanded' => false, 'choices' => $this->choices, + 'choices_as_values' => true, )); $view = $form->createView(); @@ -1711,6 +1836,7 @@ public function testInitializeWithEmptyChoices() { $this->factory->createNamed('name', 'choice', null, array( 'choices' => array(), + 'choices_as_values' => true, )); }