diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md
index f51c9827ea941..b74f2b228a7a5 100644
--- a/CHANGELOG-2.1.md
+++ b/CHANGELOG-2.1.md
@@ -166,43 +166,30 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* allowed setting different options for RepeatedType fields (like the label)
* added support for empty form name at root level, this enables rendering forms
without form name prefix in field names
-
- * [BC BREAK] made form naming more restrictive. Form and field names must
- start with a letter, digit or underscore and only contain letters, digits,
- underscores, hyphens and colons
-
+ * [BC BREAK] form and field names must start with a letter, digit or underscore
+ and only contain letters, digits, underscores, hyphens and colons
* [BC BREAK] changed default name of the prototype in the "collection" type
from "$$name$$" to "__name__". No dollars are appended/prepended to custom
names anymore.
-
- * [BC BREAK] greatly improved `ChoiceListInterface` and all of its
- implementations. `EntityChoiceList` was adapted, the methods `getEntities()`,
- `getEntitiesByKeys()`, `getIdentifier()` and `getIdentifierValues()` were
- removed/made private. Instead of the first two you can use `getChoices()`
- and `getChoicesByValues()`, for the latter two no replacement exists.
- `ArrayChoiceList` was replaced by `SimpleChoiceList`.
- `PaddedChoiceList`, `MonthChoiceList` and `TimezoneChoiceList` were removed.
- Their functionality was merged into `DateType`, `TimeType` and `TimezoneType`.
-
- * [BC BREAK] removed `EntitiesToArrayTransformer` and `EntityToIdTransformer`.
- The former has been replaced by `CollectionToArrayTransformer` in combination
- with `EntityChoiceList`, the latter is not required in the core anymore.
+ * [BC BREAK] improved ChoiceListInterface and all of its implementations
+ * [BC BREAK] removed EntitiesToArrayTransformer and EntityToIdTransformer.
+ The former has been replaced by CollectionToArrayTransformer in combination
+ with EntityChoiceList, the latter is not required in the core anymore.
* [BC BREAK] renamed
- * `ArrayToBooleanChoicesTransformer` to `ChoicesToBooleanArrayTransformer`
- * `ScalarToBooleanChoicesTransformer` to `ChoiceToBooleanArrayTransformer`
- * `ArrayToChoicesTransformer` to `ChoicesToValuesTransformer`
- * `ScalarToChoiceTransformer` to `ChoiceToValueTransformer`
-
- to be consistent with the naming in `ChoiceListInterface`.
+ * ArrayToBooleanChoicesTransformer to ChoicesToBooleanArrayTransformer
+ * ScalarToBooleanChoicesTransformer to ChoiceToBooleanArrayTransformer
+ * ArrayToChoicesTransformer to ChoicesToValuesTransformer
+ * ScalarToChoiceTransformer to ChoiceToValueTransformer
- * [BC BREAK] removed `FormUtil::toArrayKey()` and `FormUtil::toArrayKeys()`.
- They were merged into `ChoiceList` and have no public equivalent anymore.
+ to be consistent with the naming in ChoiceListInterface.
- * added `ComplexChoiceList` and `ObjectChoiceList`. Both let you select amongst
+ * [BC BREAK] removed FormUtil::toArrayKey() and FormUtil::toArrayKeys().
+ They were merged into ChoiceList and have no public equivalent anymore.
+ * added ComplexChoiceList and ObjectChoiceList. Both let you select amongst
objects in a choice field, but feature different constructors.
- * choice fields now throw a `FormException` if neither the "choices" nor the
+ * choice fields now throw a FormException if neither the "choices" nor the
"choice_list" option is set
* the radio field is now a child type of the checkbox field
diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md
index 9b79eb32ae44b..120e66e933cae 100644
--- a/UPGRADE-2.1.md
+++ b/UPGRADE-2.1.md
@@ -78,6 +78,60 @@ UPGRADE FROM 2.0 to 2.1
enable BC behaviour by setting the option "cascade_validation" to `true` on
the parent form.
+* Changed implementation of choice lists
+
+ ArrayChoiceList was replaced. If you have custom classes that extend
+ this class, you can now extend SimpleChoiceList.
+
+ Before:
+
+ class MyChoiceList extends ArrayChoiceList
+ {
+ protected function load()
+ {
+ parent::load();
+
+ // load choices
+
+ $this->choices = $choices;
+ }
+ }
+
+ After:
+
+ class MyChoiceList extends SimpleChoiceList
+ {
+ public function __construct()
+ {
+ // load choices
+
+ parent::__construct($choices);
+ }
+ }
+
+ If you need to load the choices lazily - that is, as soon as they are
+ accessed for the first time - you can extend LazyChoiceList instead.
+
+ class MyChoiceList extends LazyChoiceList
+ {
+ protected function loadChoiceList()
+ {
+ // load choices
+
+ return new SimpleChoiceList($choices);
+ }
+ }
+
+ PaddedChoiceList, MonthChoiceList and TimezoneChoiceList were removed.
+ Their functionality was merged into DateType, TimeType and
+ TimezoneType.
+
+ EntityChoiceList was adapted. The methods `getEntities`,
+ `getEntitiesByKeys`, `getIdentifier` and `getIdentifierValues` were
+ removed/made private. Instead of the first two, you can now use
+ `getChoices` and `getChoicesByValues`. For the latter two, no
+ replacement exists.
+
* The strategy for generating the HTML attributes "id" and "name"
of choices in a choice field has changed
@@ -102,8 +156,8 @@ UPGRADE FROM 2.0 to 2.1
* In the template of the choice type, the structure of the "choices" variable
has changed
- "choices" now contains ChoiceView objects with two getters `getValue()`
- and `getLabel()` to access the choice data. The indices of the array
+ "choices" now contains ChoiceView objects with two getters `getValue`
+ and `getLabel` to access the choice data. The indices of the array
store an index whose generation is controlled by the "index_generation"
option of the choice field.
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
index c8cd44ccd8029..c9d09a4904268 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
@@ -17,7 +17,16 @@
use Symfony\Component\Form\Extension\Core\View\ChoiceView;
/**
- * Base class for choice list implementations.
+ * A choice list for choices of arbitrary data types.
+ *
+ * Choices and labels are passed in two arrays. The indices of the choices
+ * and the labels should match.
+ *
+ *
+ * $choices = array(true, false);
+ * $labels = array('Agree', 'Disagree');
+ * $choiceList = new ChoiceList($choices, $labels);
+ *
*
* @author Bernhard Schussek
*/
@@ -145,11 +154,7 @@ protected function initialize($choices, array $labels, array $preferredChoices)
}
/**
- * Returns the list of choices
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getChoices()
{
@@ -157,11 +162,7 @@ public function getChoices()
}
/**
- * Returns the values for the choices
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getValues()
{
@@ -169,12 +170,7 @@ public function getValues()
}
/**
- * Returns the choice views of the preferred choices as nested array with
- * the choice groups as top-level keys.
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getPreferredViews()
{
@@ -182,12 +178,7 @@ public function getPreferredViews()
}
/**
- * Returns the choice views of the choices that are not preferred as nested
- * array with the choice groups as top-level keys.
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getRemainingViews()
{
@@ -195,13 +186,7 @@ public function getRemainingViews()
}
/**
- * Returns the choices corresponding to the given values.
- *
- * @param array $values
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
@@ -232,13 +217,7 @@ public function getChoicesForValues(array $values)
}
/**
- * Returns the values corresponding to the given choices.
- *
- * @param array $choices
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
@@ -269,13 +248,7 @@ public function getValuesForChoices(array $choices)
}
/**
- * Returns the indices corresponding to the given choices.
- *
- * @param array $choices
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getIndicesForChoices(array $choices)
{
@@ -299,13 +272,7 @@ public function getIndicesForChoices(array $choices)
}
/**
- * Returns the indices corresponding to the given values.
- *
- * @param array $values
- *
- * @return array
- *
- * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ * {@inheritdoc}
*/
public function getIndicesForValues(array $values)
{
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php
new file mode 100644
index 0000000000000..29551de560f0b
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php
@@ -0,0 +1,149 @@
+
+ *
+ * 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\ChoiceList;
+
+use Symfony\Component\Form\Exception\FormException;
+
+/**
+ * A choice list that is loaded lazily
+ *
+ * This list loads itself as soon as any of the getters is accessed for the
+ * first time. You should implement loadChoiceList() in your child classes,
+ * which should return a ChoiceListInterface instance.
+ *
+ * @author Bernhard Schussek
+ */
+abstract class LazyChoiceList implements ChoiceListInterface
+{
+ /**
+ * The loaded choice list
+ *
+ * @var ChoiceListInterface
+ */
+ private $choiceList;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getChoices()
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getChoices();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValues()
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getValues();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPreferredViews()
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getPreferredViews();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRemainingViews()
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getRemainingViews();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getChoicesForValues(array $values)
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getChoicesForValues($values);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValuesForChoices(array $choices)
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getValuesForChoices($choices);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIndicesForChoices(array $choices)
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getIndicesForChoices($choices);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getIndicesForValues(array $values)
+ {
+ if (!$this->choiceList) {
+ $this->load();
+ }
+
+ return $this->choiceList->getIndicesForValues($values);
+ }
+
+ /**
+ * Loads the choice list
+ *
+ * Should be implemented by child classes.
+ *
+ * @return ChoiceListInterface The loaded choice list
+ */
+ abstract protected function loadChoiceList();
+
+ private function load()
+ {
+ $choiceList = $this->loadChoiceList();
+
+ if (!$choiceList instanceof ChoiceListInterface) {
+ throw new FormException('loadChoiceList() should return a ChoiceListInterface instance. Got ' . gettype($choiceList));
+ }
+
+ $this->choiceList = $choiceList;
+ }
+}
\ No newline at end of file
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
index 3533326be1f81..daf13efcd353f 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
@@ -17,11 +17,17 @@
use Symfony\Component\Form\Exception\InvalidPropertyException;
/**
- * A choice list that can store object choices.
+ * A choice list for object choices.
*
* Supports generation of choice labels, choice groups, choice values and
- * choice indices by introspecting the properties of the object (or
- * associated objects).
+ * choice indices by calling getters of the object (or associated objects).
+ *
+ *
+ * $choices = array($user1, $user2);
+ *
+ * // call getName() to determine the choice labels
+ * $choiceList = new ObjectChoiceList($choices, 'name');
+ *
*
* @author Bernhard Schussek
*/
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
index 748f8397c09d8..27a7382c03af3 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
@@ -15,10 +15,39 @@
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
- * A choice list that can store any choices that are allowed as PHP array keys.
+ * A choice list for choices of type string or integer.
*
- * The value strategy of simple choice lists is fixed to ChoiceList::COPY_CHOICE,
- * since array keys are always valid choice values.
+ * 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
+ * allowed.
+ *
+ *
+ * $choiceList = new SimpleChoiceList(array(
+ * 'creditcard' => 'Credit card payment',
+ * 'cash' => 'Cash payment',
+ * ));
+ *
+ *
+ * The default value generation strategy is `ChoiceList::COPY_CHOICE`, because
+ * choice values must be scalar, and the choices passed to this choice list
+ * are guaranteed to be scalar.
+ *
+ * The default index generation strategy is `ChoiceList::GENERATE`, so that
+ * your choices can also contain values that are illegal as indices. If your
+ * choices are guaranteed to start with a letter, digit or underscore and only
+ * contain letters, digits, underscores, hyphens and colons, you can set the
+ * strategy to `ChoiceList::COPY_CHOICE` instead.
+ *
+ *
+ * $choices = array(
+ * 'creditcard' => 'Credit card payment',
+ * 'cash' => 'Cash payment',
+ * );
+ *
+ * // value generation: COPY_CHOICE (the default)
+ * // index generation: COPY_CHOICE (custom)
+ * $choiceList = new SimpleChoiceList($choices, array(), ChoiceList::COPY_CHOICE, ChoiceList::COPY_CHOICE);
+ *
*
* @author Bernhard Schussek
*/
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/LazyChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/LazyChoiceListTest.php
new file mode 100644
index 0000000000000..6e6bb927dc895
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/LazyChoiceListTest.php
@@ -0,0 +1,116 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Tests\Component\Form\Extension\Core\ChoiceList;
+
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+
+class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
+{
+ private $list;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->list = new LazyChoiceListTest_Impl(new SimpleChoiceList(array(
+ 'a' => 'A',
+ 'b' => 'B',
+ 'c' => 'C',
+ ), array('b')));
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->list = null;
+ }
+
+ public function testGetChoices()
+ {
+ $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c'), $this->list->getChoices());
+ }
+
+ public function testGetValues()
+ {
+ $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c'), $this->list->getValues());
+ }
+
+ public function testGetPreferredViews()
+ {
+ $this->assertEquals(array(1 => new ChoiceView('b', 'B')), $this->list->getPreferredViews());
+ }
+
+ public function testGetRemainingViews()
+ {
+ $this->assertEquals(array(0 => new ChoiceView('a', 'A'), 2 => new ChoiceView('c', 'C')), $this->list->getRemainingViews());
+ }
+
+ public function testGetIndicesForChoices()
+ {
+ $choices = array('b', 'c');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForChoices($choices));
+ }
+
+ public function testGetIndicesForValues()
+ {
+ $values = array('b', 'c');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForValues($values));
+ }
+
+ public function testGetChoicesForValues()
+ {
+ $values = array('b', 'c');
+ $this->assertSame(array('b', 'c'), $this->list->getChoicesForValues($values));
+ }
+
+ public function testGetValuesForChoices()
+ {
+ $choices = array('b', 'c');
+ $this->assertSame(array('b', 'c'), $this->list->getValuesForChoices($choices));
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\FormException
+ */
+ public function testLoadChoiceListShouldReturnChoiceList()
+ {
+ $list = new LazyChoiceListTest_InvalidImpl();
+
+ $list->getChoices();
+ }
+}
+
+class LazyChoiceListTest_Impl extends LazyChoiceList
+{
+ private $choiceList;
+
+ public function __construct($choiceList)
+ {
+ $this->choiceList = $choiceList;
+ }
+
+ protected function loadChoiceList()
+ {
+ return $this->choiceList;
+ }
+}
+
+class LazyChoiceListTest_InvalidImpl extends LazyChoiceList
+{
+ protected function loadChoiceList()
+ {
+ return new \stdClass();
+ }
+}
\ No newline at end of file