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 211eee5dd784f..f6e298f4ea47d 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
@@ -96,7 +96,8 @@
{{ block('choice_widget_options') }}
{% else %}
-
+ {% set attr = choice.attributes %}
+
{% endif %}
{% endfor %}
{% endspaceless %}
@@ -421,3 +422,16 @@
{%- endfor -%}
{% endspaceless %}
{% endblock button_attributes %}
+
+{% block option_attributes %}
+{% spaceless %}
+ {%- for attrname, attrvalue in attr -%}
+ {{- " " -}}
+ {% if attrvalue is sameas(true) -%}
+ {{- attrname }}="{{ attrname }}"
+ {%- elseif attrvalue is not sameas(false) -%}
+ {{- attrname }}="{{ attrvalue }}"
+ {%- endif -%}
+ {%- endfor -%}
+{% endspaceless %}
+{% endblock option_attributes %}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php
index a7a9311d51326..65d8bddde62c3 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php
@@ -6,6 +6,6 @@
block($form, 'choice_widget_options', array('choices' => $choice)) ?>
-
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/option_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/option_attributes.html.php
new file mode 100644
index 0000000000000..e0ddedf0bf2e8
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/option_attributes.html.php
@@ -0,0 +1,7 @@
+ $v): ?>
+
+escape($k), $view->escape($k)) ?>
+
+escape($k), $view->escape($v)) ?>
+
+
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
index b8966967e3965..2ba96d6a45f83 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
@@ -69,26 +69,27 @@ class ChoiceList implements ChoiceListInterface
/**
* Creates a new choice list.
*
- * @param array|\Traversable $choices The array of choices. Choices may also be given
- * as hierarchy of unlimited depth. Hierarchies are
- * created by creating nested arrays. The title of
- * the sub-hierarchy can be stored in the array
- * key pointing to the nested array. The topmost
- * level of the hierarchy may also be a \Traversable.
- * @param array $labels The array of labels. The structure of this array
- * should match the structure of $choices.
- * @param array $preferredChoices A flat array of choices that should be
- * presented to the user with priority.
+ * @param array|\Traversable $choices The array of choices. Choices may also be given
+ * as hierarchy of unlimited depth. Hierarchies are
+ * created by creating nested arrays. The title of
+ * the sub-hierarchy can be stored in the array
+ * key pointing to the nested array. The topmost
+ * level of the hierarchy may also be a \Traversable.
+ * @param array $labels The array of labels. The structure of this array
+ * should match the structure of $choices.
+ * @param array $preferredChoices A flat array of choices that should be presented to the user with priority.
+ * @param array $attributes A multidimensionnal array of attributes. The structure of this
+ * array should match the structure of $choices.
*
* @throws UnexpectedTypeException If the choices are not an array or \Traversable.
*/
- public function __construct($choices, array $labels, array $preferredChoices = array())
+ public function __construct($choices, array $labels, array $preferredChoices = array(), array $attributes = array())
{
if (!is_array($choices) && !$choices instanceof \Traversable) {
throw new UnexpectedTypeException($choices, 'array or \Traversable');
}
- $this->initialize($choices, $labels, $preferredChoices);
+ $this->initialize($choices, $labels, $preferredChoices, $attributes);
}
/**
@@ -99,8 +100,9 @@ public function __construct($choices, array $labels, array $preferredChoices = a
* @param array|\Traversable $choices The choices to write into the list.
* @param array $labels The labels belonging to the choices.
* @param array $preferredChoices The choices to display with priority.
+ * @param array $attributes The choices' attributes.
*/
- protected function initialize($choices, array $labels, array $preferredChoices)
+ protected function initialize($choices, array $labels, array $preferredChoices, array $attributes = array())
{
$this->choices = array();
$this->values = array();
@@ -112,7 +114,8 @@ protected function initialize($choices, array $labels, array $preferredChoices)
$this->remainingViews,
$choices,
$labels,
- $preferredChoices
+ $preferredChoices,
+ $attributes
);
}
@@ -258,11 +261,12 @@ public function getIndicesForValues(array $values)
* @param array|\Traversable $choices The list of choices.
* @param array $labels The labels corresponding to the choices.
* @param array $preferredChoices The preferred choices.
+ * @param array $attributes The choices' attributes.
*
* @throws InvalidArgumentException If the structures of the choices and labels array do not match.
* @throws InvalidConfigurationException If no valid value or index could be created for a choice.
*/
- protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices)
+ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices, array $attributes = array())
{
// Add choices to the nested buckets
foreach ($choices as $group => $choice) {
@@ -270,6 +274,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai
throw new InvalidArgumentException('The structures of the choices and labels array do not match.');
}
+ $attribute = isset($attributes[$group]) ? $attributes[$group] : array();
+
if (is_array($choice)) {
// Don't do the work if the array is empty
if (count($choice) > 0) {
@@ -279,7 +285,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai
$bucketForRemaining,
$choice,
$labels[$group],
- $preferredChoices
+ $preferredChoices,
+ $attribute
);
}
} else {
@@ -288,7 +295,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai
$bucketForRemaining,
$choice,
$labels[$group],
- $preferredChoices
+ $preferredChoices,
+ $attribute
);
}
}
@@ -305,10 +313,11 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai
* @param array $choices The list of choices in the group.
* @param array $labels The labels corresponding to the choices in the group.
* @param array $preferredChoices The preferred choices.
+ * @param array $attributes The choices' attributes.
*
* @throws InvalidConfigurationException If no valid value or index could be created for a choice.
*/
- protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bucketForRemaining, array $choices, array $labels, array $preferredChoices)
+ protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bucketForRemaining, array $choices, array $labels, array $preferredChoices, array $attributes = array())
{
// If this is a choice group, create a new level in the choice
// key hierarchy
@@ -320,7 +329,8 @@ protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bu
$bucketForRemaining[$group],
$choices,
$labels,
- $preferredChoices
+ $preferredChoices,
+ $attributes
);
// Remove child levels if empty
@@ -342,10 +352,11 @@ protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bu
* @param mixed $choice The choice to add.
* @param string $label The label for the choice.
* @param array $preferredChoices The preferred choices.
+ * @param array $attributes The choices' attributes.
*
* @throws InvalidConfigurationException If no valid value or index could be created.
*/
- protected function addChoice(array &$bucketForPreferred, array &$bucketForRemaining, $choice, $label, array $preferredChoices)
+ protected function addChoice(array &$bucketForPreferred, array &$bucketForRemaining, $choice, $label, array $preferredChoices, array $attributes = array())
{
$index = $this->createIndex($choice);
@@ -359,7 +370,7 @@ protected function addChoice(array &$bucketForPreferred, array &$bucketForRemain
throw new InvalidConfigurationException(sprintf('The value created by the choice list is of type "%s", but should be a string.', gettype($value)));
}
- $view = new ChoiceView($choice, $value, $label);
+ $view = new ChoiceView($choice, $value, $label, $attributes);
$this->choices[$index] = $this->fixChoice($choice);
$this->values[$index] = $value;
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
index 7adf3f2be479d..1dd4265cd6c63 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
@@ -64,35 +64,36 @@ class ObjectChoiceList extends ChoiceList
/**
* Creates a new object choice list.
*
- * @param array|\Traversable $choices The array of choices. Choices may also be given
+ * @param array|\Traversable $choices The array of choices. Choices may also be given
* as hierarchy of unlimited depth by creating nested
* arrays. The title of the sub-hierarchy can be
* stored in the array key pointing to the nested
* array. The topmost level of the hierarchy may also
* be a \Traversable.
- * @param string $labelPath A property path pointing to the property used
+ * @param string $labelPath A property path pointing to the property used
* for the choice labels. The value is obtained
- * by calling the getter on the object. If the
+ * by calling the getter on the object. If the
* path is NULL, the object's __toString() method
* is used instead.
- * @param array $preferredChoices A flat array of choices that should be
+ * @param array $preferredChoices A flat array of choices that should be
* presented to the user with priority.
- * @param string $groupPath A property path pointing to the property used
+ * @param string $groupPath A property path pointing to the property used
* to group the choices. Only allowed if
* the choices are given as flat array.
- * @param string $valuePath A property path pointing to the property used
+ * @param string $valuePath A property path pointing to the property used
* for the choice values. If not given, integers
* are generated instead.
* @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths.
+ * @param array $attributes The choices' attributes.
*/
- public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null)
+ public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null, array $attributes = array())
{
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->labelPath = null !== $labelPath ? new PropertyPath($labelPath) : null;
$this->groupPath = null !== $groupPath ? new PropertyPath($groupPath) : null;
$this->valuePath = null !== $valuePath ? new PropertyPath($valuePath) : null;
- parent::__construct($choices, array(), $preferredChoices);
+ parent::__construct($choices, array(), $preferredChoices, $attributes);
}
/**
@@ -103,11 +104,12 @@ public function __construct($choices, $labelPath = null, array $preferredChoices
* @param array|\Traversable $choices The choices to write into the list.
* @param array $labels Ignored.
* @param array $preferredChoices The choices to display with priority.
+ * @param array $attributes The choices' attributes.
*
* @throws InvalidArgumentException When passing a hierarchy of choices and using
* the "groupPath" option at the same time.
*/
- protected function initialize($choices, array $labels, array $preferredChoices)
+ protected function initialize($choices, array $labels, array $preferredChoices, array $attributes = array())
{
if (null !== $this->groupPath) {
$groupedChoices = array();
@@ -145,7 +147,7 @@ protected function initialize($choices, array $labels, array $preferredChoices)
$this->extractLabels($choices, $labels);
- parent::initialize($choices, $labels, $preferredChoices);
+ parent::initialize($choices, $labels, $preferredChoices, $attributes);
}
/**
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
index abc1bc43fed15..c48ef85f34a5e 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
@@ -34,18 +34,19 @@ class SimpleChoiceList extends ChoiceList
/**
* Creates a new simple choice list.
*
- * @param array $choices The array of choices with the choices as keys and
- * the labels as values. Choices may also be given
- * as hierarchy of unlimited depth by creating nested
- * arrays. The title of the sub-hierarchy is stored
- * in the array key pointing to the nested array.
+ * @param array $choices The array of choices with the choices as keys and
+ * the labels as values. Choices may also be given
+ * as hierarchy of unlimited depth by creating nested
+ * arrays. The title of the sub-hierarchy is stored
+ * in the array key pointing to the nested array.
* @param array $preferredChoices A flat array of choices that should be
* presented to the user with priority.
+ * @param array $attributes The choices' attributes having the same structure than $choices.
*/
- public function __construct(array $choices, array $preferredChoices = array())
+ public function __construct(array $choices, array $preferredChoices = array(), array $attributes = array())
{
// Flip preferred choices to speed up lookup
- parent::__construct($choices, $choices, array_flip($preferredChoices));
+ parent::__construct($choices, $choices, array_flip($preferredChoices), $attributes);
}
/**
@@ -85,11 +86,14 @@ public function getValuesForChoices(array $choices)
* @param array|\Traversable $choices The list of choices.
* @param array $labels Ignored.
* @param array $preferredChoices The preferred choices.
+ * @param array $attributes The choices' attributes.
*/
- protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices)
+ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices, array $attributes = array())
{
// Add choices to the nested buckets
foreach ($choices as $choice => $label) {
+ $attribute = isset($attributes[$choice]) ? $attributes[$choice] : array();
+
if (is_array($label)) {
// Don't do the work if the array is empty
if (count($label) > 0) {
@@ -99,7 +103,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai
$bucketForRemaining,
$label,
$label,
- $preferredChoices
+ $preferredChoices,
+ $attribute
);
}
} else {
@@ -108,7 +113,8 @@ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemai
$bucketForRemaining,
$choice,
$label,
- $preferredChoices
+ $preferredChoices,
+ $attribute
);
}
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 712f5b6de8fe1..4e113ff843001 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -12,6 +12,8 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Exception\InvalidConfigurationException;
+use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
@@ -45,6 +47,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
throw new LogicException('Either the option "choices" or "choice_list" must be set.');
}
+ if (!empty($options['choices_attr']) && empty($options['choices'])) {
+ throw new InvalidConfigurationException('The option "choices_attr" cannot be specified when "choice_list" is set.
+ Pass the choices attributes when creating the choice list instead.'
+ );
+ }
+
if ($options['expanded']) {
// Initialize all choices before doing the index check below.
// This helps in cases where index checks are optimized for non
@@ -169,7 +177,33 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
$hash = hash('sha256', serialize(array($choices, $options['preferred_choices'])));
if (!isset($choiceListCache[$hash])) {
- $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']);
+ $attributes = $options['choices_attr'];
+
+ // Execute the callable with the choice as parameter
+ if (is_callable($attributes)) {
+ $callable = $attributes;
+ $attributes = array();
+
+ foreach ($choices as $group => $choice) {
+ if (is_array($choice)) {
+ foreach ($choice as $key => $value) {
+ if (!is_array($result = call_user_func($callable, $value))) {
+ throw new RuntimeException('The callable for "choices_attr" must return an array.');
+ }
+
+ $attributes[$group][$key] = $result;
+ }
+ } else {
+ if (!is_array($result = call_user_func($callable, $choice))) {
+ throw new RuntimeException('The callable for "choices_attr" must return an array.');
+ }
+
+ $attributes[$group] = $result;
+ }
+ }
+ }
+
+ $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices'], $attributes);
}
return $choiceListCache[$hash];
@@ -221,6 +255,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
// is manually set to an object.
// See https://github.com/symfony/symfony/pull/5582
'data_class' => null,
+ 'choices_attr' => array(),
));
$resolver->setNormalizers(array(
@@ -229,6 +264,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
$resolver->setAllowedTypes(array(
'choice_list' => array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'),
+ 'choices_attr' => array('array', 'callable'),
));
}
@@ -257,6 +293,7 @@ private function addSubForms(FormBuilderInterface $builder, array $choiceViews,
$choiceOpts = array(
'value' => $choiceView->value,
'label' => $choiceView->label,
+ 'attr' => $choiceView->attributes,
'translation_domain' => $options['translation_domain'],
);
diff --git a/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php b/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php
index 97cdd214c28f8..ccef2cc9349a4 100644
--- a/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php
+++ b/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php
@@ -39,17 +39,26 @@ class ChoiceView
*/
public $label;
+ /**
+ * The choice's attributes.
+ *
+ * @var array
+ */
+ public $attributes;
+
/**
* Creates a new ChoiceView.
*
- * @param mixed $data The original choice.
- * @param string $value The view representation of the choice.
- * @param string $label The label displayed to humans.
+ * @param mixed $data The original choice.
+ * @param string $value The view representation of the choice.
+ * @param string $label The label displayed to humans.
+ * @param array $attributes The choice's attributes.
*/
- public function __construct($data, $value, $label)
+ public function __construct($data, $value, $label, array $attributes = array())
{
$this->data = $data;
$this->value = $value;
$this->label = $label;
+ $this->attributes = $attributes;
}
}
diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
index 532dc41be75f5..1878e131a96bd 100644
--- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
+++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Form\Tests;
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
@@ -1969,4 +1970,102 @@ public function testButtonAttributeHiddenIfFalse()
// no foo
$this->assertSame('', $html);
}
+
+ public function testWidgetChoiceAttributes()
+ {
+ $form = $this->factory->createNamed('choice', 'choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => array(
+ 'a' => array('disabled' => true, 'data-foo' => 'bar'),
+ 'c' => array('disabled' => true, 'data-bar' => 'baz'),
+ ),
+ ));
+
+ $html = $this->renderWidget($form->createView());
+
+ $this->assertMatchesXpath($html,
+ '/select
+ [@name="choice"]
+ [
+ ./option[@disabled][@data-foo="bar"][@value="a"]
+ /following-sibling::option[not(@disabled)][@value="b"]
+ /following-sibling::option[@disabled][@data-bar="baz"][@value="c"]
+ ]
+ '
+ );
+ }
+
+ public function testWidgetChoiceListAttributes()
+ {
+ $form = $this->factory->createNamed('choice', 'choice', null, array(
+ 'choice_list' => new SimpleChoiceList(array('a' => 'A', 'b' => 'B', 'c' => 'C'), array(), array(
+ 'a' => array('disabled' => true, 'data-foo' => 'bar'),
+ 'c' => array('disabled' => true, 'data-bar' => 'baz'),
+ ))
+ ));
+
+ $html = $this->renderWidget($form->createView());
+
+ $this->assertMatchesXpath($html,
+ '/select
+ [@name="choice"]
+ [
+ ./option[@disabled][@data-foo="bar"][@value="a"]
+ /following-sibling::option[not(@disabled)][@value="b"]
+ /following-sibling::option[@disabled][@data-bar="baz"][@value="c"]
+ ]
+ '
+ );
+ }
+
+ public function testWidgetRadioAttributesFromChoice()
+ {
+ $form = $this->factory->createNamed('choice', 'choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => array(
+ 'a' => array('disabled' => true, 'data-foo' => 'bar'),
+ 'c' => array('disabled' => true, 'data-bar' => 'baz'),
+ ),
+ 'expanded' => true,
+ ));
+
+ $html = $this->renderWidget($form->createView());
+
+ $this->assertMatchesXpath($html,
+ '/div
+ [@id="choice"]
+ [
+ ./input[@type="radio"][@disabled][@data-foo="bar"][@value="a"]
+ /following-sibling::input[@type="radio"][not(@disabled)][@value="b"]
+ /following-sibling::input[@type="radio"][@disabled][@data-bar="baz"][@value="c"]
+ ]
+ '
+ );
+ }
+
+ public function testWidgetCheckboxAttributesFromChoice()
+ {
+ $form = $this->factory->createNamed('choice', 'choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => array(
+ 'a' => array('disabled' => true, 'data-foo' => 'bar'),
+ 'c' => array('disabled' => true, 'data-bar' => 'baz'),
+ ),
+ 'expanded' => true,
+ 'multiple' => true,
+ ));
+
+ $html = $this->renderWidget($form->createView());
+
+ $this->assertMatchesXpath($html,
+ '/div
+ [@id="choice"]
+ [
+ ./input[@type="checkbox"][@disabled][@data-foo="bar"][@value="a"]
+ /following-sibling::input[@type="checkbox"][not(@disabled)][@value="b"]
+ /following-sibling::input[@type="checkbox"][@disabled][@data-bar="baz"][@value="c"]
+ ]
+ '
+ );
+ }
}
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php
index 59f002d8ccb00..b7cb5fbb8db8e 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ChoiceListTest.php
@@ -48,6 +48,25 @@ public function testInitArray()
$this->assertEquals(array(0 => new ChoiceView($this->obj1, '0', 'A'), 2 => new ChoiceView($this->obj3, '2', 'C'), 3 => new ChoiceView($this->obj4, '3', 'D')), $this->list->getRemainingViews());
}
+ public function testInitAttributes()
+ {
+ $list = new ChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
+ array('A', 'B', 'C', 'D'),
+ array(),
+ array(array('foo' => 'bar1'), array('foo' => 'bar2'), array('foo' => 'bar3'))
+ );
+
+ $this->assertEquals(array(
+ 0 => new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')),
+ 1 => new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2')),
+ 2 => new ChoiceView($this->obj3, '2', 'C', array('foo' => 'bar3')),
+ 3 => new ChoiceView($this->obj4, '3', 'D')
+ ),
+ $list->getRemainingViews()
+ );
+ }
+
/**
* Necessary for interoperability with MongoDB cursors or ORM relations as
* choices parameter. A choice itself that is an object implementing \Traversable
@@ -94,6 +113,38 @@ public function testInitNestedArray()
), $this->list->getRemainingViews());
}
+ public function testInitNestedAttributes()
+ {
+ $list = new ChoiceList(
+ array(
+ 'Group 1' => array($this->obj1, $this->obj2),
+ 'Group 2' => array($this->obj3, $this->obj4),
+ ),
+ array(
+ 'Group 1' => array('A', 'B'),
+ 'Group 2' => array('C', 'D'),
+ ),
+ array(),
+ array(
+ 'Group 1' => array(array('foo' => 'bar1'), array('foo' => 'bar2')),
+ 'Group 2' => array(array(), array('foo' => 'bar4'))
+ )
+ );
+
+ $this->assertEquals(array(
+ 'Group 1' => array(
+ new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')),
+ new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2'))
+ ),
+ 'Group 2' => array(
+ 2 => new ChoiceView($this->obj3, '2', 'C'),
+ 3 => new ChoiceView($this->obj4, '3', 'D', array('foo' => 'bar4'))
+ )
+ ),
+ $list->getRemainingViews()
+ );
+ }
+
/**
* @expectedException \InvalidArgumentException
*/
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php
index 655101ca1a784..b201cb4ec293e 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/ObjectChoiceListTest.php
@@ -63,6 +63,24 @@ public function testInitArray()
$this->assertEquals(array(0 => new ChoiceView($this->obj1, '0', 'A'), 2 => new ChoiceView($this->obj3, '2', 'C'), 3 => new ChoiceView($this->obj4, '3', 'D')), $this->list->getRemainingViews());
}
+ public function testInitAttributes()
+ {
+ $list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
+ 'name', array(), null, null, null,
+ array(array('foo' => 'bar1'), array('foo' => 'bar2'), array('foo' => 'bar3'), array('foo' => 'bar4'))
+ );
+
+ $this->assertEquals(array(
+ 0 => new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')),
+ 1 => new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2')),
+ 2 => new ChoiceView($this->obj3, '2', 'C', array('foo' => 'bar3')),
+ 3 => new ChoiceView($this->obj4, '3', 'D', array('foo' => 'bar4'))
+ ),
+ $list->getRemainingViews()
+ );
+ }
+
public function testInitNestedArray()
{
$this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
@@ -77,6 +95,34 @@ public function testInitNestedArray()
), $this->list->getRemainingViews());
}
+ public function testInitNestedAttributes()
+ {
+ $list = new ObjectChoiceList(
+ array(
+ 'Group 1' => array($this->obj1, $this->obj2),
+ 'Group 2' => array($this->obj3, $this->obj4),
+ ),
+ 'name', array(), null, null, null,
+ array(
+ 'Group 1' => array(array('foo' => 'bar1'), array('foo' => 'bar2')),
+ 'Group 2' => array(array(), array('foo' => 'bar4'))
+ )
+ );
+
+ $this->assertEquals(array(
+ 'Group 1' => array(
+ new ChoiceView($this->obj1, '0', 'A', array('foo' => 'bar1')),
+ new ChoiceView($this->obj2, '1', 'B', array('foo' => 'bar2'))
+ ),
+ 'Group 2' => array(
+ 2 => new ChoiceView($this->obj3, '2', 'C'),
+ 3 => new ChoiceView($this->obj4, '3', 'D', array('foo' => 'bar4'))
+ )
+ ),
+ $list->getRemainingViews()
+ );
+ }
+
public function testInitArrayWithGroupPath()
{
$this->obj1 = (object) array('name' => 'A', 'category' => 'Group 1');
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php
index 838a8e0864e41..72b59963b19d8 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/ChoiceList/SimpleChoiceListTest.php
@@ -27,6 +27,23 @@ public function testInitArray()
$this->assertEquals(array(0 => new ChoiceView('a', 'a', 'A'), 2 => new ChoiceView('c', 'c', 'C')), $this->list->getRemainingViews());
}
+ public function testInitAttributes()
+ {
+ $list = new SimpleChoiceList(
+ array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ array(),
+ array('a' => array('foo' => 'bar1'), 'c' => array('foo' => 'bar3'))
+ );
+
+ $this->assertEquals(array(
+ new ChoiceView('a', 'a', 'A', array('foo' => 'bar1')),
+ new ChoiceView('b', 'b', 'B'),
+ new ChoiceView('c', 'c', 'C', array('foo' => 'bar3'))
+ ),
+ $list->getRemainingViews()
+ );
+ }
+
public function testInitNestedArray()
{
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $this->list->getChoices());
@@ -41,6 +58,34 @@ public function testInitNestedArray()
), $this->list->getRemainingViews());
}
+ public function testInitNestedAttributes()
+ {
+ $list = new SimpleChoiceList(
+ array(
+ 'Group 1' => array('a' => 'A', 'b' => 'B'),
+ 'Group 2' => array('c' => 'C', 'd' => 'D'),
+ ),
+ array(),
+ array(
+ 'Group 1' => array('a' => array('foo' => 'bar1'), 'b' => array('foo' => 'bar2')),
+ 'Group 2' => array('c' => array(), 'd' => array('foo' => 'bar4'))
+ )
+ );
+
+ $this->assertEquals(array(
+ 'Group 1' => array(
+ new ChoiceView('a', 'a', 'A', array('foo' => 'bar1')),
+ new ChoiceView('b', 'b', 'B', array('foo' => 'bar2'))
+ ),
+ 'Group 2' => array(
+ 2 => new ChoiceView('c', 'c', 'C'),
+ 3 => new ChoiceView('d', 'd', 'D', array('foo' => 'bar4'))
+ )
+ ),
+ $list->getRemainingViews()
+ );
+ }
+
/**
* @dataProvider dirtyValuesProvider
*/
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 b251da031c61a..9d95b1ea0eff6 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php
@@ -86,6 +86,30 @@ public function testChoiceListOptionExpectsChoiceListInterface()
));
}
+ /**
+ * @expectedException \Symfony\Component\Form\Exception\InvalidConfigurationException
+ */
+ public function testChoiceListSpecifiedAndAttributes()
+ {
+ $this->factory->create('choice', null, array(
+ 'choice_list' => new ObjectChoiceList(array()),
+ 'choices_attr' => array(1),
+ ));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Form\Exception\RuntimeException
+ */
+ public function testChoiceAttributesCallableDoesNotReturnArray()
+ {
+ $this->factory->create('choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => function () {
+ return false;
+ }
+ ));
+ }
+
public function testChoiceListAndChoicesCanBeEmpty()
{
$this->factory->create('choice');
@@ -1274,4 +1298,125 @@ public function testInitializeWithDefaultObjectChoice()
// Trigger data initialization
$form->getViewData();
}
+
+ public function testPassAttributesToView()
+ {
+ $attributes = array('foo' => 'bar', 'bar' => 'baz');
+
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => array('a' => $attributes, 'c' => $attributes),
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ new ChoiceView('a', 'a', 'A', $attributes),
+ new ChoiceView('b', 'b', 'B'),
+ new ChoiceView('c', 'c', 'C', $attributes),
+ ), $view->vars['choices']);
+ }
+
+ public function testPassAttributesToViewWithCallable()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => function ($choice) {
+ return array('choice' => $choice);
+ }
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ new ChoiceView('a', 'a', 'A', array('choice' => 'A')),
+ new ChoiceView('b', 'b', 'B', array('choice' => 'B')),
+ new ChoiceView('c', 'c', 'C', array('choice' => 'C')),
+ ), $view->vars['choices']);
+ }
+
+ public function testPassAttributesToViewNestedWithCallable()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => array(
+ 'Group 1' => array('a' => 'A', 'b' => 'B'),
+ 'Group 2' => array('c' => 'C', 'd' => 'D'),
+ ),
+ 'choices_attr' => function ($choice) {
+ return array('choice' => $choice);
+ }
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ 'Group 1' => array(
+ 0 => new ChoiceView('a', 'a', 'A', array('choice' => 'A')),
+ 1 => new ChoiceView('b', 'b', 'B', array('choice' => 'B')),
+ ),
+ 'Group 2' => array(
+ 2 => new ChoiceView('c', 'c', 'C', array('choice' => 'C')),
+ 3 => new ChoiceView('d', 'd', 'D', array('choice' => 'D')),
+ ),
+ ), $view->vars['choices']);
+ }
+
+ public function testPassAttributesToViewFromChoiceList()
+ {
+ $attributes = array('foo' => 'bar', 'bar' => 'baz');
+ $obj1 = (object) array('value' => 'a', 'label' => 'A');
+ $obj2 = (object) array('value' => 'b', 'label' => 'B');
+ $obj3 = (object) array('value' => 'c', 'label' => 'C');
+
+ $form = $this->factory->create('choice', null, array(
+ 'choice_list' => new ObjectChoiceList(array($obj1, $obj2, $obj3), 'label', array(), null, 'value', null, array($attributes, array(), $attributes))
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ new ChoiceView($obj1, 'a', 'A', $attributes),
+ new ChoiceView($obj2, 'b', 'B'),
+ new ChoiceView($obj3, 'c', 'C', $attributes),
+ ), $view->vars['choices']);
+ }
+
+ public function testExpandedAttributesArePassedToChildrenRadios()
+ {
+ $attributes = array('foo' => 'bar', 'bar' => 'baz');
+
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => array('a' => $attributes, 'c' => $attributes),
+ 'expanded' => true,
+ ));
+
+ $optionsA = $form->get(0)->getConfig()->getOptions();
+ $optionsB = $form->get(1)->getConfig()->getOptions();
+ $optionsC = $form->get(2)->getConfig()->getOptions();
+
+ $this->assertEquals($attributes, $optionsA['attr']);
+ $this->assertEmpty($optionsB['attr']);
+ $this->assertEquals($attributes, $optionsC['attr']);
+ }
+
+ public function testExpandedAttributesArePassedToChildrenCheckboxes()
+ {
+ $attributes = array('foo' => 'bar', 'bar' => 'baz');
+
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'C'),
+ 'choices_attr' => array('a' => $attributes, 'c' => $attributes),
+ 'expanded' => true,
+ 'multiple' => true,
+ ));
+
+ $optionsA = $form->get(0)->getConfig()->getOptions();
+ $optionsB = $form->get(1)->getConfig()->getOptions();
+ $optionsC = $form->get(2)->getConfig()->getOptions();
+
+ $this->assertEquals($attributes, $optionsA['attr']);
+ $this->assertEmpty($optionsB['attr']);
+ $this->assertEquals($attributes, $optionsC['attr']);
+ }
}