[Form] fix ChoiceList and ObjectChoiceList when choices is a Traversable

This commit is contained in:
Tobias Schultze 2012-07-24 01:49:07 +02:00
parent 6f7ea8dbbe
commit 805393303c
4 changed files with 86 additions and 38 deletions

View File

@ -20,7 +20,10 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView;
* A choice list for choices of arbitrary data types. * A choice list for choices of arbitrary data types.
* *
* Choices and labels are passed in two arrays. The indices of the choices * Choices and labels are passed in two arrays. The indices of the choices
* and the labels should match. * and the labels should match. 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.
* *
* <code> * <code>
* $choices = array(true, false); * $choices = array(true, false);
@ -69,14 +72,21 @@ class ChoiceList implements ChoiceListInterface
* as hierarchy of unlimited depth. Hierarchies are * as hierarchy of unlimited depth. Hierarchies are
* created by creating nested arrays. The title of * created by creating nested arrays. The title of
* the sub-hierarchy can be stored in the array * the sub-hierarchy can be stored in the array
* key pointing to the nested 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 * @param array $labels The array of labels. The structure of this array
* should match the structure of $choices. * should match the structure of $choices.
* @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. * presented to the user with priority.
*
* @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())
{ {
if (!is_array($choices) && !$choices instanceof \Traversable) {
throw new UnexpectedTypeException($choices, 'array or \Traversable');
}
$this->initialize($choices, $labels, $preferredChoices); $this->initialize($choices, $labels, $preferredChoices);
} }
@ -236,17 +246,17 @@ class ChoiceList implements ChoiceListInterface
/** /**
* Recursively adds the given choices to the list. * Recursively adds the given choices to the list.
* *
* @param array $bucketForPreferred The bucket where to store the preferred * @param array $bucketForPreferred The bucket where to store the preferred
* view objects. * view objects.
* @param array $bucketForRemaining The bucket where to store the * @param array $bucketForRemaining The bucket where to store the
* non-preferred view objects. * non-preferred view objects.
* @param array $choices The list of choices. * @param array|\Traversable $choices The list of choices.
* @param array $labels The labels corresponding to the choices. * @param array $labels The labels corresponding to the choices.
* @param array $preferredChoices The preferred choices. * @param array $preferredChoices The preferred choices.
* *
* @throws InvalidConfigurationException If no valid value or index could be created for a choice. * @throws InvalidConfigurationException If no valid value or index could be created for a choice.
*/ */
protected function addChoices(&$bucketForPreferred, &$bucketForRemaining, array $choices, array $labels, array $preferredChoices) protected function addChoices(&$bucketForPreferred, &$bucketForRemaining, $choices, array $labels, array $preferredChoices)
{ {
// Add choices to the nested buckets // Add choices to the nested buckets
foreach ($choices as $group => $choice) { foreach ($choices as $group => $choice) {
@ -278,13 +288,13 @@ class ChoiceList implements ChoiceListInterface
* Recursively adds a choice group. * Recursively adds a choice group.
* *
* @param string $group The name of the group. * @param string $group The name of the group.
* @param array $bucketForPreferred The bucket where to store the preferred * @param array $bucketForPreferred The bucket where to store the preferred
* view objects. * view objects.
* @param array $bucketForRemaining The bucket where to store the * @param array $bucketForRemaining The bucket where to store the
* non-preferred view objects. * non-preferred view objects.
* @param array $choices The list of choices in the group. * @param array $choices The list of choices in the group.
* @param array $labels The labels corresponding to the choices in the group. * @param array $labels The labels corresponding to the choices in the group.
* @param array $preferredChoices The preferred choices. * @param array $preferredChoices The preferred choices.
* *
* @throws InvalidConfigurationException If no valid value or index could be created for a choice. * @throws InvalidConfigurationException If no valid value or index could be created for a choice.
*/ */

View File

@ -13,7 +13,6 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList;
use Symfony\Component\Form\Util\PropertyPath; use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Exception\StringCastException; use Symfony\Component\Form\Exception\StringCastException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Exception\InvalidPropertyException; use Symfony\Component\Form\Exception\InvalidPropertyException;
/** /**
@ -58,10 +57,11 @@ class ObjectChoiceList extends ChoiceList
* Creates a new object choice list. * 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. Hierarchies are * as hierarchy of unlimited depth by creating nested
* created by creating nested arrays. The title of * arrays. The title of the sub-hierarchy can be
* the sub-hierarchy can be stored in the array * stored in the array key pointing to the nested
* key pointing to the nested array. * 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 * 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
@ -93,19 +93,18 @@ class ObjectChoiceList extends ChoiceList
* @param array|\Traversable $choices The choices to write into the list. * @param array|\Traversable $choices The choices to write into the list.
* @param array $labels Ignored. * @param array $labels Ignored.
* @param array $preferredChoices The choices to display with priority. * @param array $preferredChoices The choices to display with priority.
*
* @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)
{ {
if (!is_array($choices) && !$choices instanceof \Traversable) {
throw new UnexpectedTypeException($choices, 'array or \Traversable');
}
if (null !== $this->groupPath) { if (null !== $this->groupPath) {
$groupedChoices = array(); $groupedChoices = array();
foreach ($choices as $i => $choice) { foreach ($choices as $i => $choice) {
if (is_array($choice)) { if (is_array($choice)) {
throw new \InvalidArgumentException('You should pass a plain object array (without groups, $code, $previous) when using the "groupPath" option'); throw new \InvalidArgumentException('You should pass a plain object array (without groups, $code, $previous) when using the "groupPath" option.');
} }
try { try {
@ -142,10 +141,10 @@ class ObjectChoiceList extends ChoiceList
* *
* If a property path for the value was given at object creation, * If a property path for the value was given at object creation,
* the getter behind that path is now called to obtain a new value. * the getter behind that path is now called to obtain a new value.
*
* Otherwise a new integer is generated. * Otherwise a new integer is generated.
* *
* @param mixed $choice The choice to create a value for * @param mixed $choice The choice to create a value for
*
* @return integer|string A unique value without character limitations. * @return integer|string A unique value without character limitations.
*/ */
protected function createValue($choice) protected function createValue($choice)
@ -160,7 +159,7 @@ class ObjectChoiceList extends ChoiceList
private function extractLabels($choices, array &$labels) private function extractLabels($choices, array &$labels)
{ {
foreach ($choices as $i => $choice) { foreach ($choices as $i => $choice) {
if (is_array($choice) || $choice instanceof \Traversable) { if (is_array($choice)) {
$labels[$i] = array(); $labels[$i] = array();
$this->extractLabels($choice, $labels[$i]); $this->extractLabels($choice, $labels[$i]);
} elseif ($this->labelPath) { } elseif ($this->labelPath) {

View File

@ -11,14 +11,14 @@
namespace Symfony\Component\Form\Extension\Core\ChoiceList; namespace Symfony\Component\Form\Extension\Core\ChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/** /**
* A choice list for choices of type string or integer. * A choice list for choices of type string or integer.
* *
* Choices and their associated labels can be passed in a single array. Since * Choices and their associated labels can be passed in a single array. Since
* choices are passed as array keys, only strings or integer choices are * choices are passed as array keys, only strings or integer choices are
* allowed. * allowed. 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.
* *
* <code> * <code>
* $choiceList = new SimpleChoiceList(array( * $choiceList = new SimpleChoiceList(array(
@ -36,10 +36,9 @@ class SimpleChoiceList extends ChoiceList
* *
* @param array $choices The array of choices with the choices as keys and * @param array $choices The array of choices with the choices as keys and
* the labels as values. Choices may also be given * the labels as values. Choices may also be given
* as hierarchy of unlimited depth. Hierarchies are * as hierarchy of unlimited depth by creating nested
* created by creating nested arrays. The title of * arrays. The title of the sub-hierarchy is stored
* the sub-hierarchy is stored in the array * in the array key pointing to the nested array.
* key pointing to the nested array.
* @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. * presented to the user with priority.
*/ */
@ -74,12 +73,20 @@ class SimpleChoiceList extends ChoiceList
} }
/** /**
* {@inheritdoc} * Recursively adds the given choices to the list.
* *
* Takes care of splitting the single $choices array passed in the * Takes care of splitting the single $choices array passed in the
* constructor into choices and labels. * constructor into choices and labels.
*
* @param array $bucketForPreferred The bucket where to store the preferred
* view objects.
* @param array $bucketForRemaining The bucket where to store the
* non-preferred view objects.
* @param array|\Traversable $choices The list of choices.
* @param array $labels Ignored.
* @param array $preferredChoices The preferred choices.
*/ */
protected function addChoices(&$bucketForPreferred, &$bucketForRemaining, array $choices, array $labels, array $preferredChoices) protected function addChoices(&$bucketForPreferred, &$bucketForRemaining, $choices, array $labels, array $preferredChoices)
{ {
// Add choices to the nested buckets // Add choices to the nested buckets
foreach ($choices as $choice => $label) { foreach ($choices as $choice => $label) {

View File

@ -73,6 +73,38 @@ class ChoiceListTest extends \PHPUnit_Framework_TestCase
$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()); $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());
} }
/**
* Necessary for interoperability with MongoDB cursors or ORM relations as
* choices parameter. A choice itself that is an object implementing \Traversable
* is not treated as hierarchical structure, but as-is.
*/
public function testInitNestedTraversable()
{
$traversableChoice = new \ArrayIterator(array($this->obj3, $this->obj4));
$this->list = new ChoiceList(
new \ArrayIterator(array(
'Group' => array($this->obj1, $this->obj2),
'Not a Group' => $traversableChoice
)),
array(
'Group' => array('A', 'B'),
'Not a Group' => 'C',
),
array($this->obj2)
);
$this->assertSame(array($this->obj1, $this->obj2, $traversableChoice), $this->list->getChoices());
$this->assertSame(array('0', '1', '2'), $this->list->getValues());
$this->assertEquals(array(
'Group' => array(1 => new ChoiceView($this->obj2, '1', 'B'))
), $this->list->getPreferredViews());
$this->assertEquals(array(
'Group' => array(0 => new ChoiceView($this->obj1, '0', 'A')),
2 => new ChoiceView($traversableChoice, '2', 'C')
), $this->list->getRemainingViews());
}
public function testInitNestedArray() public function testInitNestedArray()
{ {
$this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices()); $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());