From 02b1768a7c270ae9806eaf48deab0e770df8b151 Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Sun, 21 Jun 2015 12:06:54 +0200 Subject: [PATCH] [Form] Add option widget to ChoiceType --- .../views/Form/form_div_layout.html.twig | 6 +- .../ValuesToStringTransformer.php | 89 +++++++++++++++++++ .../Form/Extension/Core/Type/ChoiceType.php | 41 +++++++-- .../ValuesToStringTransformerTest.php | 71 +++++++++++++++ .../Extension/Core/Type/ChoiceTypeTest.php | 64 +++++++++++++ 5 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ValuesToStringTransformerTest.php diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 81b6dcee9135d..e44fc28c04b52 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -35,7 +35,11 @@ {%- endblock textarea_widget -%} {%- block choice_widget -%} - {% if expanded %} + {% if 'hidden' == widget %} + {{- block('hidden_widget') -}} + {% elseif 'text' == widget %} + {{- block('form_widget_simple') -}} + {% elseif expanded %} {{- block('choice_widget_expanded') -}} {% else %} {{- block('choice_widget_collapsed') -}} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php new file mode 100644 index 0000000000000..2a82748ca4801 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ValuesToStringTransformer.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Converts an array of values to a string with multiple values separated by a delimiter. + * + * @author Bilal Amarni + */ +class ValuesToStringTransformer implements DataTransformerInterface +{ + /** + * @var string + */ + private $delimiter; + + /** + * @var bool + */ + private $trim; + + /** + * @param string $delimiter + * @param bool $trim + */ + public function __construct($delimiter, $trim) + { + $this->delimiter = $delimiter; + $this->trim = $trim; + } + + /** + * @param array $array + * + * @return string + * + * @throws UnexpectedTypeException if the given value is not an array + */ + public function transform($array) + { + if (null === $array) { + return ''; + } + + if (!is_array($array)) { + throw new TransformationFailedException('Expected an array'); + } + + return implode($this->delimiter, $array); + } + + /** + * @param string $string + * + * @return array + * + * @throws UnexpectedTypeException if the given value is not a string + */ + public function reverseTransform($string) + { + if (empty($string)) { + return array(); + } + + if (!is_string($string)) { + throw new TransformationFailedException('Expected a string'); + } + + $values = explode($this->delimiter, $string); + + if ($this->trim) { + $values = array_map('trim', $values); + } + + return $values; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index d53c3d4106ec5..02caa97e7defb 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -32,6 +32,7 @@ use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\ValuesToStringTransformer; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -54,7 +55,7 @@ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null */ public function buildForm(FormBuilderInterface $builder, array $options) { - if ($options['expanded']) { + if ($options['expanded'] && !in_array($options['widget'], array('text', 'hidden'))) { $builder->setDataMapper($options['multiple'] ? new CheckboxListMapper($options['choice_list']) : new RadioListMapper($options['choice_list'])); @@ -140,10 +141,15 @@ public function buildForm(FormBuilderInterface $builder, array $options) }); } } elseif ($options['multiple']) { - // tag without "multiple" option + // "select", "text" or "hidden" tag without "multiple" option $builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list'])); } @@ -170,8 +176,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) : $this->createChoiceListView($options['choice_list'], $options); $view->vars = array_replace($view->vars, array( + 'widget' => $options['widget'], 'multiple' => $options['multiple'], - 'expanded' => $options['expanded'], + 'expanded' => $options['expanded'], // BC 'preferred_choices' => $choiceListView->preferredChoices, 'choices' => $choiceListView->choices, 'separator' => '-------------------', @@ -179,6 +186,10 @@ public function buildView(FormView $view, FormInterface $form, array $options) 'choice_translation_domain' => $choiceTranslationDomain, )); + if (in_array($options['widget'], array('text', 'hidden'))) { + return; + } + // The decision, whether a choice is selected, is potentially done // thousand of times during the rendering of a template. Provide a // closure here that is optimized for the value of the form, to @@ -218,6 +229,10 @@ public function buildView(FormView $view, FormInterface $form, array $options) */ public function finishView(FormView $view, FormInterface $form, array $options) { + if (in_array($options['widget'], array('text', 'hidden'))) { + return; + } + if ($options['expanded']) { // Radio buttons should have the same name as the parent $childName = $view->vars['full_name']; @@ -292,6 +307,18 @@ public function configureOptions(OptionsResolver $resolver) return; }; + $multipleNormalizer = function (Options $options, $multiple) { + if (in_array($options['widget'], array('radio', 'checkbox'))) { + return 'checkbox' == $options['widget']; + } + + return $multiple; + }; + + $expandedNomalizer = function (Options $options, $expanded) { + return in_array($options['widget'], array('radio', 'checkbox')) ?: $expanded; + }; + $choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) { if ($choiceList) { @trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED); @@ -364,8 +391,10 @@ public function configureOptions(OptionsResolver $resolver) }; $resolver->setDefaults(array( + 'widget' => null, 'multiple' => false, - 'expanded' => false, + 'delimiter' => ',', + 'expanded' => false, // deprecated 'choice_list' => null, // deprecated 'choices' => array(), 'choices_as_values' => false, @@ -388,6 +417,8 @@ public function configureOptions(OptionsResolver $resolver) 'choice_translation_domain' => true, )); + $resolver->setNormalizer('expanded', $expandedNomalizer); + $resolver->setNormalizer('multiple', $multipleNormalizer); $resolver->setNormalizer('choices', $choicesNormalizer); $resolver->setNormalizer('choice_list', $choiceListNormalizer); $resolver->setNormalizer('placeholder', $placeholderNormalizer); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ValuesToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ValuesToStringTransformerTest.php new file mode 100644 index 0000000000000..6105b1e47add1 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/ValuesToStringTransformerTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\ValuesToStringTransformer; + +class ValuesToStringTransformerTest extends \PHPUnit_Framework_TestCase +{ + private $transformer; + + protected function setUp() + { + $this->transformer = new ValuesToStringTransformer(',', true); + } + + protected function tearDown() + { + $this->transformer = null; + } + + public function testTransform() + { + $output = 'a,b,c'; + + $this->assertSame($output, $this->transformer->transform(array('a', 'b', 'c'))); + } + + public function testTransformNull() + { + $this->assertSame('', $this->transformer->transform(null)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformRequiresAnArray() + { + $this->transformer->transform('a, b, c'); + } + + public function testReverseTransform() + { + $input = 'a, b ,c '; + + $this->assertSame(array('a', 'b', 'c'), $this->transformer->reverseTransform($input)); + } + + public function testReverseTransformEmpty() + { + $input = ''; + + $this->assertSame(array(), $this->transformer->reverseTransform($input)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformRequiresAString() + { + $this->transformer->reverseTransform(array('a', 'b', 'c')); + } +} 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 976cbc838b077..cb4198a68fe05 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -1887,4 +1887,68 @@ public function testInitializeWithDefaultObjectChoice() // Trigger data initialization $form->getViewData(); } + + /** + * @dataProvider simpleWidgetsProvider + */ + public function testSubmitChoicesWithSimpleWidgets($widget) + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( + 'widget' => $widget, + 'multiple' => false, + 'choices' => $this->choices, + 'choices_as_values' => true, + )); + + $form->submit('b'); + + $this->assertEquals('b', $form->getData()); + $this->assertEquals('b', $form->getViewData()); + } + + /** + * @dataProvider simpleWidgetsProvider + */ + public function testSubmitMultipleChoicesWithSimpleWidgets($widget) + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( + 'widget' => $widget, + 'multiple' => true, + 'choices' => $this->choices, + 'choices_as_values' => true, + )); + + $form->submit('a,b'); + + $this->assertEquals(array('a', 'b'), $form->getData()); + $this->assertEquals('a,b', $form->getViewData()); + } + + /** + * @dataProvider simpleWidgetsProvider + */ + public function testSubmitMultipleChoicesDelimiterAndTrimWithSimpleWidgets($widget) + { + $form = $this->factory->create('Symfony\Component\Form\Extension\Core\Type\ChoiceType', null, array( + 'widget' => $widget, + 'multiple' => true, + 'delimiter' => '|', + 'trim' => true, + 'choices' => $this->choices, + 'choices_as_values' => true, + )); + + $form->submit('a| b '); + + $this->assertEquals(array('a', 'b'), $form->getData()); + $this->assertEquals('a|b', $form->getViewData()); + } + + public function simpleWidgetsProvider() + { + return array( + array('text'), + array('hidden'), + ); + } }