diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md
index 9dd5555796..7e1cfe65fc 100644
--- a/CHANGELOG-2.1.md
+++ b/CHANGELOG-2.1.md
@@ -166,6 +166,37 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* added support for empty form name at root level, this enables rendering forms
without form name prefix in field names
+ * [BC BREAK] greatly improved `ChoiceListInterface` and all of its
+ implementations. `EntityChoiceList` was adapted, the methods `getEntities()`,
+ `getEntitiesByByKeys()`, `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] renamed
+
+ * `ArrayToBooleanChoicesTransformer` to `ChoicesToBooleanArrayTransformer`
+ * `ScalarToBooleanChoicesTransformer` to `ChoiceToBooleanArrayTransformer`
+ * `ArrayToChoicesTransformer` to `ChoicesToValuesTransformer`
+ * `ScalarToChoiceTransformer` to `ChoiceToValueTransformer`
+
+ to be consistent with the naming in `ChoiceListInterface`.
+
+ * [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_list" option is set
+ * the radio field is now a child type of the checkbox field
+
### HttpFoundation
* added support for streamed responses
diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md
index 985463f06c..00a86f6833 100644
--- a/UPGRADE-2.1.md
+++ b/UPGRADE-2.1.md
@@ -76,4 +76,49 @@ UPGRADE FROM 2.0 to 2.1
If you don't want to set the `Valid` constraint, or if there is no reference
from the data of the parent form to the data of the child form, you can
enable BC behaviour by setting the option "cascade_validation" to `true` on
- the parent form.
\ No newline at end of file
+ the parent form.
+
+* In the template of the choice type, instead of a single "choices" variable
+ there are now two variables: "choices" and "choice_labels".
+ "choices" contains the choices in the values (before they were in the keys)
+ and "choice_labels" contains the matching labels. Both arrays have
+ identical keys.
+
+ Before:
+
+ {% for choice, label in choices %}
+
+ {{ label }}
+
+ {% endfor %}
+
+ After:
+
+ {% for index, choice in choices %}
+
+ {{ choice_labels[index] }}
+
+ {% endfor %}
+
+* The strategy for generating the HTML attributes "id" and "name"
+ of choices in a choice field has changed. Instead of appending the choice
+ value, a generated integer is now appended by default. Take care if your
+ Javascript relies on that. If you can guarantee that your choice values only
+ contain ASCII letters, digits, letters, colons and underscores, you can
+ restore the old behaviour by setting the option "index_strategy" of the
+ choice field to `ChoiceList::COPY_CHOICE`.
+
+* The strategy for generating the HTML attributes "value" of choices in a
+ choice field has changed. Instead of using the choice value, a generated
+ integer is now stored. Again, take care if your Javascript reads this value.
+ If your choice field is a non-expanded single-choice field, or if
+ the choices are guaranteed not to contain the empty string '' (which is the
+ case when you added it manually or when the field is a single-choice field
+ and is not required), you can restore the old behaviour by setting the
+ option "value_strategy" to `ChoiceList::COPY_CHOICE`.
+ of choices in a choice field has changed. Instead of appending the choice
+ value, a generated integer is now appended by default. Take care if your
+ Javascript relies on that. If you can guarantee that your choice values only
+ contain ASCII letters, digits, letters, colons and underscores, you can
+ restore the old behaviour by setting the option "index_strategy" of the
+ choice field to `ChoiceList::COPY_CHOICE`.
diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
index f81d58bd4f..ddda8141a3 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
@@ -13,11 +13,17 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Symfony\Component\Form\Util\PropertyPath;
use Symfony\Component\Form\Exception\FormException;
+use Symfony\Component\Form\Exception\StringCastException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
-use Symfony\Component\Form\Extension\Core\ChoiceList\ArrayChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
use Doctrine\Common\Persistence\ObjectManager;
-class EntityChoiceList extends ArrayChoiceList
+/**
+ * A choice list presenting a list of Doctrine entities as choices
+ *
+ * @author Bernhard Schussek
+ */
+class EntityChoiceList extends ObjectChoiceList
{
/**
* @var ObjectManager
@@ -34,19 +40,6 @@ class EntityChoiceList extends ArrayChoiceList
*/
private $classMetadata;
- /**
- * The entities from which the user can choose
- *
- * This array is either indexed by ID (if the ID is a single field)
- * or by key in the choices array (if the ID consists of multiple fields)
- *
- * This property is initialized by initializeChoices(). It should only
- * be accessed through getEntity() and getEntities().
- *
- * @var array
- */
- private $entities = array();
-
/**
* Contains the query builder that builds the query for fetching the
* entities
@@ -67,223 +60,336 @@ class EntityChoiceList extends ArrayChoiceList
private $identifier = array();
/**
- * Property path to access the key value of this choice-list.
+ * Whether the entities have already been loaded.
*
- * @var PropertyPath
+ * @var Boolean
*/
- private $propertyPath;
+ private $loaded = false;
/**
- * Closure or PropertyPath string on Entity to use for grouping of entities
+ * Creates a new entity choice list.
*
- * @var mixed
- */
- private $groupBy;
-
- /**
- * Constructor.
- *
- * @param ObjectManager $manager An EntityManager instance
+ * @param ObjectManager $manager An EntityManager instance
* @param string $class The class name
- * @param string $property The property name
+ * @param string $labelPath The property path used for the label
* @param EntityLoaderInterface $entityLoader An optional query builder
- * @param array|\Closure $choices An array of choices or a function returning an array
- * @param string $groupBy
+ * @param array $entities An array of choices
+ * @param string $groupPath
*/
- public function __construct(ObjectManager $manager, $class, $property = null, EntityLoaderInterface $entityLoader = null, $choices = null, $groupBy = null)
+ public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, $groupPath = null)
{
$this->em = $manager;
$this->class = $class;
$this->entityLoader = $entityLoader;
$this->classMetadata = $manager->getClassMetadata($class);
$this->identifier = $this->classMetadata->getIdentifierFieldNames();
- $this->groupBy = $groupBy;
+ $this->loaded = is_array($entities) || $entities instanceof \Traversable;
- // The property option defines, which property (path) is used for
- // displaying entities as strings
- if ($property) {
- $this->propertyPath = new PropertyPath($property);
- } elseif (!method_exists($this->classMetadata->getName(), '__toString')) {
- // Otherwise expect a __toString() method in the entity
- throw new FormException('Entities passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option).');
+ if (!$this->loaded) {
+ // Make sure the constraints of the parent constructor are
+ // fulfilled
+ $entities = array();
}
- if (!is_array($choices) && !$choices instanceof \Closure && !is_null($choices)) {
- throw new UnexpectedTypeException($choices, 'array or \Closure or null');
- }
-
- $this->choices = $choices;
+ parent::__construct($entities, $labelPath, array(), $groupPath);
}
/**
- * Initializes the choices and returns them.
+ * Returns the list of entities
*
- * If the entities were passed in the "choices" option, this method
- * does not have any significant overhead. Otherwise, if a query builder
- * was passed in the "query_builder" option, this builder is now used
- * to construct a query which is executed. In the last case, all entities
- * for the underlying class are fetched from the repository.
+ * @return array
*
- * @return array An array of choices
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
- protected function load()
+ public function getChoices()
{
- parent::load();
+ if (!$this->loaded) {
+ $this->load();
+ }
- if (is_array($this->choices)) {
- $entities = $this->choices;
- } elseif ($entityLoader = $this->entityLoader) {
- $entities = $entityLoader->getEntities();
+ return parent::getChoices();
+ }
+
+ /**
+ * Returns the labels for the entities
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getLabels()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getLabels();
+ }
+
+ /**
+ * Returns the values for the entities
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getValues()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getValues();
+ }
+
+ /**
+ * Returns the values of the entities that should be presented to the user
+ * with priority.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getPreferredValues()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getPreferredValues();
+ }
+
+ /**
+ * Returns the values of the entities that should be presented to the user
+ * with priority as nested array with the choice groups as top-level keys.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getPreferredValueHierarchy()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getPreferredValueHierarchy();
+ }
+
+ /**
+ * Returns the values of the entities that are not preferred.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getRemainingValues()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getRemainingValues();
+ }
+
+ /**
+ * Returns the values of the entities 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
+ */
+ public function getRemainingValueHierarchy()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getRemainingValueHierarchy();
+ }
+
+ /**
+ * Returns the entities corresponding to the given values.
+ *
+ * @param array $values
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getChoicesForValues(array $values)
+ {
+ if (!$this->loaded) {
+ // Optimize performance in case we have an entity loader and
+ // a single-field identifier
+ if (count($this->identifier) === 1 && $this->entityLoader) {
+ return $this->entityLoader->getEntitiesByIds(current($this->identifier), $values);
+ }
+
+ $this->load();
+ }
+
+ return parent::getChoicesForValues($values);
+ }
+
+ /**
+ * Returns the values corresponding to the given entities.
+ *
+ * @param array $entities
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getValuesForChoices(array $entities)
+ {
+ if (!$this->loaded) {
+ // Optimize performance for single-field identifiers. We already
+ // know that the IDs are used as values
+
+ // Attention: This optimization does not check choices for existence
+ if (count($this->identifier) === 1) {
+ $values = array();
+
+ foreach ($entities as $entity) {
+ if ($entity instanceof $this->class) {
+ // Make sure to convert to the right format
+ $values[] = $this->fixValue(current($this->getIdentifierValues($entity)));
+ }
+ }
+
+ return $values;
+ }
+
+ $this->load();
+ }
+
+ return parent::getValuesForChoices($entities);
+ }
+
+ /**
+ * Returns the indices corresponding to the given entities.
+ *
+ * @param array $entities
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getIndicesForChoices(array $entities)
+ {
+ if (!$this->loaded) {
+ // Optimize performance for single-field identifiers. We already
+ // know that the IDs are used as indices
+
+ // Attention: This optimization does not check choices for existence
+ if (count($this->identifier) === 1) {
+ $indices = array();
+
+ foreach ($entities as $entity) {
+ if ($entity instanceof $this->class) {
+ // Make sure to convert to the right format
+ $indices[] = $this->fixIndex(current($this->getIdentifierValues($entity)));
+ }
+ }
+
+ return $indices;
+ }
+
+ $this->load();
+ }
+
+ return parent::getIndicesForChoices($entities);
+ }
+
+ /**
+ * Returns the entities corresponding to the given values.
+ *
+ * @param array $values
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getIndicesForValues(array $values)
+ {
+ if (!$this->loaded) {
+ // Optimize performance for single-field identifiers. We already
+ // know that the IDs are used as indices and values
+
+ // Attention: This optimization does not check values for existence
+ if (count($this->identifier) === 1) {
+ return $this->fixIndices($values);
+ }
+
+ $this->load();
+ }
+
+ return parent::getIndicesForValues($values);
+ }
+
+ /**
+ * Creates a new unique index for this entity.
+ *
+ * If the entity has a single-field identifier, this identifier is used.
+ *
+ * Otherwise a new integer is generated.
+ *
+ * @param mixed $choice The choice to create an index for
+ *
+ * @return integer|string A unique index containing only ASCII letters,
+ * digits and underscores.
+ */
+ protected function createIndex($entity)
+ {
+ if (count($this->identifier) === 1) {
+ return current($this->getIdentifierValues($entity));
+ }
+
+ return parent::createIndex($entity);
+ }
+
+ /**
+ * Creates a new unique value for this entity.
+ *
+ * If the entity has a single-field identifier, this identifier is used.
+ *
+ * Otherwise a new integer is generated.
+ *
+ * @param mixed $choice The choice to create a value for
+ *
+ * @return integer|string A unique value without character limitations.
+ */
+ protected function createValue($entity)
+ {
+ if (count($this->identifier) === 1) {
+ return current($this->getIdentifierValues($entity));
+ }
+
+ return parent::createValue($entity);
+ }
+
+ /**
+ * Loads the list with entities.
+ */
+ private function load()
+ {
+ if ($this->entityLoader) {
+ $entities = $this->entityLoader->getEntities();
} else {
$entities = $this->em->getRepository($this->class)->findAll();
}
- $this->choices = array();
- $this->entities = array();
-
- if ($this->groupBy) {
- $entities = $this->groupEntities($entities, $this->groupBy);
+ try {
+ // The second parameter $labels is ignored by ObjectChoiceList
+ // The third parameter $preferredChoices is currently not supported
+ parent::initialize($entities, array(), array());
+ } catch (StringCastException $e) {
+ throw new StringCastException('Objects in the entity field must have a "__toString()" method defined. Alternatively you can set the "property" option.', null, $e);
}
- $this->loadEntities($entities);
-
- return $this->choices;
- }
-
- private function groupEntities($entities, $groupBy)
- {
- $grouped = array();
- $path = new PropertyPath($groupBy);
-
- foreach ($entities as $entity) {
- // Get group name from property path
- try {
- $group = (string) $path->getValue($entity);
- } catch (UnexpectedTypeException $e) {
- // PropertyPath cannot traverse entity
- $group = null;
- }
-
- if (empty($group)) {
- $grouped[] = $entity;
- } else {
- $grouped[$group][] = $entity;
- }
- }
-
- return $grouped;
- }
-
- /**
- * Converts entities into choices with support for groups.
- *
- * The choices are generated from the entities. If the entities have a
- * composite identifier, the choices are indexed using ascending integers.
- * Otherwise the identifiers are used as indices.
- *
- * If the option "property" was passed, the property path in that option
- * is used as option values. Otherwise this method tries to convert
- * objects to strings using __toString().
- *
- * @param array $entities An array of entities
- * @param string $group A group name
- */
- private function loadEntities($entities, $group = null)
- {
- foreach ($entities as $key => $entity) {
- if (is_array($entity)) {
- // Entities are in named groups
- $this->loadEntities($entity, $key);
- continue;
- }
-
- if ($this->propertyPath) {
- // If the property option was given, use it
- $value = $this->propertyPath->getValue($entity);
- } else {
- $value = (string) $entity;
- }
-
- if (count($this->identifier) > 1) {
- // When the identifier consists of multiple field, use
- // naturally ordered keys to refer to the choices
- $id = $key;
- } else {
- // When the identifier is a single field, index choices by
- // entity ID for performance reasons
- $id = current($this->getIdentifierValues($entity));
- }
-
- if (null === $group) {
- // Flat list of choices
- $this->choices[$id] = $value;
- } else {
- // Nested choices
- $this->choices[$group][$id] = $value;
- }
-
- $this->entities[$id] = $entity;
- }
- }
-
- /**
- * Returns the fields of which the identifier of the underlying class consists.
- *
- * @return array
- */
- public function getIdentifier()
- {
- return $this->identifier;
- }
-
- /**
- * Returns the according entities for the choices.
- *
- * If the choices were not initialized, they are initialized now. This
- * is an expensive operation, except if the entities were passed in the
- * "choices" option.
- *
- * @return array An array of entities
- */
- public function getEntities()
- {
- if (!$this->loaded) {
- $this->load();
- }
-
- return $this->entities;
- }
-
- /**
- * Returns the entities for the given keys.
- *
- * If the underlying entities have composite identifiers, the choices
- * are initialized. The key is expected to be the index in the choices
- * array in this case.
- *
- * If they have single identifiers, they are either fetched from the
- * internal entity cache (if filled) or loaded from the database.
- *
- * @param array $keys The choice key (for entities with composite
- * identifiers) or entity ID (for entities with single
- * identifiers)
- * @return object[] The matching entity
- */
- public function getEntitiesByKeys(array $keys)
- {
- if (!$this->loaded) {
- $this->load();
- }
-
- $found = array();
-
- foreach ($keys as $key) {
- if (isset($this->entities[$key])) {
- $found[] = $this->entities[$key];
- }
- }
-
- return $found;
+ $this->loaded = true;
}
/**
@@ -299,7 +405,7 @@ class EntityChoiceList extends ArrayChoiceList
*
* @throws FormException If the entity does not exist in Doctrine's identity map
*/
- public function getIdentifierValues($entity)
+ private function getIdentifierValues($entity)
{
if (!$this->em->contains($entity)) {
throw new FormException('Entities passed to the choice field must be managed');
diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php
index a64496e2f6..4fc8e6ba99 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php
@@ -19,9 +19,19 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
interface EntityLoaderInterface
{
/**
- * Return an array of entities that are valid choices in the corresponding choice list.
+ * Returns an array of entities that are valid choices in the corresponding choice list.
*
* @return array
*/
function getEntities();
+
+ /**
+ * Returns an array of entities matching the given identifiers.
+ *
+ * @param string $identifier
+ * @param array $values
+ *
+ * @return array
+ */
+ function getEntitiesByIds($identifier, array $values);
}
diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php
index eb585f0b9e..5dd51fb10b 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php
@@ -13,6 +13,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Doctrine\ORM\QueryBuilder;
+use Doctrine\DBAL\Connection;
/**
* Getting Entities through the ORM QueryBuilder
@@ -62,4 +63,19 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
{
return $this->queryBuilder->getQuery()->execute();
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getEntitiesByIds($identifier, array $values)
+ {
+ $qb = clone ($this->queryBuilder);
+ $alias = current($qb->getRootAliases());
+ $where = $qb->expr()->in($alias.'.'.$identifier, "?1");
+
+ return $qb->andWhere($where)
+ ->getQuery()
+ ->setParameter(1, $values, Connection::PARAM_STR_ARRAY)
+ ->getResult();
+ }
}
diff --git a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php
new file mode 100644
index 0000000000..94ff98359e
--- /dev/null
+++ b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
+
+use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+use Symfony\Component\Form\DataTransformerInterface;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ * @author Bernhard Schussek
+ */
+class CollectionToArrayTransformer implements DataTransformerInterface
+{
+ /**
+ * Transforms a collection into an array.
+ *
+ * @param Collection|object $collection A collection of entities, a single entity or NULL
+ *
+ * @return mixed An array of choice keys, a single key or NULL
+ */
+ public function transform($collection)
+ {
+ if (null === $collection) {
+ return array();
+ }
+
+ if (!$collection instanceof Collection) {
+ throw new UnexpectedTypeException($collection, 'Doctrine\Common\Collections\Collection');
+ }
+
+ return $collection->toArray();
+ }
+
+ /**
+ * Transforms choice keys into entities.
+ *
+ * @param mixed $keys An array of keys, a single key or NULL
+ *
+ * @return Collection|object A collection of entities, a single entity or NULL
+ */
+ public function reverseTransform($array)
+ {
+ if ('' === $array || null === $array) {
+ $array = array();
+ } else {
+ $array = (array) $array;
+ }
+
+ return new ArrayCollection($array);
+ }
+}
diff --git a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/EntitiesToArrayTransformer.php b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/EntitiesToArrayTransformer.php
deleted file mode 100644
index 753aa59c31..0000000000
--- a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/EntitiesToArrayTransformer.php
+++ /dev/null
@@ -1,98 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
-
-use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
-use Symfony\Component\Form\Exception\UnexpectedTypeException;
-use Symfony\Component\Form\Exception\TransformationFailedException;
-use Symfony\Component\Form\DataTransformerInterface;
-use Doctrine\Common\Collections\Collection;
-use Doctrine\Common\Collections\ArrayCollection;
-
-class EntitiesToArrayTransformer implements DataTransformerInterface
-{
- private $choiceList;
-
- public function __construct(EntityChoiceList $choiceList)
- {
- $this->choiceList = $choiceList;
- }
-
- /**
- * Transforms entities into choice keys.
- *
- * @param Collection|object $collection A collection of entities, a single entity or NULL
- *
- * @return mixed An array of choice keys, a single key or NULL
- */
- public function transform($collection)
- {
- if (null === $collection) {
- return array();
- }
-
- if (!($collection instanceof Collection)) {
- throw new UnexpectedTypeException($collection, 'Doctrine\Common\Collections\Collection');
- }
-
- $array = array();
-
- if (count($this->choiceList->getIdentifier()) > 1) {
- // load all choices
- $availableEntities = $this->choiceList->getEntities();
-
- foreach ($collection as $entity) {
- // identify choices by their collection key
- $key = array_search($entity, $availableEntities, true);
- $array[] = $key;
- }
- } else {
- foreach ($collection as $entity) {
- $value = current($this->choiceList->getIdentifierValues($entity));
- $array[] = is_numeric($value) ? (int) $value : $value;
- }
- }
-
- return $array;
- }
-
- /**
- * Transforms choice keys into entities.
- *
- * @param mixed $keys An array of keys, a single key or NULL
- *
- * @return Collection|object A collection of entities, a single entity or NULL
- */
- public function reverseTransform($keys)
- {
- $collection = new ArrayCollection();
-
- if ('' === $keys || null === $keys) {
- return $collection;
- }
-
- if (!is_array($keys)) {
- throw new UnexpectedTypeException($keys, 'array');
- }
-
- $entities = $this->choiceList->getEntitiesByKeys($keys);
- if (count($keys) !== count($entities)) {
- throw new TransformationFailedException('Not all entities matching the keys were found.');
- }
-
- foreach ($entities as $entity) {
- $collection->add($entity);
- }
-
- return $collection;
- }
-}
diff --git a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/EntityToIdTransformer.php b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/EntityToIdTransformer.php
deleted file mode 100644
index 0de01caca4..0000000000
--- a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/EntityToIdTransformer.php
+++ /dev/null
@@ -1,83 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Bridge\Doctrine\Form\DataTransformer;
-
-use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
-use Symfony\Component\Form\DataTransformerInterface;
-use Symfony\Component\Form\Exception\UnexpectedTypeException;
-use Symfony\Component\Form\Exception\TransformationFailedException;
-use Doctrine\Common\Collections\Collection;
-
-class EntityToIdTransformer implements DataTransformerInterface
-{
- private $choiceList;
-
- public function __construct(EntityChoiceList $choiceList)
- {
- $this->choiceList = $choiceList;
- }
-
- /**
- * Transforms entities into choice keys.
- *
- * @param Collection|object $entity A collection of entities, a single entity or NULL
- *
- * @return mixed An array of choice keys, a single key or NULL
- */
- public function transform($entity)
- {
- if (null === $entity || '' === $entity) {
- return '';
- }
-
- if (!is_object($entity)) {
- throw new UnexpectedTypeException($entity, 'object');
- }
-
- if ($entity instanceof Collection) {
- throw new \InvalidArgumentException('Expected an object, but got a collection. Did you forget to pass "multiple=true" to an entity field?');
- }
-
- if (count($this->choiceList->getIdentifier()) > 1) {
- // load all choices
- $availableEntities = $this->choiceList->getEntities();
-
- return array_search($entity, $availableEntities);
- }
-
- return current($this->choiceList->getIdentifierValues($entity));
- }
-
- /**
- * Transforms choice keys into entities.
- *
- * @param mixed $key An array of keys, a single key or NULL
- *
- * @return Collection|object A collection of entities, a single entity or NULL
- */
- public function reverseTransform($key)
- {
- if ('' === $key || null === $key) {
- return null;
- }
-
- if (count($this->choiceList->getIdentifier()) > 1 && !is_numeric($key)) {
- throw new UnexpectedTypeException($key, 'numeric');
- }
-
- if (!($entities = $this->choiceList->getEntitiesByKeys(array($key)))) {
- throw new TransformationFailedException(sprintf('The entity with key "%s" could not be found', $key));
- }
-
- return $entities[0];
- }
-}
diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
index 7a4c67f3a1..0937c1c001 100644
--- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
+++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php
@@ -17,8 +17,7 @@ use Symfony\Component\Form\FormBuilder;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
use Symfony\Bridge\Doctrine\Form\EventListener\MergeCollectionListener;
-use Symfony\Bridge\Doctrine\Form\DataTransformer\EntitiesToArrayTransformer;
-use Symfony\Bridge\Doctrine\Form\DataTransformer\EntityToIdTransformer;
+use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
use Symfony\Component\Form\AbstractType;
abstract class DoctrineType extends AbstractType
@@ -38,10 +37,8 @@ abstract class DoctrineType extends AbstractType
if ($options['multiple']) {
$builder
->addEventSubscriber(new MergeCollectionListener())
- ->prependClientTransformer(new EntitiesToArrayTransformer($options['choice_list']))
+ ->prependClientTransformer(new CollectionToArrayTransformer())
;
- } else {
- $builder->prependClientTransformer(new EntityToIdTransformer($options['choice_list']));
}
}
@@ -61,6 +58,7 @@ abstract class DoctrineType extends AbstractType
if (!isset($options['choice_list'])) {
$manager = $this->registry->getManager($options['em']);
+
if (isset($options['query_builder'])) {
$options['loader'] = $this->getLoader($manager, $options);
}
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 3ba5fc5f4b..4639a1c408 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
@@ -26,15 +26,15 @@
{% block widget_choice_options %}
{% spaceless %}
- {% for choice, label in options %}
- {% if _form_is_choice_group(label) %}
-
- {% for nestedChoice, nestedLabel in label %}
- {{ nestedLabel|trans({}, translation_domain) }}
+ {% for index, choice in options %}
+ {% if _form_is_choice_group(choice) %}
+
+ {% for nested_index, nested_choice in choice %}
+ {{ choice_labels[nested_index]|trans({}, translation_domain) }}
{% endfor %}
{% else %}
- {{ label|trans({}, translation_domain) }}
+ {{ choice_labels[index]|trans({}, translation_domain) }}
{% endif %}
{% endfor %}
{% endspaceless %}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php
index cb95136e63..12281606d4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_options.html.php
@@ -1,11 +1,11 @@
- $label): ?>
- isChoiceGroup($label)): ?>
-
- $nestedLabel): ?>
- isChoiceSelected($form, $nestedChoice)): ?> selected="selected">escape($view['translator']->trans($nestedLabel, array(), $translation_domain)) ?>
+ $choice): ?>
+ isChoiceGroup($choice)): ?>
+
+ $nested_choice): ?>
+ isChoiceSelected($form, $nested_choice)): ?> selected="selected">escape($view['translator']->trans($choice_labels[$nested_index], array(), $translation_domain)) ?>
- isChoiceSelected($form, $choice)): ?> selected="selected">escape($view['translator']->trans($label, array(), $translation_domain)) ?>
+ isChoiceSelected($form, $choice)): ?> selected="selected">escape($view['translator']->trans($choice_labels[$index], array(), $translation_domain)) ?>
diff --git a/src/Symfony/Component/Form/Exception/StringCastException.php b/src/Symfony/Component/Form/Exception/StringCastException.php
new file mode 100644
index 0000000000..ff882ae8f6
--- /dev/null
+++ b/src/Symfony/Component/Form/Exception/StringCastException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Exception;
+
+class StringCastException extends FormException
+{
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ArrayChoiceList.php
deleted file mode 100644
index 0ab969afe7..0000000000
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ArrayChoiceList.php
+++ /dev/null
@@ -1,69 +0,0 @@
-
- *
- * 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\UnexpectedTypeException;
-
-class ArrayChoiceList implements ChoiceListInterface
-{
- protected $choices;
-
- protected $loaded = false;
-
- /**
- * Constructor.
- *
- * @param array|\Closure $choices An array of choices or a function returning an array
- *
- * @throws UnexpectedTypeException if the type of the choices parameter is not supported
- */
- public function __construct($choices)
- {
- if (!is_array($choices) && !$choices instanceof \Closure) {
- throw new UnexpectedTypeException($choices, 'array or \Closure');
- }
-
- $this->choices = $choices;
- }
-
- /**
- * Returns a list of choices
- *
- * @return array
- */
- public function getChoices()
- {
- if (!$this->loaded) {
- $this->load();
- }
-
- return $this->choices;
- }
-
- /**
- * Initializes the list of choices.
- *
- * @throws UnexpectedTypeException if the function does not return an array
- */
- protected function load()
- {
- $this->loaded = true;
-
- if ($this->choices instanceof \Closure) {
- $this->choices = call_user_func($this->choices);
-
- if (!is_array($this->choices)) {
- throw new UnexpectedTypeException($this->choices, 'array');
- }
- }
- }
-}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
new file mode 100644
index 0000000000..f77eb205be
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
@@ -0,0 +1,649 @@
+
+ *
+ * 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\Util\FormUtil;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+/**
+ * Base class for choice list implementations.
+ *
+ * @author Bernhard Schussek
+ */
+abstract class ChoiceList implements ChoiceListInterface
+{
+ /**
+ * Strategy creating new indices/values by creating a copy of the choice.
+ *
+ * This strategy can only be used for index creation if choices are
+ * guaranteed to only contain ASCII letters, digits and underscores.
+ *
+ * It can be used for value creation if choices can safely be cast into
+ * a (unique) string.
+ *
+ * @var integer
+ */
+ const COPY_CHOICE = 0;
+
+ /**
+ * Strategy creating new indices/values by generating a new integer.
+ *
+ * This strategy can always be applied, but leads to loss of information
+ * in the HTML source code.
+ *
+ * @var integer
+ */
+ const GENERATE = 1;
+
+ /**
+ * The choices with their indices as keys.
+ *
+ * @var array
+ */
+ private $choices = array();
+
+ /**
+ * The choice values with the indices of the matching choices as keys.
+ *
+ * @var array
+ */
+ private $values = array();
+
+ /**
+ * The choice labels with the indices of the matching choices as keys.
+ *
+ * @var array
+ */
+ private $labels = array();
+
+ /**
+ * The preferred values as flat array with the indices of the matching
+ * choices as keys.
+ *
+ * @var array
+ */
+ private $preferredValues = array();
+
+ /**
+ * The non-preferred values as flat array with the indices of the matching
+ * choices as keys.
+ *
+ * @var array
+ */
+ private $remainingValues = array();
+
+ /**
+ * The preferred values as hierarchy containing also the choice groups
+ * with the indices of the matching choices as bottom-level keys.
+ *
+ * @var array
+ */
+ private $preferredValueHierarchy = array();
+
+ /**
+ * The non-preferred values as hierarchy containing also the choice groups
+ * with the indices of the matching choices as bottom-level keys.
+ *
+ * @var array
+ */
+ private $remainingValueHierarchy = array();
+
+ /**
+ * The strategy used for creating choice indices.
+ *
+ * @var integer
+ * @see COPY_CHOICE
+ * @see GENERATE
+ */
+ private $indexStrategy;
+
+ /**
+ * The strategy used for creating choice values.
+ *
+ * @var integer
+ * @see COPY_CHOICE
+ * @see GENERATE
+ */
+ private $valueStrategy;
+
+ /**
+ * 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.
+ * @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 integer $valueStrategy The strategy used to create choice values.
+ * One of COPY_CHOICE and GENERATE.
+ * @param integer $indexStrategy The strategy used to create choice indices.
+ * One of COPY_CHOICE and GENERATE.
+ */
+ public function __construct($choices, array $labels,
+ array $preferredChoices = array(), $valueStrategy, $indexStrategy)
+ {
+ $this->valueStrategy = $valueStrategy;
+ $this->indexStrategy = $indexStrategy;
+
+ $this->initialize($choices, $labels, $preferredChoices);
+ }
+
+ /**
+ * Initializes the list with choices.
+ *
+ * Safe to be called multiple times. The list is cleared on every call.
+ *
+ * @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.
+ */
+ protected function initialize($choices, array $labels, array $preferredChoices)
+ {
+ if (!is_array($choices) && !$choices instanceof \Traversable) {
+ throw new UnexpectedTypeException($choices, 'array or \Traversable');
+ }
+
+ $this->choices = array();
+ $this->values = array();
+ $this->labels = array();
+ $this->preferredValues = array();
+ $this->preferredValueHierarchy = array();
+ $this->remainingValues = array();
+ $this->remainingValueHierarchy = array();
+
+ $this->addChoices(
+ $this->preferredValueHierarchy,
+ $this->remainingValueHierarchy,
+ $choices,
+ $labels,
+ $preferredChoices
+ );
+ }
+
+ /**
+ * Returns the list of choices
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getChoices()
+ {
+ return $this->choices;
+ }
+
+ /**
+ * Returns the labels for the choices
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getLabels()
+ {
+ return $this->labels;
+ }
+
+ /**
+ * Returns the values for the choices
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Returns the values of the choices that should be presented to the user
+ * with priority.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getPreferredValues()
+ {
+ return $this->preferredValues;
+ }
+
+ /**
+ * Returns the values of the choices that should be presented to the user
+ * with priority as nested array with the choice groups as top-level keys.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getPreferredValueHierarchy()
+ {
+ return $this->preferredValueHierarchy;
+ }
+
+ /**
+ * Returns the values of the choices that are not preferred.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getRemainingValues()
+ {
+ return $this->remainingValues;
+ }
+
+ /**
+ * Returns the values of the choices that are not preferred as nested array
+ * with the choice groups as top-level keys.
+ *
+ * @return array A nested array containing the values with the corresponding
+ * choice indices as keys on the lower levels and the choice
+ * group names in the keys of the topmost level.
+ *
+ * @see getPreferredValueHierarchy
+ */
+ public function getRemainingValueHierarchy()
+ {
+ return $this->remainingValueHierarchy;
+ }
+
+ /**
+ * Returns the choices corresponding to the given values.
+ *
+ * @param array $values
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getChoicesForValues(array $values)
+ {
+ $values = $this->fixValues($values);
+
+ // If the values are identical to the choices, we can just return them
+ // to improve performance a little bit
+ if ($this->valueStrategy === self::COPY_CHOICE) {
+ return $this->fixChoices(array_intersect($values, $this->values));
+ }
+
+ $choices = array();
+
+ foreach ($this->values as $i => $value) {
+ foreach ($values as $j => $givenValue) {
+ if ($value === $givenValue) {
+ $choices[] = $this->choices[$i];
+ unset($values[$j]);
+
+ if (count($values) === 0) {
+ break 2;
+ }
+ }
+ }
+ }
+
+ return $choices;
+ }
+
+ /**
+ * Returns the values corresponding to the given choices.
+ *
+ * @param array $choices
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getValuesForChoices(array $choices)
+ {
+ $choices = $this->fixChoices($choices);
+
+ // If the values are identical to the choices, we can just return them
+ // to improve performance a little bit
+ if ($this->valueStrategy === self::COPY_CHOICE) {
+ return $this->fixValues(array_intersect($choices, $this->choices));
+ }
+
+ $values = array();
+
+ foreach ($this->choices as $i => $choice) {
+ foreach ($choices as $j => $givenChoice) {
+ if ($choice === $givenChoice) {
+ $values[] = $this->values[$i];
+ unset($choices[$j]);
+
+ if (count($choices) === 0) {
+ break 2;
+ }
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns the indices corresponding to the given choices.
+ *
+ * @param array $choices
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getIndicesForChoices(array $choices)
+ {
+ $choices = $this->fixChoices($choices);
+ $indices = array();
+
+ foreach ($this->choices as $i => $choice) {
+ foreach ($choices as $j => $givenChoice) {
+ if ($choice === $this->fixChoice($givenChoice)) {
+ $indices[] = $i;
+ unset($choices[$j]);
+
+ if (count($choices) === 0) {
+ break 2;
+ }
+ }
+ }
+ }
+
+ return $indices;
+ }
+
+ /**
+ * Returns the indices corresponding to the given values.
+ *
+ * @param array $values
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getIndicesForValues(array $values)
+ {
+ $values = $this->fixValues($values);
+ $indices = array();
+
+ foreach ($this->values as $i => $value) {
+ foreach ($values as $j => $givenValue) {
+ if ($value === $givenValue) {
+ $indices[] = $i;
+ unset($values[$j]);
+
+ if (count($values) === 0) {
+ break 2;
+ }
+ }
+ }
+ }
+
+ return $indices;
+ }
+
+ /**
+ * Recursively adds the given choices to the list.
+ *
+ * @param array $bucketForPreferred The bucket where to store the preferred
+ * values.
+ * @param array $bucketForRemaining The bucket where to store the
+ * non-preferred values.
+ * @param array $choices The list of choices.
+ * @param array $labels The labels corresponding to the choices.
+ * @param array $preferredChoices The preferred choices.
+ *
+ * @throws UnexpectedTypeException If the structure of the $labels array
+ * does not match the structure of the
+ * $choices array.
+ */
+ protected function addChoices(&$bucketForPreferred, &$bucketForRemaining, $choices, $labels, array $preferredChoices)
+ {
+ // Add choices to the nested buckets
+ foreach ($choices as $group => $choice) {
+ if (is_array($choice)) {
+ if (!is_array($labels)) {
+ throw new UnexpectedTypeException($labels, 'array');
+ }
+
+ // Don't do the work if the array is empty
+ if (count($choice) > 0) {
+ $this->addChoiceGroup(
+ $group,
+ $bucketForPreferred,
+ $bucketForRemaining,
+ $choice,
+ $labels[$group],
+ $preferredChoices
+ );
+ }
+ } else {
+ $this->addChoice(
+ $bucketForPreferred,
+ $bucketForRemaining,
+ $choice,
+ $labels[$group],
+ $preferredChoices
+ );
+ }
+ }
+ }
+
+ /**
+ * Recursively adds a choice group.
+ *
+ * @param string $group The name of the group.
+ * @param array $bucketForPreferred The bucket where to store the preferred
+ * values.
+ * @param array $bucketForRemaining The bucket where to store the
+ * non-preferred values.
+ * @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.
+ */
+ protected function addChoiceGroup($group, &$bucketForPreferred, &$bucketForRemaining, $choices, $labels, array $preferredChoices)
+ {
+ // If this is a choice group, create a new level in the choice
+ // key hierarchy
+ $bucketForPreferred[$group] = array();
+ $bucketForRemaining[$group] = array();
+
+ $this->addChoices(
+ $bucketForPreferred[$group],
+ $bucketForRemaining[$group],
+ $choices,
+ $labels,
+ $preferredChoices
+ );
+
+ // Remove child levels if empty
+ if (empty($bucketForPreferred[$group])) {
+ unset($bucketForPreferred[$group]);
+ }
+ if (empty($bucketForRemaining[$group])) {
+ unset($bucketForRemaining[$group]);
+ }
+ }
+
+ /**
+ * Adds a new choice.
+ *
+ * @param array $bucketForPreferred The bucket where to store the preferred
+ * values.
+ * @param array $bucketForRemaining The bucket where to store the
+ * non-preferred values.
+ * @param mixed $choice The choice to add.
+ * @param string $label The label for the choice.
+ * @param array $preferredChoices The preferred choices.
+ */
+ protected function addChoice(&$bucketForPreferred, &$bucketForRemaining, $choice, $label, array $preferredChoices)
+ {
+ $index = $this->createIndex($choice);
+
+ // Always store values as strings to facilitate comparisons
+ $value = $this->fixValue($this->createValue($choice));
+
+ $this->choices[$index] = $this->fixChoice($choice);
+ $this->labels[$index] = $label;
+ $this->values[$index] = $value;
+
+ if ($this->isPreferred($choice, $preferredChoices)) {
+ $bucketForPreferred[$index] = $value;
+ $this->preferredValues[$index] = $value;
+ } else {
+ $bucketForRemaining[$index] = $value;
+ $this->remainingValues[$index] = $value;
+ }
+ }
+
+ /**
+ * Returns whether the given choice should be preferred judging by the
+ * given array of preferred choices.
+ *
+ * Extension point to optimize performance by changing the structure of the
+ * $preferredChoices array.
+ *
+ * @param mixed $choice The choice to test.
+ * @param array $preferredChoices An array of preferred choices.
+ */
+ protected function isPreferred($choice, $preferredChoices)
+ {
+ return false !== array_search($choice, $preferredChoices, true);
+ }
+
+ /**
+ * Creates a new unique index for this choice.
+ *
+ * Extension point to change the indexing strategy.
+ *
+ * @param mixed $choice The choice to create an index for
+ *
+ * @return integer|string A unique index containing only ASCII letters,
+ * digits and underscores.
+ */
+ protected function createIndex($choice)
+ {
+ if ($this->indexStrategy === self::COPY_CHOICE) {
+ return $choice;
+ }
+
+ return count($this->choices);
+ }
+
+ /**
+ * Creates a new unique value for this choice.
+ *
+ * Extension point to change the value strategy.
+ *
+ * @param mixed $choice The choice to create a value for
+ *
+ * @return integer|string A unique value without character limitations.
+ */
+ protected function createValue($choice)
+ {
+ if ($this->valueStrategy === self::COPY_CHOICE) {
+ return $choice;
+ }
+
+ return count($this->values);
+ }
+
+ /**
+ * Fixes the data type of the given choice value to avoid comparison
+ * problems.
+ *
+ * @param mixed $value The choice value.
+ *
+ * @return string The value as string.
+ */
+ protected function fixValue($value)
+ {
+ return (string) $value;
+ }
+
+ /**
+ * Fixes the data types of the given choice values to avoid comparison
+ * problems.
+ *
+ * @param array $values The choice values.
+ *
+ * @return array The values as strings.
+ */
+ protected function fixValues(array $values)
+ {
+ return array_map(array($this, 'fixValue'), $values);
+ }
+
+ /**
+ * Fixes the data type of the given choice index to avoid comparison
+ * problems.
+ *
+ * @param mixed $index The choice index.
+ *
+ * @return integer|string The index as PHP array key.
+ */
+ protected function fixIndex($index)
+ {
+ if (is_bool($index) || (string) (int) $index === (string) $index) {
+ return (int) $index;
+ }
+
+ return (string) $index;
+ }
+
+ /**
+ * Fixes the data types of the given choice indices to avoid comparison
+ * problems.
+ *
+ * @param array $indices The choice indices.
+ *
+ * @return array The indices as strings.
+ */
+ protected function fixIndices(array $indices)
+ {
+ return array_map(array($this, 'fixIndex'), $indices);
+ }
+
+ /**
+ * Fixes the data type of the given choice to avoid comparison problems.
+ *
+ * Extension point. In this implementation, choices are guaranteed to
+ * always maintain their type and thus can be typesafely compared.
+ *
+ * @param mixed $choice The choice.
+ *
+ * @return mixed The fixed choice.
+ */
+ protected function fixChoice($choice)
+ {
+ return $choice;
+ }
+
+ /**
+ * Fixes the data type of the given choices to avoid comparison problems.
+ *
+ * @param array $choice The choices.
+ *
+ * @return array The fixed choices.
+ *
+ * @see fixChoice
+ */
+ protected function fixChoices(array $choices)
+ {
+ return $choices;
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php
index f9d0ab877e..85df8d8d88 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php
@@ -11,12 +11,126 @@
namespace Symfony\Component\Form\Extension\Core\ChoiceList;
+/**
+ * Contains choices that can be selected in a form field.
+ *
+ * Each choice has four different properties:
+ *
+ * - Choice: The choice that should be returned to the application by the
+ * choice field. Can be any scalar value or an object, but no
+ * array.
+ * - Label: A text representing the choice that is displayed to the user.
+ * - Index: A uniquely identifying index that should only contain ASCII
+ * characters, digits and underscores. This index is used to
+ * identify the choice in the HTML "id" and "name" attributes.
+ * It is also used as index of the arrays returned by the various
+ * getters of this class.
+ * - Value: A uniquely identifying value that can contain arbitrary
+ * characters, but no arrays or objects. This value is displayed
+ * in the HTML "value" attribute.
+ *
+ * @author Bernhard Schussek
+ */
interface ChoiceListInterface
{
/**
- * Returns a list of choices
+ * Returns the list of choices
*
- * @return array
+ * @return array The choices with their indices as keys.
*/
function getChoices();
+
+ /**
+ * Returns the labels for the choices
+ *
+ * @return array The labels with the corresponding choice indices as keys.
+ */
+ function getLabels();
+
+ /**
+ * Returns the values for the choices
+ *
+ * @return array The values with the corresponding choice indices as keys.
+ */
+ function getValues();
+
+ /**
+ * Returns the values of the choices that should be presented to the user
+ * with priority.
+ *
+ * @return array The values with the corresponding choice indices as keys.
+ */
+ function getPreferredValues();
+
+ /**
+ * Returns the values of the choices that should be presented to the user
+ * with priority as nested array with the choice groups as top-level keys.
+ *
+ * @return array A nested array containing the values with the corresponding
+ * choice indices as keys on the lower levels and the choice
+ * group names in the keys of the topmost level.
+ */
+ function getPreferredValueHierarchy();
+
+ /**
+ * Returns the values of the choices that are not preferred.
+ *
+ * @return array The values with the corresponding choice indices as keys.
+ *
+ * @see getPreferredValues
+ */
+ function getRemainingValues();
+
+ /**
+ * Returns the values of the choices that are not preferred as nested array
+ * with the choice groups as top-level keys.
+ *
+ * @return array A nested array containing the values with the corresponding
+ * choice indices as keys on the lower levels and the choice
+ * group names in the keys of the topmost level.
+ *
+ * @see getPreferredValueHierarchy
+ */
+ function getRemainingValueHierarchy();
+
+ /**
+ * Returns the choices corresponding to the given values.
+ *
+ * @param array $values An array of choice values. Not existing values in
+ * this array are ignored.
+ *
+ * @return array An array of choices with ascending, 0-based numeric keys
+ */
+ function getChoicesForValues(array $values);
+
+ /**
+ * Returns the values corresponding to the given choices.
+ *
+ * @param array $choices An array of choices. Not existing choices in this
+ * array are ignored.
+ *
+ * @return array An array of choice values with ascending, 0-based numeric
+ * keys
+ */
+ function getValuesForChoices(array $choices);
+
+ /**
+ * Returns the indices corresponding to the given choices.
+ *
+ * @param array $choices An array of choices. Not existing choices in this
+ * array are ignored.
+ *
+ * @return array An array of indices with ascending, 0-based numeric keys
+ */
+ function getIndicesForChoices(array $choices);
+
+ /**
+ * Returns the indices corresponding to the given values.
+ *
+ * @param array $values An array of choice values. Not existing values in
+ * this array are ignored.
+ *
+ * @return array An array of indices with ascending, 0-based numeric keys
+ */
+ function getIndicesForValues(array $values);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ComplexChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ComplexChoiceList.php
new file mode 100644
index 0000000000..3528042387
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ComplexChoiceList.php
@@ -0,0 +1,44 @@
+
+ *
+ * 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\Util\FormUtil;
+
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+/**
+ * A choice list that can store arbitrary scalar and object choices.
+ *
+ * Arrays as choices are not supported.
+ *
+ * @author Bernhard Schussek
+ */
+class ComplexChoiceList extends ChoiceList
+{
+ /**
+ * Creates a new complex choice list.
+ *
+ * @param array $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.
+ * @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.
+ */
+ public function __construct(array $choices, array $labels, array $preferredChoices = array())
+ {
+ parent::__construct($choices, $labels, $preferredChoices, self::GENERATE, self::GENERATE);
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/MonthChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/MonthChoiceList.php
deleted file mode 100644
index fc2f73a588..0000000000
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/MonthChoiceList.php
+++ /dev/null
@@ -1,58 +0,0 @@
-
- *
- * 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;
-
-class MonthChoiceList extends PaddedChoiceList
-{
- private $formatter;
-
- /**
- * Generates an array of localized month choices.
- *
- * @param IntlDateFormatter $formatter An IntlDateFormatter instance
- * @param array $months The month numbers to generate
- */
- public function __construct(\IntlDateFormatter $formatter, array $months)
- {
- parent::__construct(array_combine($months, $months), 2, '0', STR_PAD_LEFT);
- $this->formatter = $formatter;
- }
-
- /**
- * Initializes the list of months.
- *
- * @throws UnexpectedTypeException if the function does not return an array
- */
- protected function load()
- {
- parent::load();
-
- $pattern = $this->formatter->getPattern();
- $timezone = $this->formatter->getTimezoneId();
-
- $this->formatter->setTimezoneId(\DateTimeZone::UTC);
-
- if (preg_match('/M+/', $pattern, $matches)) {
- $this->formatter->setPattern($matches[0]);
-
- foreach ($this->choices as $choice => $value) {
- $this->choices[$choice] = $this->formatter->format(gmmktime(0, 0, 0, $value, 15));
- }
-
- // I'd like to clone the formatter above, but then we get a
- // segmentation fault, so let's restore the old state instead
- $this->formatter->setPattern($pattern);
- }
-
- $this->formatter->setTimezoneId($timezone);
- }
-}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
new file mode 100644
index 0000000000..08225ba6a0
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php
@@ -0,0 +1,201 @@
+
+*
+* 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\Util\PropertyPath;
+use Symfony\Component\Form\Exception\StringCastException;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\InvalidPropertyException;
+
+/**
+ * A choice list that can store object choices.
+ *
+ * Supports generation of choice labels, choice groups, choice values and
+ * choice indices by introspecting the properties of the object (or
+ * associated objects).
+ *
+ * @author Bernhard Schussek
+ */
+class ObjectChoiceList extends ChoiceList
+{
+ /**
+ * The property path used to obtain the choice label.
+ *
+ * @var PropertyPath
+ */
+ private $labelPath;
+
+ /**
+ * The property path used for object grouping.
+ *
+ * @var PropertyPath
+ */
+ private $groupPath;
+
+ /**
+ * The property path used to obtain the choice value.
+ *
+ * @var PropertyPath
+ */
+ private $valuePath;
+
+ /**
+ * The property path used to obtain the choice index.
+ *
+ * @var PropertyPath
+ */
+ private $indexPath;
+
+ /**
+ * Creates a new object choice list.
+ *
+ * @param array $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.
+ * @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
+ * path is NULL, the object's __toString() method
+ * is used instead.
+ * @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
+ * 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
+ * for the choice values. If not given, integers
+ * are generated instead.
+ * @param string $indexPath A property path pointing to the property used
+ * for the choice indices. If not given, integers
+ * are generated instead.
+ */
+ public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, $indexPath = null)
+ {
+ $this->labelPath = $labelPath ? new PropertyPath($labelPath) : null;
+ $this->groupPath = $groupPath ? new PropertyPath($groupPath) : null;
+ $this->valuePath = $valuePath ? new PropertyPath($valuePath) : null;
+ $this->indexPath = $indexPath ? new PropertyPath($indexPath) : null;
+
+ parent::__construct($choices, array(), $preferredChoices, self::GENERATE, self::GENERATE);
+ }
+
+ /**
+ * Initializes the list with choices.
+ *
+ * Safe to be called multiple times. The list is cleared on every call.
+ *
+ * @param array|\Traversable $choices The choices to write into the list.
+ * @param array $labels Ignored.
+ * @param array $preferredChoices The choices to display with priority.
+ */
+ protected function initialize($choices, array $labels, array $preferredChoices)
+ {
+ if (!is_array($choices) && !$choices instanceof \Traversable) {
+ throw new UnexpectedTypeException($choices, 'array or \Traversable');
+ }
+
+ if ($this->groupPath !== null) {
+ $groupedChoices = array();
+
+ foreach ($choices as $i => $choice) {
+ if (is_array($choice)) {
+ throw new \InvalidArgumentException('You should pass a plain object array (without groups, $code, $previous) when using the "groupPath" option');
+ }
+
+ try {
+ $group = $this->groupPath->getValue($choice);
+ } catch (InvalidPropertyException $e) {
+ // Don't group items whose group property does not exist
+ // see https://github.com/symfony/symfony/commit/d9b7abb7c7a0f28e0ce970afc5e305dce5dccddf
+ $group = null;
+ }
+
+ if ($group === null) {
+ $groupedChoices[$i] = $choice;
+ } else {
+ if (!isset($groupedChoices[$group])) {
+ $groupedChoices[$group] = array();
+ }
+
+ $groupedChoices[$group][$i] = $choice;
+ }
+ }
+
+ $choices = $groupedChoices;
+ }
+
+ $labels = array();
+
+ $this->extractLabels($choices, $labels);
+
+ parent::initialize($choices, $labels, $preferredChoices);
+ }
+
+ /**
+ * Creates a new unique index for this choice.
+ *
+ * If a property path for the index was given at object creation,
+ * the getter behind that path is now called to obtain a new value.
+ *
+ * Otherwise a new integer is generated.
+ *
+ * @param mixed $choice The choice to create an index for
+ * @return integer|string A unique index containing only ASCII letters,
+ * digits and underscores.
+ */
+ protected function createIndex($choice)
+ {
+ if ($this->indexPath) {
+ return $this->indexPath->getValue($choice);
+ }
+
+ return parent::createIndex($choice);
+ }
+
+ /**
+ * Creates a new unique value for this choice.
+ *
+ * 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.
+ *
+ * Otherwise a new integer is generated.
+ *
+ * @param mixed $choice The choice to create a value for
+ * @return integer|string A unique value without character limitations.
+ */
+ protected function createValue($choice)
+ {
+ if ($this->valuePath) {
+ return $this->valuePath->getValue($choice);
+ }
+
+ return parent::createValue($choice);
+ }
+
+ private function extractLabels($choices, array &$labels)
+ {
+ foreach ($choices as $i => $choice) {
+ if (is_array($choice) || $choice instanceof \Traversable) {
+ $labels[$i] = array();
+ $this->extractLabels($choice, $labels[$i]);
+ } elseif ($this->labelPath) {
+ $labels[$i] = $this->labelPath->getValue($choice);
+ } elseif (method_exists($choice, '__toString')) {
+ $labels[$i] = (string) $choice;
+ } else {
+ throw new StringCastException('Objects passed to the choice field must have a "__toString()" method defined. Alternatively you can set the $labelPath argument to choose the property used as label.');
+ }
+ }
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/PaddedChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/PaddedChoiceList.php
deleted file mode 100644
index fd1f9d1b33..0000000000
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/PaddedChoiceList.php
+++ /dev/null
@@ -1,59 +0,0 @@
-
- *
- * 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;
-
-class PaddedChoiceList extends ArrayChoiceList
-{
- private $padLength;
-
- private $padString;
-
- private $padType;
-
- /**
- * Generates an array of choices for the given values
- *
- * If the values are shorter than $padLength characters, they are padded with
- * zeros on the left side.
- *
- * @param array|\Closure $values The available choices
- * @param integer $padLength The length to pad the choices
- * @param string $padString The padding character
- * @param integer $padType The direction of padding
- *
- * @throws UnexpectedTypeException if the type of the values parameter is not supported
- */
- public function __construct($values, $padLength, $padString, $padType = STR_PAD_LEFT)
- {
- parent::__construct($values);
-
- $this->padLength = $padLength;
- $this->padString = $padString;
- $this->padType = $padType;
- }
-
- /**
- * Initializes the list of choices.
- *
- * Each choices is padded according to the format given in the constructor
- *
- * @throws UnexpectedTypeException if the function does not return an array
- */
- protected function load()
- {
- parent::load();
-
- foreach ($this->choices as $key => $choice) {
- $this->choices[$key] = str_pad($choice, $this->padLength, $this->padString, $this->padType);
- }
- }
-}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
new file mode 100644
index 0000000000..16845d648d
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php
@@ -0,0 +1,133 @@
+
+ *
+ * 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\Util\FormUtil;
+
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+
+/**
+ * A choice list that can store any choices that are allowed as PHP array keys.
+ *
+ * The value strategy of simple choice lists is fixed to ChoiceList::COPY_CHOICE,
+ * since array keys are always valid choice values.
+ *
+ * @author Bernhard Schussek
+ */
+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. Hierarchies are
+ * created 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 integer $indexStrategy The strategy used to create choice indices.
+ * One of COPY_CHOICE and GENERATE.
+ */
+ public function __construct(array $choices, array $preferredChoices = array(),
+ $valueStrategy = self::COPY_CHOICE, $indexStrategy = self::GENERATE)
+ {
+ // Flip preferred choices to speed up lookup
+ parent::__construct($choices, $choices, array_flip($preferredChoices), $valueStrategy, $indexStrategy);
+ }
+
+ /**
+ * Recursively adds the given choices to the list.
+ *
+ * Takes care of splitting the single $choices array passed in the
+ * constructor into choices and labels.
+ *
+ * @param array $bucketForPreferred
+ * @param array $bucketForRemaining
+ * @param array $choices
+ * @param array $labels
+ * @param array $preferredChoices
+ *
+ * @throws UnexpectedTypeException
+ *
+ * @see parent::addChoices
+ */
+ protected function addChoices(&$bucketForPreferred, &$bucketForRemaining, $choices, $labels, array $preferredChoices)
+ {
+ // Add choices to the nested buckets
+ foreach ($choices as $choice => $label) {
+ if (is_array($label)) {
+ // Don't do the work if the array is empty
+ if (count($label) > 0) {
+ $this->addChoiceGroup(
+ $choice,
+ $bucketForPreferred,
+ $bucketForRemaining,
+ $label,
+ $label,
+ $preferredChoices
+ );
+ }
+ } else {
+ $this->addChoice(
+ $bucketForPreferred,
+ $bucketForRemaining,
+ $choice,
+ $label,
+ $preferredChoices
+ );
+ }
+ }
+ }
+
+ /**
+ * Returns whether the given choice should be preferred judging by the
+ * given array of preferred choices.
+ *
+ * Optimized for performance by treating the preferred choices as array
+ * where choices are stored in the keys.
+ *
+ * @param mixed $choice The choice to test.
+ * @param array $preferredChoices An array of preferred choices.
+ */
+ protected function isPreferred($choice, $preferredChoices)
+ {
+ // Optimize performance over the default implementation
+ return isset($preferredChoices[$choice]);
+ }
+
+ /**
+ * Converts the choice to a valid PHP array key.
+ *
+ * @param mixed $choice The choice.
+ *
+ * @return string|integer A valid PHP array key.
+ */
+ protected function fixChoice($choice)
+ {
+ return $this->fixIndex($choice);
+ }
+
+
+ /**
+ * Converts the choices to a valid PHP array keys.
+ *
+ * @param array $choices The choices.
+ *
+ * @return array Valid PHP array keys.
+ */
+ protected function fixChoices(array $choices)
+ {
+ return $this->fixIndices($choices);
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/TimezoneChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/TimezoneChoiceList.php
deleted file mode 100644
index 57e7bb5863..0000000000
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/TimezoneChoiceList.php
+++ /dev/null
@@ -1,63 +0,0 @@
-
- *
- * 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;
-
-/**
- * Represents a choice list where each timezone is broken down by continent.
- *
- * @author Bernhard Schussek
- */
-class TimezoneChoiceList implements ChoiceListInterface
-{
- /**
- * Stores the available timezone choices
- * @var array
- */
- static protected $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
- */
- public function getChoices()
- {
- if (null !== static::$timezones) {
- return static::$timezones;
- }
-
- static::$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];
- }
-
- static::$timezones[$region][$timezone] = str_replace('_', ' ', $name);
- }
-
- return static::$timezones;
- }
-}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ScalarToBooleanChoicesTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php
similarity index 69%
rename from src/Symfony/Component/Form/Extension/Core/DataTransformer/ScalarToBooleanChoicesTransformer.php
rename to src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php
index 439acfb0a6..4b92f0d788 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ScalarToBooleanChoicesTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php
@@ -17,7 +17,10 @@ use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Form\Util\FormUtil;
-class ScalarToBooleanChoicesTransformer implements DataTransformerInterface
+/**
+ * @author Bernhard Schussek
+ */
+class ChoiceToBooleanArrayTransformer implements DataTransformerInterface
{
private $choiceList;
@@ -47,24 +50,21 @@ class ScalarToBooleanChoicesTransformer implements DataTransformerInterface
* @throws UnexpectedTypeException if the given value is not scalar
* @throws TransformationFailedException if the choices can not be retrieved
*/
- public function transform($value)
+ public function transform($choice)
{
- if (!is_scalar($value) && null !== $value) {
- throw new UnexpectedTypeException($value, 'scalar');
- }
-
try {
- $choices = $this->choiceList->getChoices();
+ $values = $this->choiceList->getValues();
} catch (\Exception $e) {
throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e);
}
- $value = FormUtil::toArrayKey($value);
- foreach (array_keys($choices) as $key) {
- $choices[$key] = $key === $value;
+ $index = current($this->choiceList->getIndicesForChoices(array($choice)));
+
+ foreach ($values as $i => $value) {
+ $values[$i] = $i === $index;
}
- return $choices;
+ return $values;
}
/**
@@ -80,15 +80,25 @@ class ScalarToBooleanChoicesTransformer implements DataTransformerInterface
*
* @throws new UnexpectedTypeException if the given value is not an array
*/
- public function reverseTransform($value)
+ public function reverseTransform($values)
{
- if (!is_array($value)) {
- throw new UnexpectedTypeException($value, 'array');
+ if (!is_array($values)) {
+ throw new UnexpectedTypeException($values, 'array');
}
- foreach ($value as $choice => $selected) {
+ try {
+ $choices = $this->choiceList->getChoices();
+ } catch (\Exception $e) {
+ throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e);
+ }
+
+ foreach ($values as $i => $selected) {
if ($selected) {
- return (string) $choice;
+ if (isset($choices[$i])) {
+ return $choices[$i] === '' ? null : $choices[$i];
+ } else {
+ throw new TransformationFailedException('The choice "' . $i . '" does not exist');
+ }
}
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php
new file mode 100644
index 0000000000..e63e0f1941
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php
@@ -0,0 +1,63 @@
+
+ *
+ * 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\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
+
+/**
+ * @author Bernhard Schussek
+ */
+class ChoiceToValueTransformer implements DataTransformerInterface
+{
+ private $choiceList;
+
+ /**
+ * Constructor.
+ *
+ * @param ChoiceListInterface $choiceList
+ */
+ public function __construct(ChoiceListInterface $choiceList)
+ {
+ $this->choiceList = $choiceList;
+ }
+
+ public function transform($choice)
+ {
+ return (string) current($this->choiceList->getValuesForChoices(array($choice)));
+ }
+
+ public function reverseTransform($value)
+ {
+ if (null !== $value && !is_scalar($value)) {
+ throw new UnexpectedTypeException($value, 'scalar');
+ }
+
+ // These are now valid ChoiceList values, so we can return null
+ // right away
+ if ($value === '' || $value === null) {
+ return null;
+ }
+
+ $choices = $this->choiceList->getChoicesForValues(array($value));
+
+ if (count($choices) !== 1) {
+ throw new TransformationFailedException('The choice "' . $value . '" does not exist');
+ }
+
+ $choice = current($choices);
+
+ return $choice === '' ? null : $choice;
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToBooleanChoicesTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php
similarity index 66%
rename from src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToBooleanChoicesTransformer.php
rename to src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php
index 11b00d94a6..eafc6c3f82 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToBooleanChoicesTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php
@@ -16,7 +16,10 @@ use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
-class ArrayToBooleanChoicesTransformer implements DataTransformerInterface
+/**
+ * @author Bernhard Schussek
+ */
+class ChoicesToBooleanArrayTransformer implements DataTransformerInterface
{
private $choiceList;
@@ -51,16 +54,18 @@ class ArrayToBooleanChoicesTransformer implements DataTransformerInterface
}
try {
- $choices = $this->choiceList->getChoices();
+ $values = $this->choiceList->getValues();
} catch (\Exception $e) {
throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e);
}
- foreach (array_keys($choices) as $key) {
- $choices[$key] = in_array($key, $array, true);
+ $indexMap = array_flip($this->choiceList->getIndicesForChoices($array));
+
+ foreach ($values as $i => $value) {
+ $values[$i] = isset($indexMap[$i]);
}
- return $choices;
+ return $values;
}
/**
@@ -76,20 +81,35 @@ class ArrayToBooleanChoicesTransformer implements DataTransformerInterface
*
* @throws UnexpectedTypeException if the given value is not an array
*/
- public function reverseTransform($value)
+ public function reverseTransform($values)
{
- if (!is_array($value)) {
- throw new UnexpectedTypeException($value, 'array');
+ if (!is_array($values)) {
+ throw new UnexpectedTypeException($values, 'array');
}
- $choices = array();
+ try {
+ $choices = $this->choiceList->getChoices();
+ } catch (\Exception $e) {
+ throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e);
+ }
- foreach ($value as $choice => $selected) {
+ $result = array();
+ $unknown = array();
+
+ foreach ($values as $i => $selected) {
if ($selected) {
- $choices[] = $choice;
+ if (isset($choices[$i])) {
+ $result[] = $choices[$i];
+ } else {
+ $unknown[] = $i;
+ }
}
}
- return $choices;
+ if (count($unknown) > 0) {
+ throw new TransformationFailedException('The choices "' . implode('", "', $unknown, $code, $previous) . '" where not found');
+ }
+
+ return $result;
}
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToChoicesTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php
similarity index 59%
rename from src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToChoicesTransformer.php
rename to src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php
index 49e8f088df..e722c47065 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToChoicesTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php
@@ -11,12 +11,30 @@
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
+use Symfony\Component\Form\Exception\TransformationFailedException;
+
use Symfony\Component\Form\Util\FormUtil;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
-class ArrayToChoicesTransformer implements DataTransformerInterface
+/**
+ * @author Bernhard Schussek
+ */
+class ChoicesToValuesTransformer implements DataTransformerInterface
{
+ private $choiceList;
+
+ /**
+ * Constructor.
+ *
+ * @param ChoiceListInterface $choiceList
+ */
+ public function __construct(ChoiceListInterface $choiceList)
+ {
+ $this->choiceList = $choiceList;
+ }
+
/**
* @param array $array
*
@@ -34,7 +52,7 @@ class ArrayToChoicesTransformer implements DataTransformerInterface
throw new UnexpectedTypeException($array, 'array');
}
- return FormUtil::toArrayKeys($array);
+ return $this->choiceList->getValuesForChoices($array);
}
/**
@@ -54,6 +72,12 @@ class ArrayToChoicesTransformer implements DataTransformerInterface
throw new UnexpectedTypeException($array, 'array');
}
- return $array;
+ $choices = $this->choiceList->getChoicesForValues($array);
+
+ if (count($choices) !== count($array)) {
+ throw new TransformationFailedException('Could not find all matching choices for the given values');
+ }
+
+ return $choices;
}
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ScalarToChoiceTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/ScalarToChoiceTransformer.php
deleted file mode 100644
index c6c6953586..0000000000
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/ScalarToChoiceTransformer.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
- *
- * 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\Util\FormUtil;
-use Symfony\Component\Form\DataTransformerInterface;
-use Symfony\Component\Form\Exception\UnexpectedTypeException;
-
-class ScalarToChoiceTransformer implements DataTransformerInterface
-{
- public function transform($value)
- {
- if (null !== $value && !is_scalar($value)) {
- throw new UnexpectedTypeException($value, 'scalar');
- }
-
- return FormUtil::toArrayKey($value);
- }
-
- public function reverseTransform($value)
- {
- if (null !== $value && !is_scalar($value)) {
- throw new UnexpectedTypeException($value, 'scalar');
- }
-
- return $value;
- }
-}
diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php
index ddf474781c..e094a77a38 100644
--- a/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php
+++ b/src/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\Form\Extension\Core\EventListener;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
/**
* Takes care of converting the input from a single radio button
@@ -23,11 +24,24 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
*/
class FixRadioInputListener implements EventSubscriberInterface
{
+ private $choiceList;
+
+ /**
+ * Constructor.
+ *
+ * @param ChoiceListInterface $choiceList
+ */
+ public function __construct(ChoiceListInterface $choiceList)
+ {
+ $this->choiceList = $choiceList;
+ }
+
public function onBindClientData(FilterDataEvent $event)
{
- $data = $event->getData();
+ $value = $event->getData();
+ $index = current($this->choiceList->getIndicesForValues(array($value)));
- $event->setData(strlen($data) < 1 ? array() : array($data => true));
+ $event->setData($index !== false ? array($index => $value) : array());
}
static public function getSubscribedEvents()
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index bcc119fee8..378a795e6b 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -15,14 +15,17 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Exception\FormException;
-use Symfony\Component\Form\Extension\Core\ChoiceList\ArrayChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\Loader\ChoiceListLoaderInterface;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
use Symfony\Component\Form\FormView;
-use Symfony\Component\Form\Extension\Core\DataTransformer\ScalarToChoiceTransformer;
-use Symfony\Component\Form\Extension\Core\DataTransformer\ScalarToBooleanChoicesTransformer;
-use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToChoicesTransformer;
-use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToBooleanChoicesTransformer;
+use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
+use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer;
+use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
+use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer;
class ChoiceType extends AbstractType
{
@@ -35,40 +38,38 @@ class ChoiceType extends AbstractType
throw new FormException('The "choice_list" must be an instance of "Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface".');
}
+ if (!$options['choice_list'] && !$options['choices']) {
+ throw new FormException('Either the option "choices" or "choice_list" must be set.');
+ }
+
if (!$options['choice_list']) {
- $options['choice_list'] = new ArrayChoiceList($options['choices']);
+ $options['choice_list'] = new SimpleChoiceList(
+ $options['choices'],
+ $options['preferred_choices'],
+ $options['value_strategy'],
+ $options['index_strategy']
+ );
}
if ($options['expanded']) {
// Load choices already if expanded
- $choices = $options['choice_list']->getChoices();
+ $values = $options['choice_list']->getValues();
+ $labels = $options['choice_list']->getLabels();
- // Flatten choices
- $flattened = array();
- foreach ($choices as $value => $choice) {
- if (is_array($choice)) {
- $flattened = array_replace($flattened, $choice);
- } else {
- $flattened[$value] = $choice;
- }
- }
-
- $options['choices'] = $flattened;
-
- foreach ($options['choices'] as $choice => $value) {
+ foreach ($values as $i => $value) {
if ($options['multiple']) {
- $builder->add((string) $choice, 'checkbox', array(
- 'value' => $choice,
- 'label' => $value,
+ $builder->add((string) $i, 'checkbox', array(
+ 'value' => $value,
+ 'label' => $labels[$i],
// The user can check 0 or more checkboxes. If required
// is true, he is required to check all of them.
'required' => false,
'translation_domain' => $options['translation_domain'],
));
} else {
- $builder->add((string) $choice, 'radio', array(
- 'value' => $choice,
- 'label' => $value,
+ $builder->add((string) $i, 'radio', array(
+ 'value' => $value,
+ 'label' => $labels[$i],
'translation_domain' => $options['translation_domain'],
));
}
@@ -101,18 +102,18 @@ class ChoiceType extends AbstractType
if ($options['expanded']) {
if ($options['multiple']) {
- $builder->appendClientTransformer(new ArrayToBooleanChoicesTransformer($options['choice_list']));
+ $builder->appendClientTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list']));
} else {
$builder
- ->appendClientTransformer(new ScalarToBooleanChoicesTransformer($options['choice_list']))
- ->addEventSubscriber(new FixRadioInputListener(), 10)
+ ->appendClientTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list']))
+ ->addEventSubscriber(new FixRadioInputListener($options['choice_list']), 10)
;
}
} else {
if ($options['multiple']) {
- $builder->appendClientTransformer(new ArrayToChoicesTransformer());
+ $builder->appendClientTransformer(new ChoicesToValuesTransformer($options['choice_list']));
} else {
- $builder->appendClientTransformer(new ScalarToChoiceTransformer());
+ $builder->appendClientTransformer(new ChoiceToValueTransformer($options['choice_list']));
}
}
@@ -123,14 +124,14 @@ class ChoiceType extends AbstractType
*/
public function buildView(FormView $view, FormInterface $form)
{
- $choices = $form->getAttribute('choice_list')->getChoices();
- $preferred = array_flip($form->getAttribute('preferred_choices'));
+ $choiceList = $form->getAttribute('choice_list');
$view
->set('multiple', $form->getAttribute('multiple'))
->set('expanded', $form->getAttribute('expanded'))
- ->set('preferred_choices', array_intersect_key($choices, $preferred))
- ->set('choices', array_diff_key($choices, $preferred))
+ ->set('preferred_choices', $choiceList->getPreferredValueHierarchy())
+ ->set('choices', $choiceList->getRemainingValueHierarchy())
+ ->set('choice_labels', $choiceList->getLabels())
->set('separator', '-------------------')
->set('empty_value', $form->getAttribute('empty_value'))
;
@@ -157,6 +158,8 @@ class ChoiceType extends AbstractType
'choice_list' => null,
'choices' => array(),
'preferred_choices' => array(),
+ 'value_strategy' => ChoiceList::GENERATE,
+ 'index_strategy' => ChoiceList::GENERATE,
'empty_data' => $multiple || $expanded ? array() : '',
'empty_value' => $multiple || $expanded || !isset($options['empty_value']) ? null : '',
'error_bubbling' => false,
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php
index cea4ab2914..f800678d87 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
use Symfony\Component\Locale\Locale;
class CountryType extends AbstractType
@@ -23,6 +24,8 @@ class CountryType extends AbstractType
{
return array(
'choices' => Locale::getDisplayCountries(\Locale::getDefault()),
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index ba72257768..9e878d6535 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
@@ -11,12 +11,13 @@
namespace Symfony\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
+
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\Exception\CreationException;
-use Symfony\Component\Form\Extension\Core\ChoiceList\PaddedChoiceList;
-use Symfony\Component\Form\Extension\Core\ChoiceList\MonthChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\Loader\MonthChoiceListLoader;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
@@ -74,23 +75,35 @@ class DateType extends AbstractType
$options['empty_value'] = array('year' => $options['empty_value'], 'month' => $options['empty_value'], 'day' => $options['empty_value']);
}
+ $years = $months = $days = array();
+
+ foreach ($options['years'] as $year) {
+ $years[$year] = str_pad($year, 4, '0', STR_PAD_LEFT);
+ }
+ foreach ($options['months'] as $month) {
+ $months[$month] = str_pad($month, 2, '0', STR_PAD_LEFT);
+ }
+ foreach ($options['days'] as $day) {
+ $days[$day] = str_pad($day, 2, '0', STR_PAD_LEFT);
+ }
+
// Only pass a subset of the options to children
$yearOptions = array(
- 'choice_list' => new PaddedChoiceList(
- array_combine($options['years'], $options['years']), 4, '0', STR_PAD_LEFT
- ),
+ 'choices' => $years,
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
'empty_value' => $options['empty_value']['year'],
);
$monthOptions = array(
- 'choice_list' => new MonthChoiceList(
- $formatter, $options['months']
- ),
+ 'choices' => $this->formatMonths($formatter, $months),
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
'empty_value' => $options['empty_value']['month'],
);
$dayOptions = array(
- 'choice_list' => new PaddedChoiceList(
- array_combine($options['days'], $options['days']), 2, '0', STR_PAD_LEFT
- ),
+ 'choices' => $days,
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
'empty_value' => $options['empty_value']['day'],
);
@@ -209,4 +222,28 @@ class DateType extends AbstractType
{
return 'date';
}
+
+ private function formatMonths(\IntlDateFormatter $formatter, array $months)
+ {
+ $pattern = $formatter->getPattern();
+ $timezone = $formatter->getTimezoneId();
+
+ $formatter->setTimezoneId(\DateTimeZone::UTC);
+
+ if (preg_match('/M+/', $pattern, $matches)) {
+ $formatter->setPattern($matches[0]);
+
+ foreach ($months as $key => $value) {
+ $months[$key] = $formatter->format(gmmktime(0, 0, 0, $key, 15));
+ }
+
+ // I'd like to clone the formatter above, but then we get a
+ // segmentation fault, so let's restore the old state instead
+ $formatter->setPattern($pattern);
+ }
+
+ $formatter->setTimezoneId($timezone);
+
+ return $months;
+ }
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php
index 46cacfd1bc..da2e315c13 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
use Symfony\Component\Locale\Locale;
class LanguageType extends AbstractType
@@ -23,6 +24,7 @@ class LanguageType extends AbstractType
{
return array(
'choices' => Locale::getDisplayLanguages(\Locale::getDefault()),
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php
index 4f3fa08760..c4841e3184 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
use Symfony\Component\Locale\Locale;
class LocaleType extends AbstractType
@@ -23,6 +24,7 @@ class LocaleType extends AbstractType
{
return array(
'choices' => Locale::getDisplayLocales(\Locale::getDefault()),
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php
index 97a5d03948..233db68f51 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/RadioType.php
@@ -19,48 +19,22 @@ use Symfony\Component\Form\FormView;
class RadioType extends AbstractType
{
- /**
- * {@inheritdoc}
- */
- public function buildForm(FormBuilder $builder, array $options)
- {
- $builder
- ->appendClientTransformer(new BooleanToStringTransformer())
- ->setAttribute('value', $options['value'])
- ;
- }
-
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form)
{
- $view
- ->set('value', $form->getAttribute('value'))
- ->set('checked', (Boolean) $form->getClientData())
- ;
-
if ($view->hasParent()) {
$view->set('full_name', $view->getParent()->get('full_name'));
}
}
- /**
- * {@inheritdoc}
- */
- public function getDefaultOptions(array $options)
- {
- return array(
- 'value' => null,
- );
- }
-
/**
* {@inheritdoc}
*/
public function getParent(array $options)
{
- return 'field';
+ return 'checkbox';
}
/**
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
index 71c19b408a..1eeaa40ec5 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
@@ -14,8 +14,8 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilder;
-use Symfony\Component\Form\Extension\Core\ChoiceList\PaddedChoiceList;
use Symfony\Component\Form\ReversedTransformer;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
@@ -47,25 +47,40 @@ class TimeType extends AbstractType
$options['empty_value'] = array('hour' => $options['empty_value'], 'minute' => $options['empty_value'], 'second' => $options['empty_value']);
}
+ $hours = $minutes = array();
+
+ foreach ($options['hours'] as $hour) {
+ $hours[$hour] = str_pad($hour, 2, '0', STR_PAD_LEFT);
+ }
+ foreach ($options['minutes'] as $minute) {
+ $minutes[$minute] = str_pad($minute, 2, '0', STR_PAD_LEFT);
+ }
+
// Only pass a subset of the options to children
$hourOptions = array(
- 'choice_list' => new PaddedChoiceList(
- array_combine($options['hours'], $options['hours']), 2, '0', STR_PAD_LEFT
- ),
+ 'choices' => $hours,
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
'empty_value' => $options['empty_value']['hour'],
);
$minuteOptions = array(
- 'choice_list' => new PaddedChoiceList(
- array_combine($options['minutes'], $options['minutes']), 2, '0', STR_PAD_LEFT
- ),
+ 'choices' => $minutes,
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
'empty_value' => $options['empty_value']['minute'],
);
if ($options['with_seconds']) {
+ $seconds = array();
+
+ foreach ($options['seconds'] as $second) {
+ $seconds[$second] = str_pad($second, 2, '0', STR_PAD_LEFT);
+ }
+
$secondOptions = array(
- 'choice_list' => new PaddedChoiceList(
- array_combine($options['seconds'], $options['seconds']), 2, '0', STR_PAD_LEFT
- ),
+ 'choices' => $seconds,
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
+ 'index_strategy' => ChoiceList::COPY_CHOICE,
'empty_value' => $options['empty_value']['second'],
);
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
index 2b22523af7..ef308faeeb 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
@@ -12,18 +12,30 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
-use Symfony\Component\Form\Extension\Core\ChoiceList\TimezoneChoiceList;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList;
class TimezoneType extends AbstractType
{
+ /**
+ * Stores the available timezone choices
+ * @var array
+ */
+ static protected $timezones;
+
/**
* {@inheritdoc}
*/
public function getDefaultOptions(array $options)
{
- return array(
- 'choice_list' => new TimezoneChoiceList(),
+ $defaultOptions = array(
+ 'value_strategy' => ChoiceList::COPY_CHOICE,
);
+
+ if (!isset($options['choice_list']) && !isset($options['choices'])) {
+ $defaultOptions['choices'] = self::getTimezones();
+ }
+
+ return $defaultOptions;
}
/**
@@ -41,4 +53,40 @@ class TimezoneType extends AbstractType
{
return 'timezone';
}
+
+ /**
+ * 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
+ */
+ static private function getTimezones()
+ {
+ if (null === static::$timezones) {
+ static::$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];
+ }
+
+ static::$timezones[$region][$timezone] = str_replace('_', ' ', $name);
+ }
+ }
+
+ return static::$timezones;
+ }
}
diff --git a/src/Symfony/Component/Form/Util/FormUtil.php b/src/Symfony/Component/Form/Util/FormUtil.php
index bc521efff2..4ddacd25c5 100644
--- a/src/Symfony/Component/Form/Util/FormUtil.php
+++ b/src/Symfony/Component/Form/Util/FormUtil.php
@@ -13,20 +13,6 @@ namespace Symfony\Component\Form\Util;
abstract class FormUtil
{
- static public function toArrayKey($value)
- {
- if (is_bool($value) || (string) (int) $value === (string) $value) {
- return (int) $value;
- }
-
- return (string) $value;
- }
-
- static public function toArrayKeys(array $array)
- {
- return array_map(array(__CLASS__, 'toArrayKey'), $array);
- }
-
/**
* Returns whether the given choice is a group.
*
@@ -49,10 +35,6 @@ abstract class FormUtil
*/
static public function isChoiceSelected($choice, $value)
{
- $choice = static::toArrayKey($choice);
-
- // The value should already have been converted by value transformers,
- // otherwise we had to do the conversion on every call of this method
if (is_array($value)) {
return false !== array_search($choice, $value, true);
}
diff --git a/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php b/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php
index 74feb80d4b..66e2121b7f 100644
--- a/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php
+++ b/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php
@@ -89,7 +89,7 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
)
);
- $this->assertSame(array(1 => 'Foo', 2 => 'Bar'), $choiceList->getChoices());
+ $this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
}
public function testEmptyChoicesAreManaged()
@@ -132,10 +132,11 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
)
);
+ $this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
$this->assertSame(array(
- 'group1' => array(1 => 'Foo'),
- 'group2' => array(2 => 'Bar')
- ), $choiceList->getChoices());
+ 'group1' => array(1 => '1'),
+ 'group2' => array(2 => '2')
+ ), $choiceList->getRemainingValueHierarchy());
}
public function testGroupBySupportsString()
@@ -164,11 +165,12 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
'groupName'
);
+ $this->assertEquals(array(1 => $item1, 2 => $item2, 3 => $item3, 4 => $item4), $choiceList->getChoices());
$this->assertEquals(array(
- 'Group1' => array(1 => 'Foo', '2' => 'Bar'),
- 'Group2' => array(3 => 'Baz'),
- '4' => 'Boo!'
- ), $choiceList->getChoices('choices'));
+ 'Group1' => array(1 => '1', 2 => '2'),
+ 'Group2' => array(3 => '3'),
+ 4 => '4'
+ ), $choiceList->getRemainingValueHierarchy());
}
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
@@ -188,13 +190,13 @@ class EntityChoiceListTest extends DoctrineOrmTestCase
$item1,
$item2,
),
- 'groupName.child.that.does.not.exist'
+ 'child.that.does.not.exist'
);
$this->assertEquals(array(
- 1 => 'Foo',
- 2 => 'Bar'
- ), $choiceList->getChoices('choices'));
+ 1 => $item1,
+ 2 => $item2
+ ), $choiceList->getChoices());
}
public function testPossibleToProvideShorthandEntityName()
diff --git a/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php b/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php
index 5f77dd5a37..5ff457f879 100644
--- a/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php
+++ b/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php
@@ -109,7 +109,8 @@ class EntityTypeTest extends TypeTestCase
'property' => 'name'
));
- $this->assertEquals(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => '1', 2 => '2'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choice_labels'));
}
public function testSetDataToUninitializedEntityWithNonRequiredToString()
@@ -125,7 +126,8 @@ class EntityTypeTest extends TypeTestCase
'required' => false,
));
- $this->assertEquals(array("1" => 'Foo', "2" => 'Bar'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => '1', 2 => '2'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choice_labels'));
}
public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder()
@@ -144,7 +146,8 @@ class EntityTypeTest extends TypeTestCase
'query_builder' => $qb
));
- $this->assertEquals(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => '1', 2 => '2'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choice_labels'));
}
/**
@@ -185,7 +188,7 @@ class EntityTypeTest extends TypeTestCase
$field->setData(null);
$this->assertNull($field->getData());
- $this->assertEquals('', $field->getClientData());
+ $this->assertSame('', $field->getClientData());
}
public function testSetDataMultipleExpandedNull()
@@ -199,7 +202,7 @@ class EntityTypeTest extends TypeTestCase
$field->setData(null);
$this->assertNull($field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSetDataMultipleNonExpandedNull()
@@ -213,7 +216,7 @@ class EntityTypeTest extends TypeTestCase
$field->setData(null);
$this->assertNull($field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSubmitSingleExpandedNull()
@@ -227,7 +230,7 @@ class EntityTypeTest extends TypeTestCase
$field->bind(null);
$this->assertNull($field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSubmitSingleNonExpandedNull()
@@ -241,7 +244,7 @@ class EntityTypeTest extends TypeTestCase
$field->bind(null);
$this->assertNull($field->getData());
- $this->assertEquals('', $field->getClientData());
+ $this->assertSame('', $field->getClientData());
}
public function testSubmitMultipleNull()
@@ -254,7 +257,7 @@ class EntityTypeTest extends TypeTestCase
$field->bind(null);
$this->assertEquals(new ArrayCollection(), $field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSubmitSingleNonExpandedSingleIdentifier()
@@ -275,8 +278,8 @@ class EntityTypeTest extends TypeTestCase
$field->bind('2');
$this->assertTrue($field->isSynchronized());
- $this->assertEquals($entity2, $field->getData());
- $this->assertEquals(2, $field->getClientData());
+ $this->assertSame($entity2, $field->getData());
+ $this->assertSame('2', $field->getClientData());
}
public function testSubmitSingleNonExpandedCompositeIdentifier()
@@ -298,8 +301,8 @@ class EntityTypeTest extends TypeTestCase
$field->bind('1');
$this->assertTrue($field->isSynchronized());
- $this->assertEquals($entity2, $field->getData());
- $this->assertEquals(1, $field->getClientData());
+ $this->assertSame($entity2, $field->getData());
+ $this->assertSame('1', $field->getClientData());
}
public function testSubmitMultipleNonExpandedSingleIdentifier()
@@ -324,7 +327,7 @@ class EntityTypeTest extends TypeTestCase
$this->assertTrue($field->isSynchronized());
$this->assertEquals($expected, $field->getData());
- $this->assertEquals(array(1, 3), $field->getClientData());
+ $this->assertSame(array('1', '3'), $field->getClientData());
}
public function testSubmitMultipleNonExpandedSingleIdentifier_existingData()
@@ -355,7 +358,7 @@ class EntityTypeTest extends TypeTestCase
$this->assertEquals($expected, $field->getData());
// same object still, useful if it is a PersistentCollection
$this->assertSame($existing, $field->getData());
- $this->assertEquals(array(1, 3), $field->getClientData());
+ $this->assertSame(array('1', '3'), $field->getClientData());
}
public function testSubmitMultipleNonExpandedCompositeIdentifier()
@@ -381,7 +384,7 @@ class EntityTypeTest extends TypeTestCase
$this->assertTrue($field->isSynchronized());
$this->assertEquals($expected, $field->getData());
- $this->assertEquals(array(0, 2), $field->getClientData());
+ $this->assertSame(array('0', '2'), $field->getClientData());
}
public function testSubmitMultipleNonExpandedCompositeIdentifier_existingData()
@@ -412,7 +415,7 @@ class EntityTypeTest extends TypeTestCase
$this->assertEquals($expected, $field->getData());
// same object still, useful if it is a PersistentCollection
$this->assertSame($existing, $field->getData());
- $this->assertEquals(array(0, 2), $field->getClientData());
+ $this->assertSame(array('0', '2'), $field->getClientData());
}
public function testSubmitSingleExpanded()
@@ -433,7 +436,7 @@ class EntityTypeTest extends TypeTestCase
$field->bind('2');
$this->assertTrue($field->isSynchronized());
- $this->assertEquals($entity2, $field->getData());
+ $this->assertSame($entity2, $field->getData());
$this->assertFalse($field['1']->getData());
$this->assertTrue($field['2']->getData());
$this->assertSame('', $field['1']->getClientData());
@@ -488,10 +491,11 @@ class EntityTypeTest extends TypeTestCase
$field->bind('2');
- $this->assertEquals(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => '1', 2 => '2'), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choice_labels'));
$this->assertTrue($field->isSynchronized());
- $this->assertEquals($entity2, $field->getData());
- $this->assertEquals(2, $field->getClientData());
+ $this->assertSame($entity2, $field->getData());
+ $this->assertSame('2', $field->getClientData());
}
public function testGroupByChoices()
@@ -513,12 +517,13 @@ class EntityTypeTest extends TypeTestCase
$field->bind('2');
- $this->assertEquals(2, $field->getClientData());
- $this->assertEquals(array(
- 'Group1' => array(1 => 'Foo', '2' => 'Bar'),
- 'Group2' => array(3 => 'Baz'),
- '4' => 'Boo!'
+ $this->assertSame('2', $field->getClientData());
+ $this->assertSame(array(
+ 'Group1' => array(1 => '1', 2 => '2'),
+ 'Group2' => array(3 => '3'),
+ '4' => '4'
), $field->createView()->get('choices'));
+ $this->assertSame(array(1 => 'Foo', 2 => 'Bar', 3 => 'Baz', 4 => 'Boo!'), $field->createView()->get('choice_labels'));
}
public function testDisallowChoicesThatAreNotIncluded_choicesSingleIdentifier()
@@ -652,8 +657,8 @@ class EntityTypeTest extends TypeTestCase
$field->bind('foo');
$this->assertTrue($field->isSynchronized());
- $this->assertEquals($entity1, $field->getData());
- $this->assertEquals('foo', $field->getClientData());
+ $this->assertSame($entity1, $field->getData());
+ $this->assertSame('foo', $field->getClientData());
}
public function testSubmitCompositeStringIdentifier()
@@ -674,8 +679,8 @@ class EntityTypeTest extends TypeTestCase
$field->bind('0');
$this->assertTrue($field->isSynchronized());
- $this->assertEquals($entity1, $field->getData());
- $this->assertEquals(0, $field->getClientData());
+ $this->assertSame($entity1, $field->getData());
+ $this->assertSame('0', $field->getClientData());
}
protected function createRegistryMock($name, $em)
diff --git a/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php b/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php
index 877bda7c0e..a5ce3b0695 100644
--- a/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php
+++ b/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php
@@ -309,23 +309,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
);
}
- public function testCheckedCheckboxWithValue()
- {
- $form = $this->factory->createNamed('checkbox', 'na&me', true, array(
- 'property_path' => 'name',
- 'value' => 'foo&bar',
- ));
-
- $this->assertWidgetMatchesXpath($form->createView(), array(),
-'/input
- [@type="checkbox"]
- [@name="na&me"]
- [@checked="checked"]
- [@value="foo&bar"]
-'
- );
- }
-
public function testUncheckedCheckbox()
{
$form = $this->factory->createNamed('checkbox', 'na&me', false, array(
@@ -341,6 +324,22 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
);
}
+ public function testCheckboxWithValue()
+ {
+ $form = $this->factory->createNamed('checkbox', 'na&me', false, array(
+ 'property_path' => 'name',
+ 'value' => 'foo&bar',
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/input
+ [@type="checkbox"]
+ [@name="na&me"]
+ [@value="foo&bar"]
+'
+ );
+ }
+
public function testSingleChoice()
{
$form = $this->factory->createNamed('choice', 'na&me', '&a', array(
@@ -355,8 +354,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me"]
[@required="required"]
[
- ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=2]
'
@@ -378,9 +377,9 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me"]
[@required="required"]
[
- ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
/following-sibling::option[@disabled="disabled"][not(@selected)][.="-- sep --"]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
]
[count(./option)=3]
'
@@ -403,8 +402,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me"]
[@required="required"]
[
- ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ ./option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
]
[count(./option)=2]
'
@@ -426,9 +425,9 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me"]
[@required="required"]
[
- ./option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
/following-sibling::option[@disabled="disabled"][not(@selected)][.=""]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
]
[count(./option)=3]
'
@@ -468,8 +467,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[not(@required)]
[
./option[@value=""][.="[trans][/trans]"]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=3]
'
@@ -492,8 +491,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[not(@required)]
[
./option[@value=""][.="[trans][/trans]"]
- /following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ /following-sibling::option[@value="0"][not(@selected)][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=3]
'
@@ -517,8 +516,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[not(@required)]
[
./option[@value=""][not(@selected)][.="[trans]Select&Anything&Not&Me[/trans]"]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=3]
'
@@ -542,8 +541,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@required="required"]
[
./option[@value=""][.="[trans]Test&Me[/trans]"]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=3]
'
@@ -566,8 +565,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@required="required"]
[
./option[@value=""][.="[trans][/trans]"]
- /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ /following-sibling::option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=3]
'
@@ -591,13 +590,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me"]
[./optgroup[@label="[trans]Group&1[/trans]"]
[
- ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=2]
]
[./optgroup[@label="[trans]Group&2[/trans]"]
- [./option[@value="&c"][not(@selected)][.="[trans]Choice&C[/trans]"]]
+ [./option[@value="2"][not(@selected)][.="[trans]Choice&C[/trans]"]]
[count(./option)=1]
]
[count(./optgroup)=2]
@@ -619,8 +618,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me[]"]
[@multiple="multiple"]
[
- ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=2]
'
@@ -642,8 +641,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me[]"]
[@multiple="multiple"]
[
- ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=2]
'
@@ -665,8 +664,8 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@name="na&me[]"]
[@multiple="multiple"]
[
- ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
- /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
+ ./option[@value="0"][@selected="selected"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::option[@value="1"][not(@selected)][.="[trans]Choice&B[/trans]"]
]
[count(./option)=2]
'
@@ -685,10 +684,10 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="radio"][@name="na&me"][@id="na&me_&a"][@checked]
- /following-sibling::label[@for="na&me_&a"][.="[trans]Choice&A[/trans]"]
- /following-sibling::input[@type="radio"][@name="na&me"][@id="na&me_&b"][not(@checked)]
- /following-sibling::label[@for="na&me_&b"][.="[trans]Choice&B[/trans]"]
+ ./input[@type="radio"][@name="na&me"][@id="na&me_0"][@value="0"][@checked]
+ /following-sibling::label[@for="na&me_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="radio"][@name="na&me"][@id="na&me_1"][@value="1"][not(@checked)]
+ /following-sibling::label[@for="na&me_1"][.="[trans]Choice&B[/trans]"]
]
[count(./input)=2]
'
@@ -708,10 +707,10 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="radio"][@name="na&me"][@id="na&me_&a"][@checked]
- /following-sibling::label[@for="na&me_&a"][.="[trans]Choice&A[/trans]"]
- /following-sibling::input[@type="radio"][@name="na&me"][@id="na&me_&b"][not(@checked)]
- /following-sibling::label[@for="na&me_&b"][.="[trans]Choice&B[/trans]"]
+ ./input[@type="radio"][@name="na&me"][@id="na&me_0"][@checked]
+ /following-sibling::label[@for="na&me_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="radio"][@name="na&me"][@id="na&me_1"][not(@checked)]
+ /following-sibling::label[@for="na&me_1"][.="[trans]Choice&B[/trans]"]
]
[count(./input)=2]
'
@@ -730,10 +729,10 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="radio"][@name="na&me"][@id="na&me_1"][@checked]
- /following-sibling::label[@for="na&me_1"][.="[trans]Choice&A[/trans]"]
- /following-sibling::input[@type="radio"][@name="na&me"][@id="na&me_0"][not(@checked)]
- /following-sibling::label[@for="na&me_0"][.="[trans]Choice&B[/trans]"]
+ ./input[@type="radio"][@name="na&me"][@id="na&me_0"][@checked]
+ /following-sibling::label[@for="na&me_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="radio"][@name="na&me"][@id="na&me_1"][not(@checked)]
+ /following-sibling::label[@for="na&me_1"][.="[trans]Choice&B[/trans]"]
]
[count(./input)=2]
'
@@ -753,12 +752,12 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="checkbox"][@name="na&me[&a]"][@id="na&me_&a"][@checked][not(@required)]
- /following-sibling::label[@for="na&me_&a"][.="[trans]Choice&A[/trans]"]
- /following-sibling::input[@type="checkbox"][@name="na&me[&b]"][@id="na&me_&b"][not(@checked)][not(@required)]
- /following-sibling::label[@for="na&me_&b"][.="[trans]Choice&B[/trans]"]
- /following-sibling::input[@type="checkbox"][@name="na&me[&c]"][@id="na&me_&c"][@checked][not(@required)]
- /following-sibling::label[@for="na&me_&c"][.="[trans]Choice&C[/trans]"]
+ ./input[@type="checkbox"][@name="na&me[0]"][@id="na&me_0"][@checked][not(@required)]
+ /following-sibling::label[@for="na&me_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="checkbox"][@name="na&me[1]"][@id="na&me_1"][not(@checked)][not(@required)]
+ /following-sibling::label[@for="na&me_1"][.="[trans]Choice&B[/trans]"]
+ /following-sibling::input[@type="checkbox"][@name="na&me[2]"][@id="na&me_2"][@checked][not(@required)]
+ /following-sibling::label[@for="na&me_2"][.="[trans]Choice&C[/trans]"]
]
[count(./input)=3]
'
@@ -1450,24 +1449,7 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
[@type="radio"]
[@name="na&me"]
[@checked="checked"]
- [@value=""]
-'
- );
- }
-
- public function testCheckedRadioWithValue()
- {
- $form = $this->factory->createNamed('radio', 'na&me', true, array(
- 'property_path' => 'name',
- 'value' => 'foo&bar',
- ));
-
- $this->assertWidgetMatchesXpath($form->createView(), array(),
-'/input
- [@type="radio"]
- [@name="na&me"]
- [@checked="checked"]
- [@value="foo&bar"]
+ [@value="1"]
'
);
}
@@ -1487,6 +1469,22 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
);
}
+ public function testRadioWithValue()
+ {
+ $form = $this->factory->createNamed('radio', 'na&me', false, array(
+ 'property_path' => 'name',
+ 'value' => 'foo&bar',
+ ));
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/input
+ [@type="radio"]
+ [@name="na&me"]
+ [@value="foo&bar"]
+'
+ );
+ }
+
public function testTextarea()
{
$form = $this->factory->createNamed('textarea', 'na&me', 'foo&bar', array(
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ArrayChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ArrayChoiceListTest.php
deleted file mode 100644
index ccf4b7a6dd..0000000000
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ArrayChoiceListTest.php
+++ /dev/null
@@ -1,53 +0,0 @@
-
- *
- * 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\ArrayChoiceList;
-
-class ArrayChoiceListTest extends \PHPUnit_Framework_TestCase
-{
- /**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- */
- public function testConstructorExpectsArrayOrClosure()
- {
- new ArrayChoiceList('foobar');
- }
-
- public function testGetChoices()
- {
- $choices = array('a' => 'A', 'b' => 'B');
- $list = new ArrayChoiceList($choices);
-
- $this->assertSame($choices, $list->getChoices());
- }
-
- public function testGetChoicesFromClosure()
- {
- $choices = array('a' => 'A', 'b' => 'B');
- $closure = function () use ($choices) { return $choices; };
- $list = new ArrayChoiceList($closure);
-
- $this->assertSame($choices, $list->getChoices());
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- */
- public function testClosureShouldReturnArray()
- {
- $closure = function () { return 'foobar'; };
- $list = new ArrayChoiceList($closure);
-
- $list->getChoices();
- }
-}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ComplexChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ComplexChoiceListTest.php
new file mode 100644
index 0000000000..4b61c16df3
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ComplexChoiceListTest.php
@@ -0,0 +1,143 @@
+
+ *
+ * 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\ComplexChoiceList;
+
+class ComplexChoiceListTest extends \PHPUnit_Framework_TestCase
+{
+ private $obj1;
+
+ private $obj2;
+
+ private $obj3;
+
+ private $obj4;
+
+ private $list;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->obj1 = new \stdClass();
+ $this->obj2 = new \stdClass();
+ $this->obj3 = new \stdClass();
+ $this->obj4 = new \stdClass();
+
+ $this->list = new ComplexChoiceList(
+ 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($this->obj2, $this->obj3)
+ );
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->obj1 = null;
+ $this->obj2 = null;
+ $this->obj3 = null;
+ $this->obj4 = null;
+ $this->list = null;
+ }
+
+ public function testInitArray()
+ {
+ $this->list = new ComplexChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
+ array('A', 'B', 'C', 'D'),
+ array($this->obj2)
+ );
+
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1'), $this->list->getPreferredValues());
+ $this->assertSame(array(1 => '1'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 2 => '2', 3 => '3'), $this->list->getRemainingValues());
+ $this->assertSame(array(0 => '0', 2 => '2', 3 => '3'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitNestedArray()
+ {
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1', 2 => '2'), $this->list->getPreferredValues());
+ $this->assertSame(array(
+ 'Group 1' => array(1 => '1'),
+ 'Group 2' => array(2 => '2')
+ ), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 3 => '3'), $this->list->getRemainingValues());
+ $this->assertSame(array(
+ 'Group 1' => array(0 => '0'),
+ 'Group 2' => array(3 => '3')
+ ), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testGetIndicesForChoices()
+ {
+ $choices = array($this->obj2, $this->obj3);
+ $this->assertSame(array(1, 2), $this->list->getIndicesForChoices($choices));
+ }
+
+ public function testGetIndicesForChoicesIgnoresNonExistingChoices()
+ {
+ $choices = array($this->obj2, $this->obj3, 'foobar');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForChoices($choices));
+ }
+
+ public function testGetIndicesForValues()
+ {
+ // values and indices are always the same
+ $values = array('1', '2');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForValues($values));
+ }
+
+ public function testGetIndicesForValuesIgnoresNonExistingValues()
+ {
+ $values = array('1', '2', '5');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForValues($values));
+ }
+
+ public function testGetChoicesForValues()
+ {
+ $values = array('1', '2');
+ $this->assertSame(array($this->obj2, $this->obj3), $this->list->getChoicesForValues($values));
+ }
+
+ public function testGetChoicesForValuesIgnoresNonExistingValues()
+ {
+ $values = array('1', '2', '5');
+ $this->assertSame(array($this->obj2, $this->obj3), $this->list->getChoicesForValues($values));
+ }
+
+ public function testGetValuesForChoices()
+ {
+ $choices = array($this->obj2, $this->obj3);
+ $this->assertSame(array('1', '2'), $this->list->getValuesForChoices($choices));
+ }
+
+ public function testGetValuesForChoicesIgnoresNonExistingChoices()
+ {
+ $choices = array($this->obj2, $this->obj3, 'foobar');
+ $this->assertSame(array('1', '2'), $this->list->getValuesForChoices($choices));
+ }
+}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/MonthChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/MonthChoiceListTest.php
deleted file mode 100644
index 5fd37fdea4..0000000000
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/MonthChoiceListTest.php
+++ /dev/null
@@ -1,95 +0,0 @@
-
- *
- * 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\MonthChoiceList;
-
-class MonthChoiceListTest extends \PHPUnit_Framework_TestCase
-{
- private $formatter;
-
- protected function setUp()
- {
- if (!extension_loaded('intl')) {
- $this->markTestSkipped('The "intl" extension is not available');
- }
-
- \Locale::setDefault('en');
-
- // I would prefer to mock the formatter, but this leads to weird bugs
- // with the current version of PHPUnit
- $this->formatter = new \IntlDateFormatter(
- \Locale::getDefault(),
- \IntlDateFormatter::SHORT,
- \IntlDateFormatter::NONE,
- \DateTimeZone::UTC
- );
- }
-
- protected function tearDown()
- {
- $this->formatter = null;
- }
-
- public function testNumericMonthsIfPatternContainsNoMonth()
- {
- $this->formatter->setPattern('yy');
-
- $months = array(1, 4);
- $list = new MonthChoiceList($this->formatter, $months);
-
- $names = array(1 => '01', 4 => '04');
- $this->assertSame($names, $list->getChoices());
- }
-
- public function testFormattedMonthsShort()
- {
- $this->formatter->setPattern('dd.MMM.yy');
-
- $months = array(1, 4);
- $list = new MonthChoiceList($this->formatter, $months);
-
- $names = array(1 => 'Jan', 4 => 'Apr');
- $this->assertSame($names, $list->getChoices());
- }
-
- public function testFormattedMonthsLong()
- {
- $this->formatter->setPattern('dd.MMMM.yy');
-
- $months = array(1, 4);
- $list = new MonthChoiceList($this->formatter, $months);
-
- $names = array(1 => 'January', 4 => 'April');
- $this->assertSame($names, $list->getChoices());
- }
-
- public function testFormattedMonthsLongWithDifferentTimezone()
- {
- $this->formatter = new \IntlDateFormatter(
- \Locale::getDefault(),
- \IntlDateFormatter::SHORT,
- \IntlDateFormatter::NONE,
- 'PST'
- );
-
- $this->formatter->setPattern('dd.MMMM.yy');
-
- $months = array(1, 4);
- $list = new MonthChoiceList($this->formatter, $months);
-
- $names = array(1 => 'January', 4 => 'April');
- // uses UTC internally
- $this->assertSame($names, $list->getChoices());
- $this->assertSame('PST', $this->formatter->getTimezoneId());
- }
-}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ObjectChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ObjectChoiceListTest.php
new file mode 100644
index 0000000000..8e807a86b0
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ObjectChoiceListTest.php
@@ -0,0 +1,249 @@
+
+ *
+ * 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\ObjectChoiceList;
+
+class ObjectChoiceListTest_EntityWithToString
+{
+ private $property;
+
+ public function __construct($property)
+ {
+ $this->property = $property;
+ }
+
+ public function __toString()
+ {
+ return $this->property;
+ }
+}
+
+class ObjectChoiceListTest extends \PHPUnit_Framework_TestCase
+{
+ private $obj1;
+
+ private $obj2;
+
+ private $obj3;
+
+ private $obj4;
+
+ private $list;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->obj1 = (object) array('name' => 'A');
+ $this->obj2 = (object) array('name' => 'B');
+ $this->obj3 = (object) array('name' => 'C');
+ $this->obj4 = (object) array('name' => 'D');
+
+ $this->list = new ObjectChoiceList(
+ array(
+ 'Group 1' => array($this->obj1, $this->obj2),
+ 'Group 2' => array($this->obj3, $this->obj4),
+ ),
+ 'name',
+ array($this->obj2, $this->obj3)
+ );
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->obj1 = null;
+ $this->obj2 = null;
+ $this->obj3 = null;
+ $this->obj4 = null;
+ $this->list = null;
+ }
+
+ public function testInitArray()
+ {
+ $this->list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
+ 'name',
+ array($this->obj2)
+ );
+
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1'), $this->list->getPreferredValues());
+ $this->assertSame(array(1 => '1'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 2 => '2', 3 => '3'), $this->list->getRemainingValues());
+ $this->assertSame(array(0 => '0', 2 => '2', 3 => '3'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitNestedArray()
+ {
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1', 2 => '2'), $this->list->getPreferredValues());
+ $this->assertSame(array(
+ 'Group 1' => array(1 => '1'),
+ 'Group 2' => array(2 => '2')
+ ), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 3 => '3'), $this->list->getRemainingValues());
+ $this->assertSame(array(
+ 'Group 1' => array(0 => '0'),
+ 'Group 2' => array(3 => '3')
+ ), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitArrayWithGroupPath()
+ {
+ $this->obj1 = (object) array('name' => 'A', 'category' => 'Group 1');
+ $this->obj2 = (object) array('name' => 'B', 'category' => 'Group 1');
+ $this->obj3 = (object) array('name' => 'C', 'category' => 'Group 2');
+ $this->obj4 = (object) array('name' => 'D', 'category' => 'Group 2');
+
+ // Objects with NULL groups are not grouped
+ $obj5 = (object) array('name' => 'E', 'category' => null);
+
+ // Objects without the group property are not grouped either
+ // see https://github.com/symfony/symfony/commit/d9b7abb7c7a0f28e0ce970afc5e305dce5dccddf
+ $obj6 = (object) array('name' => 'F');
+
+ $this->list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4, $obj5, $obj6),
+ 'name',
+ array($this->obj2, $this->obj3),
+ 'category'
+ );
+
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4, $obj5, $obj6), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D', 'E', 'F'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1', 2 => '2'), $this->list->getPreferredValues());
+ $this->assertSame(array(
+ 'Group 1' => array(1 => '1'),
+ 'Group 2' => array(2 => '2')
+ ), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 3 => '3', 4 => '4', 5 => '5'), $this->list->getRemainingValues());
+ $this->assertSame(array(
+ 'Group 1' => array(0 => '0'),
+ 'Group 2' => array(3 => '3'),
+ 4 => '4',
+ 5 => '5',
+ ), $this->list->getRemainingValueHierarchy());
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testInitArrayWithGroupPathThrowsExceptionIfNestedArray()
+ {
+ $this->obj1 = (object) array('name' => 'A', 'category' => 'Group 1');
+ $this->obj2 = (object) array('name' => 'B', 'category' => 'Group 1');
+ $this->obj3 = (object) array('name' => 'C', 'category' => 'Group 2');
+ $this->obj4 = (object) array('name' => 'D', 'category' => 'Group 2');
+
+ new ObjectChoiceList(
+ array(
+ 'Group 1' => array($this->obj1, $this->obj2),
+ 'Group 2' => array($this->obj3, $this->obj4),
+ ),
+ 'name',
+ array($this->obj2, $this->obj3),
+ 'category'
+ );
+ }
+
+ public function testInitArrayWithValuePath()
+ {
+ $this->obj1 = (object) array('name' => 'A', 'id' => 10);
+ $this->obj2 = (object) array('name' => 'B', 'id' => 20);
+ $this->obj3 = (object) array('name' => 'C', 'id' => 30);
+ $this->obj4 = (object) array('name' => 'D', 'id' => 40);
+
+ $this->list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
+ 'name',
+ array($this->obj2, $this->obj3),
+ null,
+ 'id'
+ );
+
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+
+ // Values are always converted to strings to avoid problems with
+ // comparisons
+ $this->assertSame(array(1 => '20', 2 => '30'), $this->list->getPreferredValues());
+ $this->assertSame(array(1 => '20', 2 => '30'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '10', 3 => '40'), $this->list->getRemainingValues());
+ $this->assertSame(array(0 => '10', 3 => '40'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitArrayWithIndexPath()
+ {
+ $this->obj1 = (object) array('name' => 'A', 'id' => 10);
+ $this->obj2 = (object) array('name' => 'B', 'id' => 20);
+ $this->obj3 = (object) array('name' => 'C', 'id' => 30);
+ $this->obj4 = (object) array('name' => 'D', 'id' => 40);
+
+ $this->list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4),
+ 'name',
+ array($this->obj2, $this->obj3),
+ null,
+ null,
+ 'id'
+ );
+
+ $this->assertSame(array(10 => $this->obj1, 20 => $this->obj2, 30 => $this->obj3, 40 => $this->obj4), $this->list->getChoices());
+ $this->assertSame(array(10 => 'A', 20 => 'B', 30 => 'C', 40 => 'D'), $this->list->getLabels());
+
+ $this->assertSame(array(20 => '1', 30 => '2'), $this->list->getPreferredValues());
+ $this->assertSame(array(20 => '1', 30 => '2'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(10 => '0', 40 => '3'), $this->list->getRemainingValues());
+ $this->assertSame(array(10 => '0', 40 => '3'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitArrayUsesToString()
+ {
+ $this->obj1 = new ObjectChoiceListTest_EntityWithToString('A');
+ $this->obj2 = new ObjectChoiceListTest_EntityWithToString('B');
+ $this->obj3 = new ObjectChoiceListTest_EntityWithToString('C');
+ $this->obj4 = new ObjectChoiceListTest_EntityWithToString('D');
+
+ $this->list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4)
+ );
+
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\FormException
+ */
+ public function testInitArrayThrowsExceptionIfToStringNotFound()
+ {
+ $this->obj1 = new ObjectChoiceListTest_EntityWithToString('A');
+ $this->obj2 = new ObjectChoiceListTest_EntityWithToString('B');
+ $this->obj3 = (object) array('name' => 'C');
+ $this->obj4 = new ObjectChoiceListTest_EntityWithToString('D');
+
+ $this->list = new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4)
+ );
+
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('A', 'B', 'C', 'D'), $this->list->getLabels());
+ }
+}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/PaddedChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/PaddedChoiceListTest.php
deleted file mode 100644
index 6bc115012a..0000000000
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/PaddedChoiceListTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-
- *
- * 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\PaddedChoiceList;
-
-class PaddedChoiceListTest extends \PHPUnit_Framework_TestCase
-{
- /**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- */
- public function testConstructorExpectsArrayOrClosure()
- {
- $list = new PaddedChoiceList('foobar', 3, '-', STR_PAD_RIGHT);
- }
-
- public function testPaddingDirections()
- {
- $list = new PaddedChoiceList(array('a' => 'C', 'b' => 'D'), 3, '-', STR_PAD_RIGHT);
- $this->assertSame(array('a' => 'C--', 'b' => 'D--'), $list->getChoices());
- $list = new PaddedChoiceList(array('a' => 'C', 'b' => 'D'), 3, '-', STR_PAD_LEFT);
- $this->assertSame(array('a' => '--C', 'b' => '--D'), $list->getChoices());
- $list = new PaddedChoiceList(array('a' => 'C', 'b' => 'D'), 3, '-', STR_PAD_BOTH);
- $this->assertSame(array('a' => '-C-', 'b' => '-D-'), $list->getChoices());
- }
-
- public function testGetChoicesFromClosure()
- {
- $closure = function () { return array('a' => 'C', 'b' => 'D'); };
- $list = new PaddedChoiceList($closure, 3, '-', STR_PAD_RIGHT);
-
- $this->assertSame(array('a' => 'C--', 'b' => 'D--'), $list->getChoices());
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- */
- public function testClosureShouldReturnArray()
- {
- $closure = function () { return 'foobar'; };
- $list = new PaddedChoiceList($closure, 3, '-', STR_PAD_RIGHT);
-
- $list->getChoices();
- }
-}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/SimpleChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/SimpleChoiceListTest.php
new file mode 100644
index 0000000000..508695cfda
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/SimpleChoiceListTest.php
@@ -0,0 +1,222 @@
+
+ *
+ * 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\ChoiceList;
+
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
+
+class SimpleChoiceListTest extends \PHPUnit_Framework_TestCase
+{
+ private $list;
+
+ private $numericList;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $choices = array(
+ 'Group 1' => array('a' => 'A', 'b' => 'B'),
+ 'Group 2' => array('c' => 'C', 'd' => 'D'),
+ );
+ $numericChoices = array(
+ 'Group 1' => array(0 => 'A', 1 => 'B'),
+ 'Group 2' => array(2 => 'C', 3 => 'D'),
+ );
+
+ $this->list = new SimpleChoiceList($choices, array('b', 'c'), ChoiceList::GENERATE, ChoiceList::GENERATE);
+
+ // Use COPY_CHOICE strategy to test for the various associated problems
+ $this->numericList = new SimpleChoiceList($numericChoices, array(1, 2), ChoiceList::COPY_CHOICE, ChoiceList::GENERATE);
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->list = null;
+ $this->numericList = null;
+ }
+
+ public function testInitArray()
+ {
+ $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C');
+ $this->list = new SimpleChoiceList($choices, array('b'), ChoiceList::GENERATE, ChoiceList::GENERATE);
+
+ $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c'), $this->list->getChoices());
+ $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1'), $this->list->getPreferredValues());
+ $this->assertSame(array(1 => '1'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 2 => '2'), $this->list->getRemainingValues());
+ $this->assertSame(array(0 => '0', 2 => '2'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitArrayValueCopyChoice()
+ {
+ $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C');
+ $this->list = new SimpleChoiceList($choices, array('b'), ChoiceList::COPY_CHOICE, ChoiceList::GENERATE);
+
+ $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c'), $this->list->getChoices());
+ $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => 'b'), $this->list->getPreferredValues());
+ $this->assertSame(array(1 => 'b'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => 'a', 2 => 'c'), $this->list->getRemainingValues());
+ $this->assertSame(array(0 => 'a', 2 => 'c'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitArrayIndexCopyChoice()
+ {
+ $choices = array('a' => 'A', 'b' => 'B', 'c' => 'C');
+ $this->list = new SimpleChoiceList($choices, array('b'), ChoiceList::GENERATE, ChoiceList::COPY_CHOICE);
+
+ $this->assertSame(array('a' => 'a', 'b' => 'b', 'c' => 'c'), $this->list->getChoices());
+ $this->assertSame(array('a' => 'A', 'b' => 'B', 'c' => 'C'), $this->list->getLabels());
+
+ $this->assertSame(array('b' => '1'), $this->list->getPreferredValues());
+ $this->assertSame(array('b' => '1'), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array('a' => '0', 'c' => '2'), $this->list->getRemainingValues());
+ $this->assertSame(array('a' => '0', 'c' => '2'), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testInitNestedArray()
+ {
+ $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $this->list->getChoices());
+ $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $this->list->getLabels());
+
+ $this->assertSame(array(1 => '1', 2 => '2'), $this->list->getPreferredValues());
+ $this->assertSame(array(
+ 'Group 1' => array(1 => '1'),
+ 'Group 2' => array(2 => '2')
+ ), $this->list->getPreferredValueHierarchy());
+ $this->assertSame(array(0 => '0', 3 => '3'), $this->list->getRemainingValues());
+ $this->assertSame(array(
+ 'Group 1' => array(0 => '0'),
+ 'Group 2' => array(3 => '3')
+ ), $this->list->getRemainingValueHierarchy());
+ }
+
+ public function testGetIndicesForChoices()
+ {
+ $choices = array('b', 'c');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForChoices($choices));
+ }
+
+ public function testGetIndicesForChoicesIgnoresNonExistingChoices()
+ {
+ $choices = array('b', 'c', 'foobar');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForChoices($choices));
+ }
+
+ public function testGetIndicesForChoicesDealsWithNumericChoices()
+ {
+ // Pass choices as strings although they are integers
+ $choices = array('0', '1');
+ $this->assertSame(array(0, 1), $this->numericList->getIndicesForChoices($choices));
+ }
+
+ public function testGetIndicesForValues()
+ {
+ $values = array('1', '2');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForValues($values));
+ }
+
+ public function testGetIndicesForValuesIgnoresNonExistingValues()
+ {
+ $values = array('1', '2', '100');
+ $this->assertSame(array(1, 2), $this->list->getIndicesForValues($values));
+ }
+
+ public function testGetIndicesForValuesDealsWithNumericValues()
+ {
+ // Pass values as strings although they are integers
+ $values = array('0', '1');
+ $this->assertSame(array(0, 1), $this->numericList->getIndicesForValues($values));
+ }
+
+ public function testGetChoicesForValues()
+ {
+ $values = array('1', '2');
+ $this->assertSame(array('b', 'c'), $this->list->getChoicesForValues($values));
+ }
+
+ public function testGetChoicesForValuesIgnoresNonExistingValues()
+ {
+ $values = array('1', '2', '100');
+ $this->assertSame(array('b', 'c'), $this->list->getChoicesForValues($values));
+ }
+
+ public function testGetChoicesForValuesDealsWithNumericValues()
+ {
+ // Pass values as strings although they are integers
+ $values = array('0', '1');
+ $this->assertSame(array(0, 1), $this->numericList->getChoicesForValues($values));
+ }
+
+ public function testGetValuesForChoices()
+ {
+ $choices = array('b', 'c');
+ $this->assertSame(array('1', '2'), $this->list->getValuesForChoices($choices));
+ }
+
+ public function testGetValuesForChoicesIgnoresNonExistingValues()
+ {
+ $choices = array('b', 'c', 'foobar');
+ $this->assertSame(array('1', '2'), $this->list->getValuesForChoices($choices));
+ }
+
+ public function testGetValuesForChoicesDealsWithNumericValues()
+ {
+ // Pass values as strings although they are integers
+ $values = array('0', '1');
+
+ $this->assertSame(array('0', '1'), $this->numericList->getValuesForChoices($values));
+ }
+
+ /**
+ * @dataProvider dirtyValuesProvider
+ */
+ public function testGetValuesForChoicesDealsWithDirtyValues($choice, $value)
+ {
+ $choices = array(
+ '0' => 'Zero',
+ '1' => 'One',
+ '' => 'Empty',
+ '1.23' => 'Float',
+ 'foo' => 'Foo',
+ 'foo10' => 'Foo 10',
+ );
+
+ // use COPY_CHOICE strategy to test the problems
+ $this->list = new SimpleChoiceList($choices, array(), ChoiceList::COPY_CHOICE, ChoiceList::GENERATE);
+
+ $this->assertSame(array($value), $this->list->getValuesForChoices(array($choice)));
+ }
+
+ public function dirtyValuesProvider()
+ {
+ return array(
+ array(0, '0'),
+ array('0', '0'),
+ array('1', '1'),
+ array(false, '0'),
+ array(true, '1'),
+ array('', ''),
+ array(null, ''),
+ array('1.23', '1.23'),
+ array('foo', 'foo'),
+ array('foo10', 'foo10'),
+ );
+ }
+}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ScalarToChoiceTransformerTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php
similarity index 69%
rename from tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ScalarToChoiceTransformerTest.php
rename to tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php
index b723419303..2885121534 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ScalarToChoiceTransformerTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php
@@ -11,15 +11,17 @@
namespace Symfony\Tests\Component\Form\Extension\Core\DataTransformer;
-use Symfony\Component\Form\Extension\Core\DataTransformer\ScalarToChoiceTransformer;
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
+use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
-class ScalarToChoiceTransformerTest extends \PHPUnit_Framework_TestCase
+class ChoiceToValueTransformerTest extends \PHPUnit_Framework_TestCase
{
protected $transformer;
protected function setUp()
{
- $this->transformer = new ScalarToChoiceTransformer();
+ $list = new SimpleChoiceList(array('' => 'A', 0 => 'B', 1 => 'C'));
+ $this->transformer = new ChoiceToValueTransformer($list);
}
protected function tearDown()
@@ -31,8 +33,8 @@ class ScalarToChoiceTransformerTest extends \PHPUnit_Framework_TestCase
{
return array(
// more extensive test set can be found in FormUtilTest
- array(0, 0),
- array(false, 0),
+ array(0, '0'),
+ array(false, '0'),
array('', ''),
);
}
@@ -50,8 +52,9 @@ class ScalarToChoiceTransformerTest extends \PHPUnit_Framework_TestCase
return array(
// values are expected to be valid choice keys already and stay
// the same
- array(0, 0),
- array('', ''),
+ array('0', 0),
+ array('', null),
+ array(null, null),
);
}
@@ -60,15 +63,7 @@ class ScalarToChoiceTransformerTest extends \PHPUnit_Framework_TestCase
*/
public function testReverseTransform($in, $out)
{
- $this->assertSame($out, $this->transformer->transform($in));
- }
-
- /**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
- */
- public function testTransformExpectsScalar()
- {
- $this->transformer->transform(array());
+ $this->assertSame($out, $this->transformer->reverseTransform($in));
}
/**
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ArrayToChoicesTransformerTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php
similarity index 68%
rename from tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ArrayToChoicesTransformerTest.php
rename to tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php
index 992dac5776..0166458043 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ArrayToChoicesTransformerTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformerTest.php
@@ -11,15 +11,18 @@
namespace Symfony\Tests\Component\Form\Extension\Core\DataTransformer;
-use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToChoicesTransformer;
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
-class ArrayToChoicesTransformerTest extends \PHPUnit_Framework_TestCase
+use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
+
+class ChoicesToValuesTransformerTest extends \PHPUnit_Framework_TestCase
{
protected $transformer;
protected function setUp()
{
- $this->transformer = new ArrayToChoicesTransformer();
+ $list = new SimpleChoiceList(array(0 => 'A', 1 => 'B', 2 => 'C'));
+ $this->transformer = new ChoicesToValuesTransformer($list);
}
protected function tearDown()
@@ -29,8 +32,9 @@ class ArrayToChoicesTransformerTest extends \PHPUnit_Framework_TestCase
public function testTransform()
{
- $in = array(0, false, '');
- $out = array(0, 0, '');
+ // Value strategy in SimpleChoiceList is to copy and convert to string
+ $in = array(0, 1, 2);
+ $out = array('0', '1', '2');
$this->assertSame($out, $this->transformer->transform($in));
}
@@ -51,10 +55,10 @@ class ArrayToChoicesTransformerTest extends \PHPUnit_Framework_TestCase
public function testReverseTransform()
{
// values are expected to be valid choices and stay the same
- $in = array(0, 0, '');
- $out = array(0, 0, '');
+ $in = array('0', '1', '2');
+ $out = array(0, 1, 2);
- $this->assertSame($out, $this->transformer->transform($in));
+ $this->assertSame($out, $this->transformer->reverseTransform($in));
}
public function testReverseTransformNull()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/FixRadioInputListenerTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/FixRadioInputListenerTest.php
index 9f9fc25246..41949e9f01 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/FixRadioInputListenerTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/EventListener/FixRadioInputListenerTest.php
@@ -13,19 +13,36 @@ namespace Symfony\Tests\Component\Form\Extension\Core\EventListener;
use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
{
+ private $listener;
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $list = new SimpleChoiceList(array(0 => 'A', 1 => 'B'));
+ $this->listener = new FixRadioInputListener($list);
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->listener = null;
+ }
+
public function testFixRadio()
{
$data = '1';
$form = $this->getMock('Symfony\Tests\Component\Form\FormInterface');
$event = new FilterDataEvent($form, $data);
- $filter = new FixRadioInputListener();
- $filter->onBindClientData($event);
+ $this->listener->onBindClientData($event);
- $this->assertEquals(array('1' => true), $event->getData());
+ $this->assertEquals(array(1 => '1'), $event->getData());
}
public function testFixZero()
@@ -34,10 +51,9 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$form = $this->getMock('Symfony\Tests\Component\Form\FormInterface');
$event = new FilterDataEvent($form, $data);
- $filter = new FixRadioInputListener();
- $filter->onBindClientData($event);
+ $this->listener->onBindClientData($event);
- $this->assertEquals(array('0' => true), $event->getData());
+ $this->assertEquals(array(0 => '0'), $event->getData());
}
public function testIgnoreEmptyString()
@@ -46,8 +62,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$form = $this->getMock('Symfony\Tests\Component\Form\FormInterface');
$event = new FilterDataEvent($form, $data);
- $filter = new FixRadioInputListener();
- $filter->onBindClientData($event);
+ $this->listener->onBindClientData($event);
$this->assertEquals(array(), $event->getData());
}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/ChoiceTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/ChoiceTypeTest.php
index 605fd3f322..8dae89442d 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/ChoiceTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/ChoiceTypeTest.php
@@ -11,6 +11,10 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
+
+use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
+
use Symfony\Component\Form\Exception\UnexpectedTypeException;
class ChoiceTypeTest extends TypeTestCase
@@ -31,6 +35,8 @@ class ChoiceTypeTest extends TypeTestCase
4 => 'Roman',
);
+ private $objectChoices;
+
private $stringButNumericChoices = array(
'0' => 'Bernhard',
'1' => 'Fabien',
@@ -51,8 +57,29 @@ class ChoiceTypeTest extends TypeTestCase
)
);
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->objectChoices = array(
+ (object) array('id' => 1, 'name' => 'Bernhard'),
+ (object) array('id' => 2, 'name' => 'Fabien'),
+ (object) array('id' => 3, 'name' => 'Kris'),
+ (object) array('id' => 4, 'name' => 'Jon'),
+ (object) array('id' => 5, 'name' => 'Roman'),
+ );
+ }
+
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $this->objectChoices = null;
+ }
+
/**
- * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException
+ * @expectedException \PHPUnit_Framework_Error
*/
public function testChoicesOptionExpectsArray()
{
@@ -71,6 +98,15 @@ class ChoiceTypeTest extends TypeTestCase
));
}
+ /**
+ * @expectedException Symfony\Component\Form\Exception\FormException
+ */
+ public function testEitherChoiceListOrChoicesMustBeSet()
+ {
+ $form = $this->factory->create('choice', null, array(
+ ));
+ }
+
public function testExpandedChoicesOptionsTurnIntoFields()
{
$form = $this->factory->create('choice', null, array(
@@ -90,7 +126,7 @@ class ChoiceTypeTest extends TypeTestCase
$flattened = array();
foreach ($this->groupedChoices as $choices) {
- $flattened = array_replace($flattened, $choices);
+ $flattened = array_merge($flattened, array_keys($choices));
}
$this->assertCount($form->count(), $flattened, 'Each nested choice should become a new field, not the groups');
@@ -150,10 +186,33 @@ class ChoiceTypeTest extends TypeTestCase
'choices' => $this->choices,
));
- $form->bind('b');
+ $form->bind('1');
$this->assertEquals('b', $form->getData());
- $this->assertEquals('b', $form->getClientData());
+ $this->assertEquals('1', $form->getClientData());
+ }
+
+ public function testBindSingleNonExpandedObjectChoices()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'multiple' => false,
+ 'expanded' => false,
+ 'choice_list' => new ObjectChoiceList(
+ $this->objectChoices,
+ // label path
+ 'name',
+ array(),
+ null,
+ // value path
+ 'id'
+ ),
+ ));
+
+ // "id" value of the second entry
+ $form->bind('2');
+
+ $this->assertEquals($this->objectChoices[1], $form->getData());
+ $this->assertEquals('2', $form->getClientData());
}
public function testBindMultipleNonExpanded()
@@ -164,10 +223,32 @@ class ChoiceTypeTest extends TypeTestCase
'choices' => $this->choices,
));
- $form->bind(array('a', 'b'));
+ $form->bind(array('0', '1'));
$this->assertEquals(array('a', 'b'), $form->getData());
- $this->assertEquals(array('a', 'b'), $form->getClientData());
+ $this->assertEquals(array('0', '1'), $form->getClientData());
+ }
+
+ public function testBindMultipleNonExpandedObjectChoices()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'multiple' => true,
+ 'expanded' => false,
+ 'choice_list' => new ObjectChoiceList(
+ $this->objectChoices,
+ // label path
+ 'name',
+ array(),
+ null,
+ // value path
+ 'id'
+ ),
+ ));
+
+ $form->bind(array('2', '3'));
+
+ $this->assertEquals(array($this->objectChoices[1], $this->objectChoices[2]), $form->getData());
+ $this->assertEquals(array('2', '3'), $form->getClientData());
}
public function testBindSingleExpanded()
@@ -178,19 +259,19 @@ class ChoiceTypeTest extends TypeTestCase
'choices' => $this->choices,
));
- $form->bind('b');
+ $form->bind('1');
$this->assertSame('b', $form->getData());
- $this->assertFalse($form['a']->getData());
- $this->assertTrue($form['b']->getData());
- $this->assertFalse($form['c']->getData());
- $this->assertFalse($form['d']->getData());
- $this->assertFalse($form['e']->getData());
- $this->assertSame('', $form['a']->getClientData());
- $this->assertSame('1', $form['b']->getClientData());
- $this->assertSame('', $form['c']->getClientData());
- $this->assertSame('', $form['d']->getClientData());
- $this->assertSame('', $form['e']->getClientData());
+ $this->assertFalse($form[0]->getData());
+ $this->assertTrue($form[1]->getData());
+ $this->assertFalse($form[2]->getData());
+ $this->assertFalse($form[3]->getData());
+ $this->assertFalse($form[4]->getData());
+ $this->assertSame('', $form[0]->getClientData());
+ $this->assertSame('1', $form[1]->getClientData());
+ $this->assertSame('', $form[2]->getClientData());
+ $this->assertSame('', $form[3]->getClientData());
+ $this->assertSame('', $form[4]->getClientData());
}
public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields()
@@ -207,6 +288,57 @@ class ChoiceTypeTest extends TypeTestCase
$this->assertNull($form->getData());
}
+ public function testBindSingleExpandedWithEmptyField()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'multiple' => false,
+ 'expanded' => true,
+ 'choices' => array(
+ '' => 'Empty',
+ '1' => 'Not empty',
+ ),
+ ));
+
+ $form->bind('0');
+
+ $this->assertNull($form->getData());
+ $this->assertTrue($form[0]->getData());
+ $this->assertFalse($form[1]->getData());
+ $this->assertSame('1', $form[0]->getClientData());
+ $this->assertSame('', $form[1]->getClientData());
+ }
+
+ public function testBindSingleExpandedObjectChoices()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'multiple' => false,
+ 'expanded' => true,
+ 'choice_list' => new ObjectChoiceList(
+ $this->objectChoices,
+ // label path
+ 'name',
+ array(),
+ null,
+ // value path
+ 'id'
+ ),
+ ));
+
+ $form->bind('2');
+
+ $this->assertSame($this->objectChoices[1], $form->getData());
+ $this->assertFalse($form[0]->getData());
+ $this->assertTrue($form[1]->getData());
+ $this->assertFalse($form[2]->getData());
+ $this->assertFalse($form[3]->getData());
+ $this->assertFalse($form[4]->getData());
+ $this->assertSame('', $form[0]->getClientData());
+ $this->assertSame('1', $form[1]->getClientData());
+ $this->assertSame('', $form[2]->getClientData());
+ $this->assertSame('', $form[3]->getClientData());
+ $this->assertSame('', $form[4]->getClientData());
+ }
+
public function testBindSingleExpandedNumericChoices()
{
$form = $this->factory->create('choice', null, array(
@@ -217,7 +349,7 @@ class ChoiceTypeTest extends TypeTestCase
$form->bind('1');
- $this->assertSame('1', $form->getData());
+ $this->assertSame(1, $form->getData());
$this->assertFalse($form[0]->getData());
$this->assertTrue($form[1]->getData());
$this->assertFalse($form[2]->getData());
@@ -240,7 +372,7 @@ class ChoiceTypeTest extends TypeTestCase
$form->bind('1');
- $this->assertSame('1', $form->getData());
+ $this->assertSame(1, $form->getData());
$this->assertFalse($form[0]->getData());
$this->assertTrue($form[1]->getData());
$this->assertFalse($form[2]->getData());
@@ -261,19 +393,50 @@ class ChoiceTypeTest extends TypeTestCase
'choices' => $this->choices,
));
- $form->bind(array('a' => 'a', 'b' => 'b'));
+ $form->bind(array(0 => 'a', 1 => 'b'));
- $this->assertSame(array('a', 'b'), $form->getData());
- $this->assertTrue($form['a']->getData());
- $this->assertTrue($form['b']->getData());
- $this->assertFalse($form['c']->getData());
- $this->assertFalse($form['d']->getData());
- $this->assertFalse($form['e']->getData());
- $this->assertSame('1', $form['a']->getClientData());
- $this->assertSame('1', $form['b']->getClientData());
- $this->assertSame('', $form['c']->getClientData());
- $this->assertSame('', $form['d']->getClientData());
- $this->assertSame('', $form['e']->getClientData());
+ $this->assertSame(array(0 => 'a', 1 => 'b'), $form->getData());
+ $this->assertTrue($form[0]->getData());
+ $this->assertTrue($form[1]->getData());
+ $this->assertFalse($form[2]->getData());
+ $this->assertFalse($form[3]->getData());
+ $this->assertFalse($form[4]->getData());
+ $this->assertSame('1', $form[0]->getClientData());
+ $this->assertSame('1', $form[1]->getClientData());
+ $this->assertSame('', $form[2]->getClientData());
+ $this->assertSame('', $form[3]->getClientData());
+ $this->assertSame('', $form[4]->getClientData());
+ }
+
+ public function testBindMultipleExpandedObjectChoices()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'multiple' => true,
+ 'expanded' => true,
+ 'choice_list' => new ObjectChoiceList(
+ $this->objectChoices,
+ // label path
+ 'name',
+ array(),
+ null,
+ // value path
+ 'id'
+ ),
+ ));
+
+ $form->bind(array(0 => '1', 1 => '2'));
+
+ $this->assertSame(array($this->objectChoices[0], $this->objectChoices[1]), $form->getData());
+ $this->assertTrue($form[0]->getData());
+ $this->assertTrue($form[1]->getData());
+ $this->assertFalse($form[2]->getData());
+ $this->assertFalse($form[3]->getData());
+ $this->assertFalse($form[4]->getData());
+ $this->assertSame('1', $form[0]->getClientData());
+ $this->assertSame('1', $form[1]->getClientData());
+ $this->assertSame('', $form[2]->getClientData());
+ $this->assertSame('', $form[3]->getClientData());
+ $this->assertSame('', $form[4]->getClientData());
}
public function testBindMultipleExpandedNumericChoices()
@@ -284,7 +447,7 @@ class ChoiceTypeTest extends TypeTestCase
'choices' => $this->numericChoices,
));
- $form->bind(array(1 => 1, 2 => 2));
+ $form->bind(array(1 => '1', 2 => '2'));
$this->assertSame(array(1, 2), $form->getData());
$this->assertFalse($form[0]->getData());
@@ -437,7 +600,8 @@ class ChoiceTypeTest extends TypeTestCase
));
$view = $form->createView();
- $this->assertSame($choices, $view->get('choices'));
+ $this->assertSame(array('0', '1', '2', '3'), $view->get('choices'));
+ $this->assertSame(array('A', 'B', 'C', 'D'), $view->get('choice_labels'));
}
public function testPassPreferredChoicesToView()
@@ -449,8 +613,22 @@ class ChoiceTypeTest extends TypeTestCase
));
$view = $form->createView();
- $this->assertSame(array('a' => 'A', 'c' => 'C'), $view->get('choices'));
- $this->assertSame(array('b' => 'B', 'd' => 'D'), $view->get('preferred_choices'));
+ $this->assertSame(array(0 => '0', 2 => '2'), $view->get('choices'));
+ $this->assertSame(array(1 => '1', 3 => '3'), $view->get('preferred_choices'));
+ $this->assertSame(array('A', 'B', 'C', 'D'), $view->get('choice_labels'));
+ }
+
+ public function testPassHierarchicalChoicesToView()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => $this->groupedChoices,
+ 'preferred_choices' => array('b', 'd'),
+ ));
+ $view = $form->createView();
+
+ $this->assertSame(array('Symfony' => array(0 => '0', 2 => '2'), 'Doctrine' => array(4 => '4')), $view->get('choices'));
+ $this->assertSame(array('Symfony' => array(1 => '1'), 'Doctrine' => array(3 => '3')), $view->get('preferred_choices'));
+ $this->assertSame(array(0 => 'Bernhard', 1 => 'Fabien', 2 => 'Kris', 3 => 'Jon', 4 => 'Roman'), $view->get('choice_labels'));
}
public function testAdjustFullNameForMultipleNonExpanded()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CountryTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CountryTypeTest.php
index ff79433e0e..a6a150a490 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CountryTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CountryTypeTest.php
@@ -21,17 +21,18 @@ class CountryTypeTest extends LocalizedTestCase
$form = $this->factory->create('country');
$view = $form->createView();
$choices = $view->get('choices');
+ $labels = $view->get('choice_labels');
- $this->assertArrayHasKey('DE', $choices);
- $this->assertEquals('Deutschland', $choices['DE']);
- $this->assertArrayHasKey('GB', $choices);
- $this->assertEquals('Vereinigtes Königreich', $choices['GB']);
- $this->assertArrayHasKey('US', $choices);
- $this->assertEquals('Vereinigte Staaten', $choices['US']);
- $this->assertArrayHasKey('FR', $choices);
- $this->assertEquals('Frankreich', $choices['FR']);
- $this->assertArrayHasKey('MY', $choices);
- $this->assertEquals('Malaysia', $choices['MY']);
+ $this->assertContains('DE', $choices);
+ $this->assertEquals('Deutschland', $labels['DE']);
+ $this->assertContains('GB', $choices);
+ $this->assertEquals('Vereinigtes Königreich', $labels['GB']);
+ $this->assertContains('US', $choices);
+ $this->assertEquals('Vereinigte Staaten', $labels['US']);
+ $this->assertContains('FR', $choices);
+ $this->assertEquals('Frankreich', $labels['FR']);
+ $this->assertContains('MY', $choices);
+ $this->assertEquals('Malaysia', $labels['MY']);
}
public function testUnknownCountryIsNotIncluded()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTimeTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTimeTypeTest.php
index e6046c987b..27f3108d9a 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTimeTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTimeTypeTest.php
@@ -237,6 +237,9 @@ class DateTimeTypeTest extends LocalizedTestCase
{
$form = $this->factory->create('datetime', null, array(
'invalid_message' => 'Customized invalid message',
+ // Only possible with the "text" widget, because the "choice"
+ // widget automatically fields invalid values
+ 'widget' => 'text',
));
$form->bind(array(
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php
index 71e8bc38f1..d1b9c45023 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php
@@ -325,7 +325,7 @@ class DateTypeTest extends LocalizedTestCase
$view = $form->createView();
- $this->assertSame(array(2010 => '2010', 2011 => '2011'), $view->getChild('year')->get('choices'));
+ $this->assertSame(array(2010 => '2010', 2011 => '2011'), $view->getChild('year')->get('choice_labels'));
}
public function testMonthsOption()
@@ -336,7 +336,55 @@ class DateTypeTest extends LocalizedTestCase
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('month')->get('choices'));
+ $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('month')->get('choice_labels'));
+ }
+
+ public function testMonthsOptionNumericIfFormatContainsNoMonth()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(6, 7),
+ 'format' => 'yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('month')->get('choice_labels'));
+ }
+
+ public function testMonthsOptionShortFormat()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(1, 4),
+ 'format' => 'dd.MMM.yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertSame(array(1 => 'Jän', 4 => 'Apr'), $view->getChild('month')->get('choice_labels'));
+ }
+
+ public function testMonthsOptionLongFormat()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(1, 4),
+ 'format' => 'dd.MMMM.yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertSame(array(1 => 'Jänner', 4 => 'April'), $view->getChild('month')->get('choice_labels'));
+ }
+
+ public function testMonthsOptionLongFormatWithDifferentTimezone()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(1, 4),
+ 'format' => 'dd.MMMM.yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertSame(array(1 => 'Jänner', 4 => 'April'), $view->getChild('month')->get('choice_labels'));
}
public function testIsDayWithinRangeReturnsTrueIfWithin()
@@ -347,7 +395,7 @@ class DateTypeTest extends LocalizedTestCase
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('day')->get('choices'));
+ $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('day')->get('choice_labels'));
}
public function testIsPartiallyFilledReturnsFalseIfSingleText()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LanguageTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LanguageTypeTest.php
index 096b568edd..b8a1595bb9 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LanguageTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LanguageTypeTest.php
@@ -21,17 +21,18 @@ class LanguageTypeTest extends LocalizedTestCase
$form = $this->factory->create('language');
$view = $form->createView();
$choices = $view->get('choices');
+ $labels = $view->get('choice_labels');
- $this->assertArrayHasKey('en', $choices);
- $this->assertEquals('Englisch', $choices['en']);
- $this->assertArrayHasKey('en_GB', $choices);
- $this->assertEquals('Britisches Englisch', $choices['en_GB']);
- $this->assertArrayHasKey('en_US', $choices);
- $this->assertEquals('Amerikanisches Englisch', $choices['en_US']);
- $this->assertArrayHasKey('fr', $choices);
- $this->assertEquals('Französisch', $choices['fr']);
- $this->assertArrayHasKey('my', $choices);
- $this->assertEquals('Birmanisch', $choices['my']);
+ $this->assertContains('en', $choices);
+ $this->assertEquals('Englisch', $labels[array_search('en', $choices)]);
+ $this->assertContains('en_GB', $choices);
+ $this->assertEquals('Britisches Englisch', $labels[array_search('en_GB', $choices)]);
+ $this->assertContains('en_US', $choices);
+ $this->assertEquals('Amerikanisches Englisch', $labels[array_search('en_US', $choices)]);
+ $this->assertContains('fr', $choices);
+ $this->assertEquals('Französisch', $labels[array_search('fr', $choices)]);
+ $this->assertContains('my', $choices);
+ $this->assertEquals('Birmanisch', $labels[array_search('my', $choices)]);
}
public function testMultipleLanguagesIsNotIncluded()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LocaleTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LocaleTypeTest.php
index c813c6c3cc..ebd89940de 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LocaleTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LocaleTypeTest.php
@@ -21,12 +21,13 @@ class LocaleTypeTest extends LocalizedTestCase
$form = $this->factory->create('locale');
$view = $form->createView();
$choices = $view->get('choices');
+ $labels = $view->get('choice_labels');
- $this->assertArrayHasKey('en', $choices);
- $this->assertEquals('Englisch', $choices['en']);
- $this->assertArrayHasKey('en_GB', $choices);
- $this->assertEquals('Englisch (Vereinigtes Königreich)', $choices['en_GB']);
- $this->assertArrayHasKey('zh_Hant_MO', $choices);
- $this->assertEquals('Chinesisch (traditionell, Sonderverwaltungszone Macao)', $choices['zh_Hant_MO']);
+ $this->assertContains('en', $choices);
+ $this->assertEquals('Englisch', $labels[array_search('en', $choices)]);
+ $this->assertContains('en_GB', $choices);
+ $this->assertEquals('Englisch (Vereinigtes Königreich)', $labels[array_search('en_GB', $choices)]);
+ $this->assertContains('zh_Hant_MO', $choices);
+ $this->assertEquals('Chinesisch (traditionell, Sonderverwaltungszone Macao)', $labels[array_search('zh_Hant_MO', $choices)]);
}
}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/RadioTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/RadioTypeTest.php
index 869a33b02e..0d3b9d2e69 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/RadioTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/RadioTypeTest.php
@@ -13,14 +13,6 @@ namespace Symfony\Tests\Component\Form\Extension\Core\Type;
class RadioTypeTest extends TypeTestCase
{
- public function testPassValueToView()
- {
- $form = $this->factory->create('radio', null, array('value' => 'foobar'));
- $view = $form->createView();
-
- $this->assertEquals('foobar', $view->get('value'));
- }
-
public function testPassParentFullNameToView()
{
$parent = $this->factory->createNamed('field', 'parent');
@@ -29,22 +21,4 @@ class RadioTypeTest extends TypeTestCase
$this->assertEquals('parent', $view['child']->get('full_name'));
}
-
- public function testCheckedIfDataTrue()
- {
- $form = $this->factory->create('radio');
- $form->setData(true);
- $view = $form->createView();
-
- $this->assertTrue($view->get('checked'));
- }
-
- public function testNotCheckedIfDataFalse()
- {
- $form = $this->factory->create('radio');
- $form->setData(false);
- $view = $form->createView();
-
- $this->assertFalse($view->get('checked'));
- }
}
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimeTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimeTypeTest.php
index c9ba9a02d0..1b46c75207 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimeTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimeTypeTest.php
@@ -243,7 +243,7 @@ class TimeTypeTest extends LocalizedTestCase
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('hour')->get('choices'));
+ $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('hour')->get('choice_labels'));
}
public function testIsMinuteWithinRange_returnsTrueIfWithin()
@@ -254,7 +254,7 @@ class TimeTypeTest extends LocalizedTestCase
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('minute')->get('choices'));
+ $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('minute')->get('choice_labels'));
}
public function testIsSecondWithinRange_returnsTrueIfWithin()
@@ -266,7 +266,7 @@ class TimeTypeTest extends LocalizedTestCase
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('second')->get('choices'));
+ $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('second')->get('choice_labels'));
}
public function testIsPartiallyFilled_returnsFalseIfCompletelyEmpty()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimezoneTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimezoneTypeTest.php
index 94e32f9a87..be7db448fe 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimezoneTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimezoneTypeTest.php
@@ -19,13 +19,14 @@ class TimezoneTypeTest extends TypeTestCase
$form = $this->factory->create('timezone');
$view = $form->createView();
$choices = $view->get('choices');
+ $labels = $view->get('choice_labels');
$this->assertArrayHasKey('Africa', $choices);
- $this->assertArrayHasKey('Africa/Kinshasa', $choices['Africa']);
- $this->assertEquals('Kinshasa', $choices['Africa']['Africa/Kinshasa']);
+ $this->assertContains('Africa/Kinshasa', $choices['Africa']);
+ $this->assertEquals('Kinshasa', $labels[array_search('Africa/Kinshasa', $choices['Africa'])]);
$this->assertArrayHasKey('America', $choices);
- $this->assertArrayHasKey('America/New_York', $choices['America']);
- $this->assertEquals('New York', $choices['America']['America/New_York']);
+ $this->assertContains('America/New_York', $choices['America']);
+ $this->assertEquals('New York', $labels[array_search('America/New_York', $choices['America'])]);
}
}
diff --git a/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php b/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php
index 3938e13117..20e110a791 100644
--- a/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php
+++ b/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php
@@ -15,42 +15,6 @@ use Symfony\Component\Form\Util\FormUtil;
class FormUtilTest extends \PHPUnit_Framework_TestCase
{
- public function toArrayKeyProvider()
- {
- return array(
- array(0, 0),
- array('0', 0),
- array('1', 1),
- array(false, 0),
- array(true, 1),
- array('', ''),
- array(null, ''),
- array('1.23', '1.23'),
- array('foo', 'foo'),
- array('foo10', 'foo10'),
- );
- }
-
- /**
- * @dataProvider toArrayKeyProvider
- */
- public function testToArrayKey($in, $out)
- {
- $this->assertSame($out, FormUtil::toArrayKey($in));
- }
-
- public function testToArrayKeys()
- {
- $in = $out = array();
-
- foreach ($this->toArrayKeyProvider() as $call) {
- $in[] = $call[0];
- $out[] = $call[1];
- }
-
- $this->assertSame($out, FormUtil::toArrayKeys($in));
- }
-
public function isChoiceGroupProvider()
{
return array(
@@ -85,14 +49,17 @@ class FormUtilTest extends \PHPUnit_Framework_TestCase
public function isChoiceSelectedProvider()
{
+ // The commented cases should not be necessary anymore, because the
+ // choice lists should assure that both values passed here are always
+ // strings
return array(
- array(true, 0, 0),
- array(true, '0', 0),
- array(true, '1', 1),
- array(true, false, 0),
- array(true, true, 1),
+// array(true, 0, 0),
+ array(true, '0', '0'),
+ array(true, '1', '1'),
+// array(true, false, 0),
+// array(true, true, 1),
array(true, '', ''),
- array(true, null, ''),
+// array(true, null, ''),
array(true, '1.23', '1.23'),
array(true, 'foo', 'foo'),
array(true, 'foo10', 'foo10'),