diff --git a/CHANGELOG-2.1.md b/CHANGELOG-2.1.md
index 9dd55557960a..9b7b63144424 100644
--- a/CHANGELOG-2.1.md
+++ b/CHANGELOG-2.1.md
@@ -165,6 +165,45 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
* allowed setting different options for RepeatedType fields (like the label)
* added support for empty form name at root level, this enables rendering forms
without form name prefix in field names
+
+ * [BC BREAK] made form naming more restrictive. Form and field names must
+ start with a letter, digit or underscore and only contain letters, digits,
+ underscores, hyphens and colons
+
+ * [BC BREAK] changed default name of the prototype in the "collection" type
+ from "$$name$$" to "__name__". No dollars are appended/prepended to custom
+ names anymore.
+
+ * [BC BREAK] greatly improved `ChoiceListInterface` and all of its
+ implementations. `EntityChoiceList` was adapted, the methods `getEntities()`,
+ `getEntitiesByKeys()`, `getIdentifier()` and `getIdentifierValues()` were
+ removed/made private. Instead of the first two you can use `getChoices()`
+ and `getChoicesByValues()`, for the latter two no replacement exists.
+ `ArrayChoiceList` was replaced by `SimpleChoiceList`.
+ `PaddedChoiceList`, `MonthChoiceList` and `TimezoneChoiceList` were removed.
+ Their functionality was merged into `DateType`, `TimeType` and `TimezoneType`.
+
+ * [BC BREAK] removed `EntitiesToArrayTransformer` and `EntityToIdTransformer`.
+ The former has been replaced by `CollectionToArrayTransformer` in combination
+ with `EntityChoiceList`, the latter is not required in the core anymore.
+
+ * [BC BREAK] 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
diff --git a/UPGRADE-2.1.md b/UPGRADE-2.1.md
index 985463f06cd9..9b79eb32ae44 100644
--- a/UPGRADE-2.1.md
+++ b/UPGRADE-2.1.md
@@ -76,4 +76,68 @@ 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.
+
+* 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`.
+
+* In the template of the choice type, the structure of the "choices" variable
+ has changed
+
+ "choices" now contains ChoiceView objects with two getters `getValue()`
+ and `getLabel()` to access the choice data. The indices of the array
+ store an index whose generation is controlled by the "index_generation"
+ option of the choice field.
+
+ Before:
+
+ {% for choice, label in choices %}
+
+ {{ label }}
+
+ {% endfor %}
+
+ After:
+
+ {% for choice in choices %}
+
+ {{ choice.label }}
+
+ {% endfor %}
+
+* In the template of the collection type, the default name of the prototype
+ field has changed from "$$name$$" to "__name__"
+
+ For custom names, no dollars are prepended/appended anymore. You are advised
+ to prepend and append double underscores wherever you have configured the
+ prototype name manually.
+
+ Before:
+
+ $builder->add('tags', 'collection', array('prototype' => 'proto'));
+
+ // results in the name "$$proto$$" in the template
+
+ After:
+
+ $builder->add('tags', 'collection', array('prototype' => '__proto__'));
+
+ // results in the name "__proto__" in the template
diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
index f81d58bd4f84..ee8019a0fd2d 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
@@ -13,11 +13,17 @@
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,289 @@ 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 A property path pointing to the property used
+ * to group the choices. Only allowed if
+ * the choices are given as flat array.
*/
- 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;
-
- // 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).');
- }
+ $this->loaded = is_array($entities) || $entities instanceof \Traversable;
- if (!is_array($choices) && !$choices instanceof \Closure && !is_null($choices)) {
- throw new UnexpectedTypeException($choices, 'array or \Closure or null');
+ if (!$this->loaded) {
+ // Make sure the constraints of the parent constructor are
+ // fulfilled
+ $entities = array();
}
- $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();
- } else {
- $entities = $this->em->getRepository($this->class)->findAll();
+ return parent::getChoices();
+ }
+
+ /**
+ * Returns the values for the entities
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getValues()
+ {
+ if (!$this->loaded) {
+ $this->load();
}
- $this->choices = array();
- $this->entities = array();
+ return parent::getValues();
+ }
- if ($this->groupBy) {
- $entities = $this->groupEntities($entities, $this->groupBy);
+ /**
+ * Returns the choice views of the preferred choices as nested array with
+ * the choice groups as top-level keys.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getPreferredViews()
+ {
+ if (!$this->loaded) {
+ $this->load();
}
- $this->loadEntities($entities);
+ return parent::getPreferredViews();
+ }
- return $this->choices;
+ /**
+ * Returns the choice views of the choices that are not preferred as nested
+ * array with the choice groups as top-level keys.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getRemainingViews()
+ {
+ if (!$this->loaded) {
+ $this->load();
+ }
+
+ return parent::getRemainingViews();
}
- private function groupEntities($entities, $groupBy)
+ /**
+ * 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)
{
- $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 (!$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);
}
- if (empty($group)) {
- $grouped[] = $entity;
- } else {
- $grouped[$group][] = $entity;
- }
+ $this->load();
}
- return $grouped;
+ return parent::getChoicesForValues($values);
}
/**
- * Converts entities into choices with support for groups.
+ * Returns the values corresponding to the given entities.
*
- * 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.
+ * @param array $entities
*
- * 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().
+ * @return array
*
- * @param array $entities An array of entities
- * @param string $group A group name
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
- private function loadEntities($entities, $group = null)
+ public function getValuesForChoices(array $entities)
{
- foreach ($entities as $key => $entity) {
- if (is_array($entity)) {
- // Entities are in named groups
- $this->loadEntities($entity, $key);
- continue;
- }
+ if (!$this->loaded) {
+ // Optimize performance for single-field identifiers. We already
+ // know that the IDs are used as values
- if ($this->propertyPath) {
- // If the property option was given, use it
- $value = $this->propertyPath->getValue($entity);
- } else {
- $value = (string) $entity;
- }
+ // Attention: This optimization does not check choices for existence
+ if (count($this->identifier) === 1) {
+ $values = array();
- 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));
- }
+ foreach ($entities as $entity) {
+ if ($entity instanceof $this->class) {
+ // Make sure to convert to the right format
+ $values[] = $this->fixValue(current($this->getIdentifierValues($entity)));
+ }
+ }
- if (null === $group) {
- // Flat list of choices
- $this->choices[$id] = $value;
- } else {
- // Nested choices
- $this->choices[$group][$id] = $value;
+ return $values;
}
- $this->entities[$id] = $entity;
+ $this->load();
}
+
+ return parent::getValuesForChoices($entities);
}
/**
- * Returns the fields of which the identifier of the underlying class consists.
+ * Returns the indices corresponding to the given entities.
+ *
+ * @param array $entities
*
* @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
- public function getIdentifier()
+ public function getIndicesForChoices(array $entities)
{
- return $this->identifier;
+ 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 according entities for the choices.
+ * Returns the entities corresponding to the given values.
+ *
+ * @param array $values
*
- * 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
*
- * @return array An array of entities
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
*/
- public function getEntities()
+ 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 $this->entities;
+ return parent::getIndicesForValues($values);
}
/**
- * Returns the entities for the given keys.
+ * Creates a new unique index for this entity.
+ *
+ * If the entity has a single-field identifier, this identifier is used.
*
- * 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.
+ * Otherwise a new integer is generated.
*
- * If they have single identifiers, they are either fetched from the
- * internal entity cache (if filled) or loaded from the database.
+ * @param mixed $choice The choice to create an index for
*
- * @param array $keys The choice key (for entities with composite
- * identifiers) or entity ID (for entities with single
- * identifiers)
- * @return object[] The matching entity
+ * @return integer|string A unique index containing only ASCII letters,
+ * digits and underscores.
*/
- public function getEntitiesByKeys(array $keys)
+ protected function createIndex($entity)
{
- if (!$this->loaded) {
- $this->load();
+ if (count($this->identifier) === 1) {
+ return current($this->getIdentifierValues($entity));
}
- $found = array();
+ return parent::createIndex($entity);
+ }
- foreach ($keys as $key) {
- if (isset($this->entities[$key])) {
- $found[] = $this->entities[$key];
- }
+ /**
+ * 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();
+ }
+
+ 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(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
}
- return $found;
+ $this->loaded = true;
}
/**
@@ -299,7 +358,7 @@ public function getEntitiesByKeys(array $keys)
*
* @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 a64496e2f6ac..ea459a648715 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php
@@ -19,9 +19,21 @@
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
+ * @return array The entities.
*/
function getEntities();
+
+ /**
+ * Returns an array of entities matching the given identifiers.
+ *
+ * @param string $identifier The identifier field of the object. This method
+ * is not applicable for fields with multiple
+ * identifiers.
+ * @param array $values The values of the identifiers.
+ *
+ * @return array The entities.
+ */
+ 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 eb585f0b9e7a..2d92b03784fd 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php
@@ -13,6 +13,7 @@
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Doctrine\ORM\QueryBuilder;
+use Doctrine\DBAL\Connection;
/**
* Getting Entities through the ORM QueryBuilder
@@ -62,4 +63,20 @@ public function getEntities()
{
return $this->queryBuilder->getQuery()->execute();
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getEntitiesByIds($identifier, array $values)
+ {
+ $qb = clone ($this->queryBuilder);
+ $alias = current($qb->getRootAliases());
+ $parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier;
+ $where = $qb->expr()->in($alias.'.'.$identifier, ':'.$parameter);
+
+ return $qb->andWhere($where)
+ ->getQuery()
+ ->setParameter($parameter, $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 000000000000..2fa93356e4fb
--- /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 $collection A collection of entities
+ *
+ * @return mixed An array of entities
+ */
+ 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 entities
+ *
+ * @return Collection A collection of entities
+ */
+ 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 753aa59c314c..000000000000
--- 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 0de01caca40f..000000000000
--- 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 7a4c67f3a136..0937c1c001a4 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\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 @@ public function buildForm(FormBuilder $builder, array $options)
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 @@ public function getDefaultOptions(array $options)
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/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
index 2de31d73bacc..cd9f808a4558 100644
--- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php
@@ -15,6 +15,7 @@
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Util\FormUtil;
/**
@@ -95,9 +96,9 @@ public function isChoiceGroup($label)
return FormUtil::isChoiceGroup($label);
}
- public function isChoiceSelected(FormView $view, $choice)
+ public function isChoiceSelected(FormView $view, ChoiceView $choice)
{
- return FormUtil::isChoiceSelected($choice, $view->get('value'));
+ return FormUtil::isChoiceSelected($choice->getValue(), $view->get('value'));
}
/**
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 3ba5fc5f4bd5..f07d31f17b54 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_choice in choice %}
+ {{ nested_choice.label|trans({}, translation_domain) }}
{% endfor %}
{% else %}
- {{ label|trans({}, translation_domain) }}
+ {{ choice.label|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 cb95136e633d..66c664fb1d62 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)): ?>
+
+
+ isChoiceSelected($form, $nested_choice)): ?> selected="selected">escape($view['translator']->trans($nested_choice->getLabel(), 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->getLabel(), array(), $translation_domain)) ?>
diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php
index 084381aa1e6e..3fac287cf3f4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php
@@ -16,6 +16,7 @@
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Util\FormUtil;
/**
@@ -63,9 +64,9 @@ public function isChoiceGroup($label)
return FormUtil::isChoiceGroup($label);
}
- public function isChoiceSelected(FormView $view, $choice)
+ public function isChoiceSelected(FormView $view, ChoiceView $choice)
{
- return FormUtil::isChoiceSelected($choice, $view->get('value'));
+ return FormUtil::isChoiceSelected($choice->getValue(), $view->get('value'));
}
/**
diff --git a/src/Symfony/Component/Form/Exception/StringCastException.php b/src/Symfony/Component/Form/Exception/StringCastException.php
new file mode 100644
index 000000000000..ff882ae8f67f
--- /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 0ab969afe723..000000000000
--- 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 000000000000..311e0f73617e
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php
@@ -0,0 +1,602 @@
+
+ *
+ * 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\Form;
+use Symfony\Component\Form\Util\FormUtil;
+use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\Form\Exception\InvalidConfigurationException;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+
+/**
+ * Base class for choice list implementations.
+ *
+ * @author Bernhard Schussek
+ */
+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 preferred view objects as hierarchy containing also the choice groups
+ * with the indices of the matching choices as bottom-level keys.
+ *
+ * @var array
+ */
+ private $preferredViews = array();
+
+ /**
+ * The non-preferred view objects as hierarchy containing also the choice
+ * groups with the indices of the matching choices as bottom-level keys.
+ *
+ * @var array
+ */
+ private $remainingViews = 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 = self::GENERATE, $indexStrategy = self::GENERATE)
+ {
+ $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)
+ {
+ $this->choices = array();
+ $this->values = array();
+ $this->preferredViews = array();
+ $this->remainingViews = array();
+
+ $this->addChoices(
+ $this->preferredViews,
+ $this->remainingViews,
+ $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 values for the choices
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Returns the choice views of the preferred choices as nested array with
+ * the choice groups as top-level keys.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getPreferredViews()
+ {
+ return $this->preferredViews;
+ }
+
+ /**
+ * Returns the choice views of the choices that are not preferred as nested
+ * array with the choice groups as top-level keys.
+ *
+ * @return array
+ *
+ * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
+ */
+ public function getRemainingViews()
+ {
+ return $this->remainingViews;
+ }
+
+ /**
+ * 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 (self::COPY_CHOICE === $this->valueStrategy) {
+ 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 (0 === count($values)) {
+ 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 (self::COPY_CHOICE === $this->valueStrategy) {
+ 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 (0 === count($choices)) {
+ 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 === $givenChoice) {
+ $indices[] = $i;
+ unset($choices[$j]);
+
+ if (0 === count($choices)) {
+ 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 (0 === count($values)) {
+ break 2;
+ }
+ }
+ }
+ }
+
+ return $indices;
+ }
+
+ /**
+ * Recursively adds the given choices to the list.
+ *
+ * @param array $bucketForPreferred The bucket where to store the preferred
+ * view objects.
+ * @param array $bucketForRemaining The bucket where to store the
+ * non-preferred view objects.
+ * @param array $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)
+ {
+ if (!is_array($choices) && !$choices instanceof \Traversable) {
+ throw new UnexpectedTypeException($choices, 'array or \Traversable');
+ }
+
+ // 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
+ * view objects.
+ * @param array $bucketForRemaining The bucket where to store the
+ * non-preferred view objects.
+ * @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
+ * view objects.
+ * @param array $bucketForRemaining The bucket where to store the
+ * non-preferred view objects.
+ * @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);
+
+ if ('' === $index || null === $index || !Form::isValidName((string)$index)) {
+ throw new InvalidConfigurationException('The choice list index "' . $index . '" is invalid. Please set the choice field option "index_generation" to ChoiceList::GENERATE.');
+ }
+
+ $value = $this->createValue($choice);
+
+ if (!is_scalar($value)) {
+ throw new InvalidConfigurationException('The choice list value of type "' . gettype($value) . '" should be a scalar. Please set the choice field option "value_generation" to ChoiceList::GENERATE.');
+ }
+
+ // Always store values as strings to facilitate comparisons
+ $value = $this->fixValue($value);
+ $view = new ChoiceView($value, $label);
+
+ $this->choices[$index] = $this->fixChoice($choice);
+ $this->values[$index] = $value;
+
+ if ($this->isPreferred($choice, $preferredChoices)) {
+ $bucketForPreferred[$index] = $view;
+ } else {
+ $bucketForRemaining[$index] = $view;
+ }
+ }
+
+ /**
+ * 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 (self::COPY_CHOICE === $this->indexStrategy) {
+ 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 (self::COPY_CHOICE === $this->valueStrategy) {
+ 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)
+ {
+ foreach ($values as $i => $value) {
+ $values[$i] = $this->fixValue($value);
+ }
+
+ return $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)
+ {
+ foreach ($indices as $i => $index) {
+ $indices[$i] = $this->fixIndex($index);
+ }
+
+ return $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 f9d0ab877e5c..122b50e7e56e 100644
--- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php
+++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php
@@ -11,12 +11,130 @@
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 values for the choices
+ *
+ * @return array The values with the corresponding choice indices as keys.
+ */
+ function getValues();
+
+ /**
+ * Returns the choice views of the preferred choices as nested array with
+ * the choice groups as top-level keys.
+ *
+ * Example:
+ *
+ *
+ * array(
+ * 'Group 1' => array(
+ * 10 => ChoiceView object,
+ * 20 => ChoiceView object,
+ * ),
+ * 'Group 2' => array(
+ * 30 => ChoiceView object,
+ * ),
+ * )
+ *
+ *
+ * @return array A nested array containing the views with the corresponding
+ * choice indices as keys on the lowest levels and the choice
+ * group names in the keys of the higher levels.
+ */
+ function getPreferredViews();
+
+ /**
+ * Returns the choice views of the choices that are not preferred as nested
+ * array with the choice groups as top-level keys.
+ *
+ * Example:
+ *
+ *
+ * array(
+ * 'Group 1' => array(
+ * 10 => ChoiceView object,
+ * 20 => ChoiceView object,
+ * ),
+ * 'Group 2' => array(
+ * 30 => ChoiceView object,
+ * ),
+ * )
+ *
+ *
+ * @return array A nested array containing the views with the corresponding
+ * choice indices as keys on the lowest levels and the choice
+ * group names in the keys of the higher levels.
+ *
+ * @see getPreferredValues
+ */
+ function getRemainingViews();
+
+ /**
+ * 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/MonthChoiceList.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/MonthChoiceList.php
deleted file mode 100644
index fc2f73a58843..000000000000
--- 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 000000000000..3533326be1f8
--- /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 (null !== $this->groupPath) {
+ $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 (null === $group) {
+ $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('A "__toString()" method was not found on the objects of type "' . get_class($choice) . '" passed to the choice field. To read a custom getter instead, set the argument $labelPath to the desired property path.');
+ }
+ }
+ }
+}
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 fd1f9d1b335d..000000000000
--- 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 000000000000..f59827e7d8ec
--- /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 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 57e7bb586333..000000000000
--- 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/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php
index 6652c3843e4b..e9c3c126e40a 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php
@@ -59,7 +59,7 @@ public function mapDataToForms($data, array $forms)
public function mapDataToForm($data, FormInterface $form)
{
if (!empty($data)) {
- if ($form->getAttribute('property_path') !== null) {
+ if (null !== $form->getAttribute('property_path')) {
$form->setData($form->getAttribute('property_path')->getValue($data));
}
}
@@ -77,7 +77,7 @@ public function mapFormsToData(array $forms, &$data)
public function mapFormToData(FormInterface $form, &$data)
{
- if ($form->getAttribute('property_path') !== null && $form->isSynchronized()) {
+ if (null !== $form->getAttribute('property_path') && $form->isSynchronized()) {
$propertyPath = $form->getAttribute('property_path');
// If the data is identical to the value in $data, we are
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 439acfb0a611..4b92f0d78808 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\UnexpectedTypeException;
use Symfony\Component\Form\Util\FormUtil;
-class ScalarToBooleanChoicesTransformer implements DataTransformerInterface
+/**
+ * @author Bernhard Schussek
+ */
+class ChoiceToBooleanArrayTransformer implements DataTransformerInterface
{
private $choiceList;
@@ -47,24 +50,21 @@ public function __construct(ChoiceListInterface $choiceList)
* @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 @@ public function transform($value)
*
* @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');
+ }
+
+ 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) {
+ 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 000000000000..dfab6ec4544a
--- /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 || null === $value) {
+ return null;
+ }
+
+ $choices = $this->choiceList->getChoicesForValues(array($value));
+
+ if (1 !== count($choices)) {
+ throw new TransformationFailedException('The choice "' . $value . '" does not exist or is not unique');
+ }
+
+ $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 11b00d94a6d5..eafc6c3f829c 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\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 @@ public function transform($array)
}
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 @@ public function transform($array)
*
* @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);
+ }
+
+ $result = array();
+ $unknown = array();
- foreach ($value as $choice => $selected) {
+ 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 49e8f088dfe2..e722c47065c4 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 @@ public function transform($array)
throw new UnexpectedTypeException($array, 'array');
}
- return FormUtil::toArrayKeys($array);
+ return $this->choiceList->getValuesForChoices($array);
}
/**
@@ -54,6 +72,12 @@ public function reverseTransform($array)
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/DateTimeToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php
index e9f5fb2f2a49..fb0a3bf2ddf8 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php
@@ -124,7 +124,7 @@ public function reverseTransform($value)
throw new UnexpectedTypeException($value, 'array');
}
- if (implode('', $value) === '') {
+ if ('' === implode('', $value)) {
return null;
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
index 56f4c68bcf0d..699dec00ea50 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
@@ -122,7 +122,7 @@ protected function getNumberFormatter()
{
$formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL);
- if ($this->precision !== null) {
+ if (null !== $this->precision) {
$formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->precision);
$formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode);
}
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 c6c695358603..000000000000
--- 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 ddf474781c9c..1378b2e2f727 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 @@
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 @@
*/
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(false !== $index ? 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 bcc119fee872..358c4317b693 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\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,44 +38,22 @@ public function buildForm(FormBuilder $builder, array $options)
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();
-
- // 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) {
- if ($options['multiple']) {
- $builder->add((string) $choice, 'checkbox', array(
- 'value' => $choice,
- 'label' => $value,
- // 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,
- 'translation_domain' => $options['translation_domain'],
- ));
- }
- }
+ $this->addSubFields($builder, $options['choice_list']->getPreferredViews(), $options);
+ $this->addSubFields($builder, $options['choice_list']->getRemainingViews(), $options);
}
// empty value
@@ -101,18 +82,18 @@ public function buildForm(FormBuilder $builder, array $options)
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 +104,13 @@ public function buildForm(FormBuilder $builder, array $options)
*/
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->getPreferredViews())
+ ->set('choices', $choiceList->getRemainingViews())
->set('separator', '-------------------')
->set('empty_value', $form->getAttribute('empty_value'))
;
@@ -157,6 +137,8 @@ public function getDefaultOptions(array $options)
'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,
@@ -178,4 +160,36 @@ public function getName()
{
return 'choice';
}
+
+ /**
+ * Adds the sub fields for an expanded choice field.
+ *
+ * @param FormBuilder $builder The form builder.
+ * @param array $choiceViews The choice view objects.
+ * @param array $options The build options.
+ */
+ private function addSubFields(FormBuilder $builder, array $choiceViews, array $options)
+ {
+ foreach ($choiceViews as $i => $choiceView) {
+ if (is_array($choiceView)) {
+ // Flatten groups
+ $this->addSubFields($builder, $choiceView, $options);
+ } elseif ($options['multiple']) {
+ $builder->add((string) $i, 'checkbox', array(
+ 'value' => $choiceView->getValue(),
+ 'label' => $choiceView->getLabel(),
+ // 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) $i, 'radio', array(
+ 'value' => $choiceView->getValue(),
+ 'label' => $choiceView->getLabel(),
+ 'translation_domain' => $options['translation_domain'],
+ ));
+ }
+ }
+ }
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php
index 66d49c747ca7..20ac8345f148 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/CollectionType.php
@@ -25,7 +25,7 @@ class CollectionType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['allow_add'] && $options['prototype']) {
- $prototype = $builder->create('$$' . $options['prototype_name'] . '$$', $options['type'], $options['options']);
+ $prototype = $builder->create($options['prototype_name'], $options['type'], $options['options']);
$builder->setAttribute('prototype', $prototype->getForm());
}
@@ -78,7 +78,7 @@ public function getDefaultOptions(array $options)
'allow_add' => false,
'allow_delete' => false,
'prototype' => true,
- 'prototype_name' => 'name',
+ 'prototype_name' => '__name__',
'type' => 'text',
'options' => array(),
);
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php
index cea4ab29143d..f800678d876f 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 @@ public function getDefaultOptions(array $options)
{
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/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
index 35fe0ffa2942..1c63d216419b 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
@@ -45,7 +45,7 @@ public function buildForm(FormBuilder $builder, array $options)
throw new FormException(sprintf('Options "date_widget" and "time_widget" need to be identical. Used: "date_widget" = "%s" and "time_widget" = "%s".', $options['date_widget'] ?: 'choice', $options['time_widget'] ?: 'choice'));
}
- if ($options['widget'] === 'single_text') {
+ if ('single_text' === $options['widget']) {
$builder->appendClientTransformer(new DateTimeToStringTransformer($options['data_timezone'], $options['user_timezone'], $format));
} else {
// Only pass a subset of the options to children
@@ -105,15 +105,15 @@ public function buildForm(FormBuilder $builder, array $options)
;
}
- if ($options['input'] === 'string') {
+ if ('string' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToStringTransformer($options['data_timezone'], $options['data_timezone'], $format)
));
- } elseif ($options['input'] === 'timestamp') {
+ } elseif ('timestamp' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToTimestampTransformer($options['data_timezone'], $options['data_timezone'])
));
- } elseif ($options['input'] === 'array') {
+ } elseif ('array' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToArrayTransformer($options['data_timezone'], $options['data_timezone'], $parts)
));
@@ -200,7 +200,7 @@ public function getAllowedOptionValues(array $options)
*/
public function getParent(array $options)
{
- return $options['widget'] === 'single_text' ? 'field' : 'form';
+ return 'single_text' === $options['widget'] ? 'field' : 'form';
}
/**
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index ba72257768a8..8c2af29e7fae 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;
@@ -62,35 +63,47 @@ public function buildForm(FormBuilder $builder, array $options)
$pattern
);
- if ($options['widget'] === 'single_text') {
+ if ('single_text' === $options['widget']) {
$builder->appendClientTransformer(new DateTimeToLocalizedStringTransformer($options['data_timezone'], $options['user_timezone'], $format, \IntlDateFormatter::NONE, \IntlDateFormatter::GREGORIAN, $pattern));
} else {
$yearOptions = $monthOptions = $dayOptions = array();
- if ($options['widget'] === 'choice') {
+ if ('choice' === $options['widget']) {
if (is_array($options['empty_value'])) {
$options['empty_value'] = array_merge(array('year' => null, 'month' => null, 'day' => null), $options['empty_value']);
} else {
$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'],
);
@@ -110,15 +123,15 @@ public function buildForm(FormBuilder $builder, array $options)
;
}
- if ($options['input'] === 'string') {
+ if ('string' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToStringTransformer($options['data_timezone'], $options['data_timezone'], 'Y-m-d')
));
- } elseif ($options['input'] === 'timestamp') {
+ } elseif ('timestamp' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToTimestampTransformer($options['data_timezone'], $options['data_timezone'])
));
- } elseif ($options['input'] === 'array') {
+ } elseif ('array' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToArrayTransformer($options['data_timezone'], $options['data_timezone'], array('year', 'month', 'day'))
));
@@ -199,7 +212,7 @@ public function getAllowedOptionValues(array $options)
*/
public function getParent(array $options)
{
- return $options['widget'] === 'single_text' ? 'field' : 'form';
+ return 'single_text' === $options['widget'] ? 'field' : 'form';
}
/**
@@ -209,4 +222,28 @@ public function getName()
{
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/FieldType.php b/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php
index 05aaf230f1db..4de3fe16f265 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php
@@ -88,6 +88,11 @@ public function buildView(FormView $view, FormInterface $form)
} else {
$id = $name;
$fullName = $name;
+
+ // Strip leading underscores and digits. These are allowed in
+ // form names, but not in HTML4 ID attributes.
+ // http://www.w3.org/TR/html401/struct/global.html#adef-id
+ $id = ltrim($id, '_0123456789');
}
$types = array();
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php
index 46cacfd1bcac..da2e315c1308 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 @@ public function getDefaultOptions(array $options)
{
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 4f3fa087600b..c4841e318442 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 @@ public function getDefaultOptions(array $options)
{
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 97a5d03948cd..233db68f51e8 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 @@
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 71c19b408a21..7bfe50dc6b81 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 @@
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;
@@ -35,37 +35,52 @@ public function buildForm(FormBuilder $builder, array $options)
$parts[] = 'second';
}
- if ($options['widget'] === 'single_text') {
+ if ('single_text' === $options['widget']) {
$builder->appendClientTransformer(new DateTimeToStringTransformer($options['data_timezone'], $options['user_timezone'], $format));
} else {
$hourOptions = $minuteOptions = $secondOptions = array();
- if ($options['widget'] === 'choice') {
+ if ('choice' === $options['widget']) {
if (is_array($options['empty_value'])) {
$options['empty_value'] = array_merge(array('hour' => null, 'minute' => null, 'second' => null), $options['empty_value']);
} else {
$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'],
);
}
@@ -88,18 +103,18 @@ public function buildForm(FormBuilder $builder, array $options)
$builder->add('second', $options['widget'], $secondOptions);
}
- $builder->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], $parts, $options['widget'] === 'text'));
+ $builder->appendClientTransformer(new DateTimeToArrayTransformer($options['data_timezone'], $options['user_timezone'], $parts, 'text' === $options['widget']));
}
- if ($options['input'] === 'string') {
+ if ('string' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToStringTransformer($options['data_timezone'], $options['data_timezone'], $format)
));
- } elseif ($options['input'] === 'timestamp') {
+ } elseif ('timestamp' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToTimestampTransformer($options['data_timezone'], $options['data_timezone'])
));
- } elseif ($options['input'] === 'array') {
+ } elseif ('array' === $options['input']) {
$builder->appendNormTransformer(new ReversedTransformer(
new DateTimeToArrayTransformer($options['data_timezone'], $options['data_timezone'], $parts)
));
@@ -169,7 +184,7 @@ public function getAllowedOptionValues(array $options)
*/
public function getParent(array $options)
{
- return $options['widget'] === 'single_text' ? 'field' : 'form';
+ return 'single_text' === $options['widget'] ? 'field' : 'form';
}
/**
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
index 2b22523af793..ef308faeeb50 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 @@ public function getName()
{
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/Extension/Core/View/ChoiceView.php b/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php
new file mode 100644
index 000000000000..4e0424790047
--- /dev/null
+++ b/src/Symfony/Component/Form/Extension/Core/View/ChoiceView.php
@@ -0,0 +1,66 @@
+
+ *
+ * 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\View;
+
+/**
+ * Represents a choice in templates.
+ *
+ * @author Bernhard Schussek
+ */
+class ChoiceView
+{
+ /**
+ * The view representation of the choice.
+ *
+ * @var string
+ */
+ private $value;
+
+ /**
+ * The label displayed to humans.
+ *
+ * @var string
+ */
+ private $label;
+
+ /**
+ * Creates a new ChoiceView.
+ *
+ * @param string $value The view representation of the choice.
+ * @param string $label The label displayed to humans.
+ */
+ public function __construct($value, $label)
+ {
+ $this->value = $value;
+ $this->label = $label;
+ }
+
+ /**
+ * Returns the choice value.
+ *
+ * @return string The view representation of the choice.
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the choice label.
+ *
+ * @return string The label displayed to humans.
+ */
+ public function getLabel()
+ {
+ return $this->label;
+ }
+}
\ No newline at end of file
diff --git a/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php b/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php
index 2b7c099403d7..dce76724c45e 100644
--- a/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php
+++ b/src/Symfony/Component/Form/Extension/Validator/Validator/DelegatingValidator.php
@@ -229,7 +229,7 @@ private function buildDataPathMapping(FormInterface $form, array &$mapping, $dat
$nestedNamePath = $namePath.'.'.$child->getName();
- if (strpos($path, '[') === 0) {
+ if (0 === strpos($path, '[')) {
$nestedDataPaths = array($dataPath.$path);
} else {
$nestedDataPaths = array($dataPath.'.'.$path);
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index aa26db6bf670..d82f2f08e3f8 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -193,6 +193,10 @@ public function __construct($name, EventDispatcherInterface $dispatcher,
$required = false, $readOnly = false, $errorBubbling = false,
$emptyData = null, array $attributes = array())
{
+ $name = (string) $name;
+
+ self::validateName($name);
+
foreach ($clientTransformers as $transformer) {
if (!$transformer instanceof DataTransformerInterface) {
throw new UnexpectedTypeException($transformer, 'Symfony\Component\Form\DataTransformerInterface');
@@ -211,7 +215,7 @@ public function __construct($name, EventDispatcherInterface $dispatcher,
}
}
- $this->name = (string) $name;
+ $this->name = $name;
$this->dispatcher = $dispatcher;
$this->types = $types;
$this->clientTransformers = $clientTransformers;
@@ -1055,4 +1059,45 @@ private function clientToNorm($value)
return $value;
}
+
+ /**
+ * Validates whether the given variable is a valid form name.
+ *
+ * @param string $name The tested form name.
+ *
+ * @throws UnexpectedTypeException If the name is not a string.
+ * @throws \InvalidArgumentException If the name contains invalid characters.
+ */
+ static public function validateName($name)
+ {
+ if (!is_string($name)) {
+ throw new UnexpectedTypeException($name, 'string');
+ }
+
+ if (!self::isValidName($name)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'The name "%s" contains illegal characters. Names should start with a letter, digit or underscore and only contains letters, digits, numbers, underscores ("_"), hyphens ("-") and colons (":").',
+ $name
+ ));
+ }
+ }
+
+ /**
+ * Returns whether the given variable contains a valid form name.
+ *
+ * A name is accepted if it
+ *
+ * * is empty
+ * * starts with a letter, digit or underscore
+ * * contains only letters, digits, numbers, underscores ("_"),
+ * hyphens ("-") and colons (":")
+ *
+ * @param string $name The tested form name.
+ *
+ * @return Boolean Whether the name is valid.
+ */
+ static public function isValidName($name)
+ {
+ return '' === $name || preg_match('/^[a-zA-Z0-9_][a-zA-Z0-9_\-:]*$/D', $name);
+ }
}
diff --git a/src/Symfony/Component/Form/FormBuilder.php b/src/Symfony/Component/Form/FormBuilder.php
index b3fcfb41fd44..9aaf47414181 100644
--- a/src/Symfony/Component/Form/FormBuilder.php
+++ b/src/Symfony/Component/Form/FormBuilder.php
@@ -123,6 +123,10 @@ class FormBuilder
*/
public function __construct($name, FormFactoryInterface $factory, EventDispatcherInterface $dispatcher, $dataClass = null)
{
+ $name = (string) $name;
+
+ Form::validateName($name);
+
$this->name = $name;
$this->factory = $factory;
$this->dispatcher = $dispatcher;
diff --git a/src/Symfony/Component/Form/Util/FormUtil.php b/src/Symfony/Component/Form/Util/FormUtil.php
index bc521efff250..d4f6b087f816 100644
--- a/src/Symfony/Component/Form/Util/FormUtil.php
+++ b/src/Symfony/Component/Form/Util/FormUtil.php
@@ -11,22 +11,11 @@
namespace Symfony\Component\Form\Util;
+/**
+ * @author Bernhard Schussek
+ */
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 +38,6 @@ static public function isChoiceGroup($choice)
*/
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/src/Symfony/Component/Form/Util/PropertyPath.php b/src/Symfony/Component/Form/Util/PropertyPath.php
index cc4626405ad2..96fa57f20214 100644
--- a/src/Symfony/Component/Form/Util/PropertyPath.php
+++ b/src/Symfony/Component/Form/Util/PropertyPath.php
@@ -67,7 +67,7 @@ public function __construct($propertyPath)
$pattern = '/^(([^\.\[]+)|\[([^\]]+)\])(.*)/';
while (preg_match($pattern, $remaining, $matches)) {
- if ($matches[2] !== '') {
+ if ('' !== $matches[2]) {
$this->elements[] = $matches[2];
$this->isIndex[] = false;
} else {
diff --git a/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php b/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php
index 74feb80d4b96..2dc3e698add6 100644
--- a/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php
+++ b/tests/Symfony/Tests/Bridge/Doctrine/Form/ChoiceList/EntityChoiceListTest.php
@@ -19,6 +19,7 @@
use Symfony\Tests\Bridge\Doctrine\Fixtures\ItemGroupEntity;
use Symfony\Tests\Bridge\Doctrine\Fixtures\SingleIdentEntity;
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class EntityChoiceListTest extends DoctrineOrmTestCase
{
@@ -89,7 +90,7 @@ public function testFlattenedChoicesAreManaged()
)
);
- $this->assertSame(array(1 => 'Foo', 2 => 'Bar'), $choiceList->getChoices());
+ $this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
}
public function testEmptyChoicesAreManaged()
@@ -132,10 +133,11 @@ public function testNestedChoicesAreManaged()
)
);
- $this->assertSame(array(
- 'group1' => array(1 => 'Foo'),
- 'group2' => array(2 => 'Bar')
- ), $choiceList->getChoices());
+ $this->assertSame(array(1 => $entity1, 2 => $entity2), $choiceList->getChoices());
+ $this->assertEquals(array(
+ 'group1' => array(1 => new ChoiceView('1', 'Foo')),
+ 'group2' => array(2 => new ChoiceView('2', 'Bar'))
+ ), $choiceList->getRemainingViews());
}
public function testGroupBySupportsString()
@@ -164,11 +166,12 @@ public function testGroupBySupportsString()
'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 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')),
+ 'Group2' => array(3 => new ChoiceView('3', 'Baz')),
+ 4 => new ChoiceView('4', 'Boo!')
+ ), $choiceList->getRemainingViews());
}
public function testGroupByInvalidPropertyPathReturnsFlatChoices()
@@ -188,13 +191,13 @@ public function testGroupByInvalidPropertyPathReturnsFlatChoices()
$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 5f77dd5a3795..8c4f65abb163 100644
--- a/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php
+++ b/tests/Symfony/Tests/Bridge/Doctrine/Form/Type/EntityTypeTest.php
@@ -29,6 +29,7 @@
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Common\Collections\ArrayCollection;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class EntityTypeTest extends TypeTestCase
{
@@ -109,7 +110,7 @@ public function testSetDataToUninitializedEntityWithNonRequired()
'property' => 'name'
));
- $this->assertEquals(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choices'));
+ $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices'));
}
public function testSetDataToUninitializedEntityWithNonRequiredToString()
@@ -125,7 +126,7 @@ public function testSetDataToUninitializedEntityWithNonRequiredToString()
'required' => false,
));
- $this->assertEquals(array("1" => 'Foo', "2" => 'Bar'), $field->createView()->get('choices'));
+ $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices'));
}
public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder()
@@ -144,7 +145,7 @@ public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder()
'query_builder' => $qb
));
- $this->assertEquals(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choices'));
+ $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices'));
}
/**
@@ -185,7 +186,7 @@ public function testSetDataSingleNull()
$field->setData(null);
$this->assertNull($field->getData());
- $this->assertEquals('', $field->getClientData());
+ $this->assertSame('', $field->getClientData());
}
public function testSetDataMultipleExpandedNull()
@@ -199,7 +200,7 @@ public function testSetDataMultipleExpandedNull()
$field->setData(null);
$this->assertNull($field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSetDataMultipleNonExpandedNull()
@@ -213,7 +214,7 @@ public function testSetDataMultipleNonExpandedNull()
$field->setData(null);
$this->assertNull($field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSubmitSingleExpandedNull()
@@ -227,7 +228,7 @@ public function testSubmitSingleExpandedNull()
$field->bind(null);
$this->assertNull($field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSubmitSingleNonExpandedNull()
@@ -241,7 +242,7 @@ public function testSubmitSingleNonExpandedNull()
$field->bind(null);
$this->assertNull($field->getData());
- $this->assertEquals('', $field->getClientData());
+ $this->assertSame('', $field->getClientData());
}
public function testSubmitMultipleNull()
@@ -254,7 +255,7 @@ public function testSubmitMultipleNull()
$field->bind(null);
$this->assertEquals(new ArrayCollection(), $field->getData());
- $this->assertEquals(array(), $field->getClientData());
+ $this->assertSame(array(), $field->getClientData());
}
public function testSubmitSingleNonExpandedSingleIdentifier()
@@ -275,8 +276,8 @@ public function testSubmitSingleNonExpandedSingleIdentifier()
$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 +299,8 @@ public function testSubmitSingleNonExpandedCompositeIdentifier()
$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 +325,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifier()
$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 +356,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifier_existingData()
$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 +382,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifier()
$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 +413,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifier_existingData()
$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 +434,7 @@ public function testSubmitSingleExpanded()
$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 +489,10 @@ public function testOverrideChoices()
$field->bind('2');
- $this->assertEquals(array(1 => 'Foo', 2 => 'Bar'), $field->createView()->get('choices'));
+ $this->assertEquals(array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')), $field->createView()->get('choices'));
$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,11 +514,11 @@ public function testGroupByChoices()
$field->bind('2');
- $this->assertEquals(2, $field->getClientData());
+ $this->assertSame('2', $field->getClientData());
$this->assertEquals(array(
- 'Group1' => array(1 => 'Foo', '2' => 'Bar'),
- 'Group2' => array(3 => 'Baz'),
- '4' => 'Boo!'
+ 'Group1' => array(1 => new ChoiceView('1', 'Foo'), 2 => new ChoiceView('2', 'Bar')),
+ 'Group2' => array(3 => new ChoiceView('3', 'Baz')),
+ '4' => new ChoiceView('4', 'Boo!')
), $field->createView()->get('choices'));
}
@@ -652,8 +653,8 @@ public function testSubmitSingleStringIdentifier()
$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 +675,8 @@ public function testSubmitCompositeStringIdentifier()
$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 877bda7c0eaf..3693afc01384 100644
--- a/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php
+++ b/tests/Symfony/Tests/Component/Form/AbstractLayoutTest.php
@@ -115,9 +115,7 @@ abstract protected function setTheme(FormView $view, array $themes);
public function testEnctype()
{
- $form = $this->factory->createNamedBuilder('form', 'na&me', null, array(
- 'property_path' => 'name',
- ))
+ $form = $this->factory->createNamedBuilder('form', 'name')
->add('file', 'file')
->getForm();
@@ -126,9 +124,7 @@ public function testEnctype()
public function testNoEnctype()
{
- $form = $this->factory->createNamedBuilder('form', 'na&me', null, array(
- 'property_path' => 'name',
- ))
+ $form = $this->factory->createNamedBuilder('form', 'name')
->add('text', 'text')
->getForm();
@@ -137,26 +133,22 @@ public function testNoEnctype()
public function testLabel()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('text', 'name');
$view = $form->createView();
$this->renderWidget($view, array('label' => 'foo'));
$html = $this->renderLabel($view);
$this->assertMatchesXpath($html,
'/label
- [@for="na&me"]
- [.="[trans]Na&me[/trans]"]
+ [@for="name"]
+ [.="[trans]Name[/trans]"]
'
);
}
public function testLabelOnForm()
{
- $form = $this->factory->createNamed('date', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('date', 'name');
$view = $form->createView();
$this->renderWidget($view, array('label' => 'foo'));
$html = $this->renderLabel($view);
@@ -164,22 +156,21 @@ public function testLabelOnForm()
$this->assertMatchesXpath($html,
'/label
[@class=" required"]
- [.="[trans]Na&me[/trans]"]
+ [.="[trans]Name[/trans]"]
'
);
}
public function testLabelWithCustomTextPassedAsOption()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('text', 'name', null, array(
'label' => 'Custom label',
));
$html = $this->renderLabel($form->createView());
$this->assertMatchesXpath($html,
'/label
- [@for="na&me"]
+ [@for="name"]
[.="[trans]Custom label[/trans]"]
'
);
@@ -187,14 +178,12 @@ public function testLabelWithCustomTextPassedAsOption()
public function testLabelWithCustomTextPassedDirectly()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('text', 'name');
$html = $this->renderLabel($form->createView(), 'Custom label');
$this->assertMatchesXpath($html,
'/label
- [@for="na&me"]
+ [@for="name"]
[.="[trans]Custom label[/trans]"]
'
);
@@ -202,15 +191,14 @@ public function testLabelWithCustomTextPassedDirectly()
public function testLabelWithCustomTextPassedAsOptionAndDirectly()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('text', 'name', null, array(
'label' => 'Custom label',
));
$html = $this->renderLabel($form->createView(), 'Overridden label');
$this->assertMatchesXpath($html,
'/label
- [@for="na&me"]
+ [@for="name"]
[.="[trans]Overridden label[/trans]"]
'
);
@@ -218,9 +206,7 @@ public function testLabelWithCustomTextPassedAsOptionAndDirectly()
public function testLabelWithCustomOptionsPassedDirectly()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('text', 'name');
$html = $this->renderLabel($form->createView(), null, array(
'attr' => array(
'class' => 'my&class'
@@ -229,7 +215,7 @@ public function testLabelWithCustomOptionsPassedDirectly()
$this->assertMatchesXpath($html,
'/label
- [@for="na&me"]
+ [@for="name"]
[@class="my&class required"]
'
);
@@ -237,9 +223,7 @@ public function testLabelWithCustomOptionsPassedDirectly()
public function testLabelWithCustomTextAndCustomOptionsPassedDirectly()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('text', 'name');
$html = $this->renderLabel($form->createView(), 'Custom label', array(
'attr' => array(
'class' => 'my&class'
@@ -248,7 +232,7 @@ public function testLabelWithCustomTextAndCustomOptionsPassedDirectly()
$this->assertMatchesXpath($html,
'/label
- [@for="na&me"]
+ [@for="name"]
[@class="my&class required"]
[.="[trans]Custom label[/trans]"]
'
@@ -257,9 +241,7 @@ public function testLabelWithCustomTextAndCustomOptionsPassedDirectly()
public function testErrors()
{
- $form = $this->factory->createNamed('text', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('text', 'name');
$form->addError(new FormError('Error 1'));
$form->addError(new FormError('Error 2'));
$view = $form->createView();
@@ -295,56 +277,49 @@ public function testWidgetById()
public function testCheckedCheckbox()
{
- $form = $this->factory->createNamed('checkbox', 'na&me', true, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('checkbox', 'name', true);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="checkbox"]
- [@name="na&me"]
+ [@name="name"]
[@checked="checked"]
[@value="1"]
'
);
}
- public function testCheckedCheckboxWithValue()
+ public function testUncheckedCheckbox()
{
- $form = $this->factory->createNamed('checkbox', 'na&me', true, array(
- 'property_path' => 'name',
- 'value' => 'foo&bar',
- ));
+ $form = $this->factory->createNamed('checkbox', 'name', false);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="checkbox"]
- [@name="na&me"]
- [@checked="checked"]
- [@value="foo&bar"]
+ [@name="name"]
+ [not(@checked)]
'
);
}
- public function testUncheckedCheckbox()
+ public function testCheckboxWithValue()
{
- $form = $this->factory->createNamed('checkbox', 'na&me', false, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('checkbox', 'name', false, array(
+ 'value' => 'foo&bar',
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="checkbox"]
- [@name="na&me"]
- [not(@checked)]
+ [@name="name"]
+ [@value="foo&bar"]
'
);
}
public function testSingleChoice()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => false,
@@ -352,11 +327,11 @@ public function testSingleChoice()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[@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]
'
@@ -365,8 +340,7 @@ public function testSingleChoice()
public function testSingleChoiceWithPreferred()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'preferred_choices' => array('&b'),
'multiple' => false,
@@ -375,12 +349,12 @@ public function testSingleChoiceWithPreferred()
$this->assertWidgetMatchesXpath($form->createView(), array('separator' => '-- sep --'),
'/select
- [@name="na&me"]
+ [@name="name"]
[@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]
'
@@ -390,8 +364,7 @@ public function testSingleChoiceWithPreferred()
public function testSingleChoiceWithPreferredAndNoSeparator()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'preferred_choices' => array('&b'),
'multiple' => false,
@@ -400,11 +373,11 @@ public function testSingleChoiceWithPreferredAndNoSeparator()
$this->assertWidgetMatchesXpath($form->createView(), array('separator' => null),
'/select
- [@name="na&me"]
+ [@name="name"]
[@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]
'
@@ -413,8 +386,7 @@ public function testSingleChoiceWithPreferredAndNoSeparator()
public function testSingleChoiceWithPreferredAndBlankSeparator()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'preferred_choices' => array('&b'),
'multiple' => false,
@@ -423,12 +395,12 @@ public function testSingleChoiceWithPreferredAndBlankSeparator()
$this->assertWidgetMatchesXpath($form->createView(), array('separator' => ''),
'/select
- [@name="na&me"]
+ [@name="name"]
[@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]
'
@@ -437,8 +409,7 @@ public function testSingleChoiceWithPreferredAndBlankSeparator()
public function testChoiceWithOnlyPreferred()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'preferred_choices' => array('&a', '&b'),
'multiple' => false,
@@ -454,8 +425,7 @@ public function testChoiceWithOnlyPreferred()
public function testSingleChoiceNonRequired()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'required' => false,
'multiple' => false,
@@ -464,12 +434,12 @@ public function testSingleChoiceNonRequired()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[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]
'
@@ -478,8 +448,7 @@ public function testSingleChoiceNonRequired()
public function testSingleChoiceNonRequiredNoneSelected()
{
- $form = $this->factory->createNamed('choice', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', null, array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'required' => false,
'multiple' => false,
@@ -488,12 +457,12 @@ public function testSingleChoiceNonRequiredNoneSelected()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[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]
'
@@ -502,8 +471,7 @@ public function testSingleChoiceNonRequiredNoneSelected()
public function testSingleChoiceWithNonRequiredEmptyValue()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => false,
@@ -513,12 +481,12 @@ public function testSingleChoiceWithNonRequiredEmptyValue()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[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]
'
@@ -527,8 +495,7 @@ public function testSingleChoiceWithNonRequiredEmptyValue()
public function testSingleChoiceRequiredWithEmptyValue()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'required' => true,
'multiple' => false,
@@ -538,12 +505,12 @@ public function testSingleChoiceRequiredWithEmptyValue()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[@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]
'
@@ -552,8 +519,7 @@ public function testSingleChoiceRequiredWithEmptyValue()
public function testSingleChoiceRequiredWithEmptyValueViaView()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'required' => true,
'multiple' => false,
@@ -562,12 +528,12 @@ public function testSingleChoiceRequiredWithEmptyValueViaView()
$this->assertWidgetMatchesXpath($form->createView(), array('empty_value' => ''),
'/select
- [@name="na&me"]
+ [@name="name"]
[@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]
'
@@ -576,8 +542,7 @@ public function testSingleChoiceRequiredWithEmptyValueViaView()
public function testSingleChoiceGrouped()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array(
'Group&1' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'Group&2' => array('&c' => 'Choice&C'),
@@ -588,16 +553,16 @@ public function testSingleChoiceGrouped()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[./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]
@@ -607,8 +572,7 @@ public function testSingleChoiceGrouped()
public function testMultipleChoice()
{
- $form = $this->factory->createNamed('choice', 'na&me', array('&a'), array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', array('&a'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => true,
'expanded' => false,
@@ -616,11 +580,11 @@ public function testMultipleChoice()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me[]"]
+ [@name="name[]"]
[@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]
'
@@ -629,8 +593,7 @@ public function testMultipleChoice()
public function testMultipleChoiceSkipEmptyValue()
{
- $form = $this->factory->createNamed('choice', 'na&me', array('&a'), array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', array('&a'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => true,
'expanded' => false,
@@ -639,11 +602,11 @@ public function testMultipleChoiceSkipEmptyValue()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me[]"]
+ [@name="name[]"]
[@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]
'
@@ -652,8 +615,7 @@ public function testMultipleChoiceSkipEmptyValue()
public function testMultipleChoiceNonRequired()
{
- $form = $this->factory->createNamed('choice', 'na&me', array('&a'), array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', array('&a'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'required' => false,
'multiple' => true,
@@ -662,11 +624,11 @@ public function testMultipleChoiceNonRequired()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me[]"]
+ [@name="name[]"]
[@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]
'
@@ -675,8 +637,7 @@ public function testMultipleChoiceNonRequired()
public function testSingleChoiceExpanded()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => true,
@@ -685,10 +646,10 @@ public function testSingleChoiceExpanded()
$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="name"][@id="name_0"][@value="0"][@checked]
+ /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="1"][not(@checked)]
+ /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
]
[count(./input)=2]
'
@@ -697,8 +658,7 @@ public function testSingleChoiceExpanded()
public function testSingleChoiceExpandedSkipEmptyValue()
{
- $form = $this->factory->createNamed('choice', 'na&me', '&a', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => true,
@@ -708,10 +668,10 @@ public function testSingleChoiceExpandedSkipEmptyValue()
$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="name"][@id="name_0"][@checked]
+ /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
+ /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
]
[count(./input)=2]
'
@@ -720,8 +680,7 @@ public function testSingleChoiceExpandedSkipEmptyValue()
public function testSingleChoiceExpandedWithBooleanValue()
{
- $form = $this->factory->createNamed('choice', 'na&me', true, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', true, array(
'choices' => array('1' => 'Choice&A', '0' => 'Choice&B'),
'multiple' => false,
'expanded' => true,
@@ -730,10 +689,10 @@ public function testSingleChoiceExpandedWithBooleanValue()
$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="name"][@id="name_0"][@checked]
+ /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
+ /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
]
[count(./input)=2]
'
@@ -742,8 +701,7 @@ public function testSingleChoiceExpandedWithBooleanValue()
public function testMultipleChoiceExpanded()
{
- $form = $this->factory->createNamed('choice', 'na&me', array('&a', '&c'), array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('choice', 'name', array('&a', '&c'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'),
'multiple' => true,
'expanded' => true,
@@ -753,12 +711,12 @@ public function testMultipleChoiceExpanded()
$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="name[0]"][@id="name_0"][@checked][not(@required)]
+ /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
+ /following-sibling::input[@type="checkbox"][@name="name[1]"][@id="name_1"][not(@checked)][not(@required)]
+ /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
+ /following-sibling::input[@type="checkbox"][@name="name[2]"][@id="name_2"][@checked][not(@required)]
+ /following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"]
]
[count(./input)=3]
'
@@ -767,13 +725,11 @@ public function testMultipleChoiceExpanded()
public function testCountry()
{
- $form = $this->factory->createNamed('country', 'na&me', 'AT', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('country', 'name', 'AT');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[./option[@value="AT"][@selected="selected"][.="[trans]Austria[/trans]"]]
[count(./option)>200]
'
@@ -782,15 +738,14 @@ public function testCountry()
public function testCountryWithEmptyValue()
{
- $form = $this->factory->createNamed('country', 'na&me', 'AT', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('country', 'name', 'AT', array(
'empty_value' => 'Select&Country',
'required' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[./option[@value=""][not(@selected)][.="[trans]Select&Country[/trans]"]]
[./option[@value="AT"][@selected="selected"][.="[trans]Austria[/trans]"]]
[count(./option)>201]
@@ -804,9 +759,7 @@ public function testCsrf()
->method('generateCsrfToken')
->will($this->returnValue('foo&bar'));
- $form = $this->factory->createNamed('csrf', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('csrf', 'name');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
@@ -818,8 +771,7 @@ public function testCsrf()
public function testDateTime()
{
- $form = $this->factory->createNamed('datetime', 'na&me', '2011-02-03 04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
'input' => 'string',
'with_seconds' => false,
));
@@ -828,26 +780,26 @@ public function testDateTime()
'/div
[
./div
- [@id="na&me_date"]
+ [@id="name_date"]
[
./select
- [@id="na&me_date_month"]
+ [@id="name_date_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_date_day"]
+ [@id="name_date_day"]
[./option[@value="3"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_date_year"]
+ [@id="name_date_year"]
[./option[@value="2011"][@selected="selected"]]
]
/following-sibling::div
- [@id="na&me_time"]
+ [@id="name_time"]
[
./select
- [@id="na&me_time_hour"]
+ [@id="name_time_hour"]
[./option[@value="4"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_time_minute"]
+ [@id="name_time_minute"]
[./option[@value="5"][@selected="selected"]]
]
]
@@ -858,8 +810,7 @@ public function testDateTime()
public function testDateTimeWithEmptyValueGlobal()
{
- $form = $this->factory->createNamed('datetime', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('datetime', 'name', null, array(
'input' => 'string',
'empty_value' => 'Change&Me',
'required' => false,
@@ -869,26 +820,26 @@ public function testDateTimeWithEmptyValueGlobal()
'/div
[
./div
- [@id="na&me_date"]
+ [@id="name_date"]
[
./select
- [@id="na&me_date_month"]
+ [@id="name_date_month"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
- [@id="na&me_date_day"]
+ [@id="name_date_day"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
- [@id="na&me_date_year"]
+ [@id="name_date_year"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
/following-sibling::div
- [@id="na&me_time"]
+ [@id="name_time"]
[
./select
- [@id="na&me_time_hour"]
+ [@id="name_time_hour"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
- [@id="na&me_time_minute"]
+ [@id="name_time_minute"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
]
@@ -899,8 +850,7 @@ public function testDateTimeWithEmptyValueGlobal()
public function testDateTimeWithEmptyValueOnTime()
{
- $form = $this->factory->createNamed('datetime', 'na&me', '2011-02-03', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('datetime', 'name', '2011-02-03', array(
'input' => 'string',
'empty_value' => array('hour' => 'Change&Me', 'minute' => 'Change&Me'),
'required' => false,
@@ -910,26 +860,26 @@ public function testDateTimeWithEmptyValueOnTime()
'/div
[
./div
- [@id="na&me_date"]
+ [@id="name_date"]
[
./select
- [@id="na&me_date_month"]
+ [@id="name_date_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_date_day"]
+ [@id="name_date_day"]
[./option[@value="3"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_date_year"]
+ [@id="name_date_year"]
[./option[@value="2011"][@selected="selected"]]
]
/following-sibling::div
- [@id="na&me_time"]
+ [@id="name_time"]
[
./select
- [@id="na&me_time_hour"]
+ [@id="name_time_hour"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
- [@id="na&me_time_minute"]
+ [@id="name_time_minute"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
]
@@ -940,8 +890,7 @@ public function testDateTimeWithEmptyValueOnTime()
public function testDateTimeWithSeconds()
{
- $form = $this->factory->createNamed('datetime', 'na&me', '2011-02-03 04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
'input' => 'string',
'with_seconds' => true,
));
@@ -950,29 +899,29 @@ public function testDateTimeWithSeconds()
'/div
[
./div
- [@id="na&me_date"]
+ [@id="name_date"]
[
./select
- [@id="na&me_date_month"]
+ [@id="name_date_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_date_day"]
+ [@id="name_date_day"]
[./option[@value="3"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_date_year"]
+ [@id="name_date_year"]
[./option[@value="2011"][@selected="selected"]]
]
/following-sibling::div
- [@id="na&me_time"]
+ [@id="name_time"]
[
./select
- [@id="na&me_time_hour"]
+ [@id="name_time_hour"]
[./option[@value="4"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_time_minute"]
+ [@id="name_time_minute"]
[./option[@value="5"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_time_second"]
+ [@id="name_time_second"]
[./option[@value="6"][@selected="selected"]]
]
]
@@ -983,8 +932,7 @@ public function testDateTimeWithSeconds()
public function testDateTimeSingleText()
{
- $form = $this->factory->createNamed('datetime', 'na&me', '2011-02-03 04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
'input' => 'string',
'date_widget' => 'single_text',
'time_widget' => 'single_text',
@@ -995,13 +943,13 @@ public function testDateTimeSingleText()
[
./input
[@type="text"]
- [@id="na&me_date"]
- [@name="na&me[date]"]
+ [@id="name_date"]
+ [@name="name[date]"]
[@value="Feb 3, 2011"]
/following-sibling::input
[@type="text"]
- [@id="na&me_time"]
- [@name="na&me[time]"]
+ [@id="name_time"]
+ [@name="name[time]"]
[@value="04:05:00"]
]
'
@@ -1011,7 +959,6 @@ public function testDateTimeSingleText()
public function testDateTimeWithWidgetSingleText()
{
$form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
- 'property_path' => 'name',
'input' => 'string',
'widget' => 'single_text',
));
@@ -1027,8 +974,7 @@ public function testDateTimeWithWidgetSingleText()
public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets()
{
- $form = $this->factory->createNamed('datetime', 'na&me', '2011-02-03 04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
'input' => 'string',
'date_widget' => 'choice',
'time_widget' => 'choice',
@@ -1038,7 +984,7 @@ public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="2011-02-03 04:05:00"]
'
);
@@ -1046,8 +992,7 @@ public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets()
public function testDateChoice()
{
- $form = $this->factory->createNamed('date', 'na&me', '2011-02-03', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('date', 'name', '2011-02-03', array(
'input' => 'string',
'widget' => 'choice',
));
@@ -1056,13 +1001,13 @@ public function testDateChoice()
'/div
[
./select
- [@id="na&me_month"]
+ [@id="name_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_day"]
+ [@id="name_day"]
[./option[@value="3"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_year"]
+ [@id="name_year"]
[./option[@value="2011"][@selected="selected"]]
]
[count(./select)=3]
@@ -1072,8 +1017,7 @@ public function testDateChoice()
public function testDateChoiceWithEmptyValueGlobal()
{
- $form = $this->factory->createNamed('date', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('date', 'name', null, array(
'input' => 'string',
'widget' => 'choice',
'empty_value' => 'Change&Me',
@@ -1084,13 +1028,13 @@ public function testDateChoiceWithEmptyValueGlobal()
'/div
[
./select
- [@id="na&me_month"]
+ [@id="name_month"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
- [@id="na&me_day"]
+ [@id="name_day"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
/following-sibling::select
- [@id="na&me_year"]
+ [@id="name_year"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
[count(./select)=3]
@@ -1100,8 +1044,7 @@ public function testDateChoiceWithEmptyValueGlobal()
public function testDateChoiceWithEmptyValueOnYear()
{
- $form = $this->factory->createNamed('date', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('date', 'name', null, array(
'input' => 'string',
'widget' => 'choice',
'required' => false,
@@ -1112,13 +1055,13 @@ public function testDateChoiceWithEmptyValueOnYear()
'/div
[
./select
- [@id="na&me_month"]
+ [@id="name_month"]
[./option[@value="1"]]
/following-sibling::select
- [@id="na&me_day"]
+ [@id="name_day"]
[./option[@value="1"]]
/following-sibling::select
- [@id="na&me_year"]
+ [@id="name_year"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
]
[count(./select)=3]
@@ -1128,8 +1071,7 @@ public function testDateChoiceWithEmptyValueOnYear()
public function testDateText()
{
- $form = $this->factory->createNamed('date', 'na&me', '2011-02-03', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('date', 'name', '2011-02-03', array(
'input' => 'string',
'widget' => 'text',
));
@@ -1138,15 +1080,15 @@ public function testDateText()
'/div
[
./input
- [@id="na&me_month"]
+ [@id="name_month"]
[@type="text"]
[@value="2"]
/following-sibling::input
- [@id="na&me_day"]
+ [@id="name_day"]
[@type="text"]
[@value="3"]
/following-sibling::input
- [@id="na&me_year"]
+ [@id="name_year"]
[@type="text"]
[@value="2011"]
]
@@ -1157,8 +1099,7 @@ public function testDateText()
public function testDateSingleText()
{
- $form = $this->factory->createNamed('date', 'na&me', '2011-02-03', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('date', 'name', '2011-02-03', array(
'input' => 'string',
'widget' => 'single_text',
));
@@ -1166,7 +1107,7 @@ public function testDateSingleText()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="Feb 3, 2011"]
'
);
@@ -1185,8 +1126,7 @@ public function testDateErrorBubbling()
public function testBirthDay()
{
- $form = $this->factory->createNamed('birthday', 'na&me', '2000-02-03', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('birthday', 'name', '2000-02-03', array(
'input' => 'string',
));
@@ -1194,13 +1134,13 @@ public function testBirthDay()
'/div
[
./select
- [@id="na&me_month"]
+ [@id="name_month"]
[./option[@value="2"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_day"]
+ [@id="name_day"]
[./option[@value="3"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_year"]
+ [@id="name_year"]
[./option[@value="2000"][@selected="selected"]]
]
[count(./select)=3]
@@ -1210,8 +1150,7 @@ public function testBirthDay()
public function testBirthDayWithEmptyValue()
{
- $form = $this->factory->createNamed('birthday', 'na&me', '1950-01-01', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('birthday', 'name', '1950-01-01', array(
'input' => 'string',
'empty_value' => '',
'required' => false,
@@ -1221,15 +1160,15 @@ public function testBirthDayWithEmptyValue()
'/div
[
./select
- [@id="na&me_month"]
+ [@id="name_month"]
[./option[@value=""][.="[trans][/trans]"]]
[./option[@value="1"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_day"]
+ [@id="name_day"]
[./option[@value=""][.="[trans][/trans]"]]
[./option[@value="1"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_year"]
+ [@id="name_year"]
[./option[@value=""][.="[trans][/trans]"]]
[./option[@value="1950"][@selected="selected"]]
]
@@ -1240,14 +1179,12 @@ public function testBirthDayWithEmptyValue()
public function testEmail()
{
- $form = $this->factory->createNamed('email', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('email', 'name', 'foo&bar');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="email"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
[not(@maxlength)]
'
@@ -1256,15 +1193,14 @@ public function testEmail()
public function testEmailWithMaxLength()
{
- $form = $this->factory->createNamed('email', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('email', 'name', 'foo&bar', array(
'max_length' => 123,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="email"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
[@maxlength="123"]
'
@@ -1273,9 +1209,7 @@ public function testEmailWithMaxLength()
public function testFile()
{
- $form = $this->factory->createNamed('file', 'na&me', null, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('file', 'name');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
@@ -1286,14 +1220,12 @@ public function testFile()
public function testHidden()
{
- $form = $this->factory->createNamed('hidden', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('hidden', 'name', 'foo&bar');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="hidden"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
'
);
@@ -1301,14 +1233,12 @@ public function testHidden()
public function testInteger()
{
- $form = $this->factory->createNamed('integer', 'na&me', 123, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('integer', 'name', 123);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="number"]
- [@name="na&me"]
+ [@name="name"]
[@value="123"]
'
);
@@ -1316,13 +1246,11 @@ public function testInteger()
public function testLanguage()
{
- $form = $this->factory->createNamed('language', 'na&me', 'de', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('language', 'name', 'de');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[./option[@value="de"][@selected="selected"][.="[trans]German[/trans]"]]
[count(./option)>200]
'
@@ -1331,13 +1259,11 @@ public function testLanguage()
public function testLocale()
{
- $form = $this->factory->createNamed('locale', 'na&me', 'de_AT', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('locale', 'name', 'de_AT');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[./option[@value="de_AT"][@selected="selected"][.="[trans]German (Austria)[/trans]"]]
[count(./option)>200]
'
@@ -1346,15 +1272,14 @@ public function testLocale()
public function testMoney()
{
- $form = $this->factory->createNamed('money', 'na&me', 1234.56, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('money', 'name', 1234.56, array(
'currency' => 'EUR',
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="1234.56"]
[contains(.., "€")]
'
@@ -1363,14 +1288,12 @@ public function testMoney()
public function testNumber()
{
- $form = $this->factory->createNamed('number', 'na&me', 1234.56, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('number', 'name', 1234.56);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="1234.56"]
'
);
@@ -1378,22 +1301,19 @@ public function testNumber()
public function testPassword()
{
- $form = $this->factory->createNamed('password', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('password', 'name', 'foo&bar');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="password"]
- [@name="na&me"]
+ [@name="name"]
'
);
}
public function testPasswordBoundNotAlwaysEmpty()
{
- $form = $this->factory->createNamed('password', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('password', 'name', null, array(
'always_empty' => false,
));
$form->bind('foo&bar');
@@ -1401,7 +1321,7 @@ public function testPasswordBoundNotAlwaysEmpty()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="password"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
'
);
@@ -1409,15 +1329,14 @@ public function testPasswordBoundNotAlwaysEmpty()
public function testPasswordWithMaxLength()
{
- $form = $this->factory->createNamed('password', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('password', 'name', 'foo&bar', array(
'max_length' => 123,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="password"]
- [@name="na&me"]
+ [@name="name"]
[@maxlength="123"]
'
);
@@ -1425,14 +1344,12 @@ public function testPasswordWithMaxLength()
public function testPercent()
{
- $form = $this->factory->createNamed('percent', 'na&me', 0.1, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('percent', 'name', 0.1);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="10"]
[contains(.., "%")]
'
@@ -1441,62 +1358,55 @@ public function testPercent()
public function testCheckedRadio()
{
- $form = $this->factory->createNamed('radio', 'na&me', true, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('radio', 'name', true);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="radio"]
- [@name="na&me"]
+ [@name="name"]
[@checked="checked"]
- [@value=""]
+ [@value="1"]
'
);
}
- public function testCheckedRadioWithValue()
+ public function testUncheckedRadio()
{
- $form = $this->factory->createNamed('radio', 'na&me', true, array(
- 'property_path' => 'name',
- 'value' => 'foo&bar',
- ));
+ $form = $this->factory->createNamed('radio', 'name', false);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="radio"]
- [@name="na&me"]
- [@checked="checked"]
- [@value="foo&bar"]
+ [@name="name"]
+ [not(@checked)]
'
);
}
- public function testUncheckedRadio()
+ public function testRadioWithValue()
{
- $form = $this->factory->createNamed('radio', 'na&me', false, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('radio', 'name', false, array(
+ 'value' => 'foo&bar',
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="radio"]
- [@name="na&me"]
- [not(@checked)]
+ [@name="name"]
+ [@value="foo&bar"]
'
);
}
public function testTextarea()
{
- $form = $this->factory->createNamed('textarea', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('textarea', 'name', 'foo&bar', array(
'pattern' => 'foo',
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/textarea
- [@name="na&me"]
+ [@name="name"]
[not(@pattern)]
[.="foo&bar"]
'
@@ -1505,14 +1415,12 @@ public function testTextarea()
public function testText()
{
- $form = $this->factory->createNamed('text', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('text', 'name', 'foo&bar');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
[not(@maxlength)]
'
@@ -1521,15 +1429,14 @@ public function testText()
public function testTextWithMaxLength()
{
- $form = $this->factory->createNamed('text', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('text', 'name', 'foo&bar', array(
'max_length' => 123,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
[@maxlength="123"]
'
@@ -1538,14 +1445,12 @@ public function testTextWithMaxLength()
public function testSearch()
{
- $form = $this->factory->createNamed('search', 'na&me', 'foo&bar', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('search', 'name', 'foo&bar');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="search"]
- [@name="na&me"]
+ [@name="name"]
[@value="foo&bar"]
[not(@maxlength)]
'
@@ -1554,8 +1459,7 @@ public function testSearch()
public function testTime()
{
- $form = $this->factory->createNamed('time', 'na&me', '04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('time', 'name', '04:05:06', array(
'input' => 'string',
'with_seconds' => false,
));
@@ -1564,11 +1468,11 @@ public function testTime()
'/div
[
./select
- [@id="na&me_hour"]
+ [@id="name_hour"]
[@size="1"]
[./option[@value="4"][@selected="selected"]]
/following-sibling::select
- [@id="na&me_minute"]
+ [@id="name_minute"]
[@size="1"]
[./option[@value="5"][@selected="selected"]]
]
@@ -1579,8 +1483,7 @@ public function testTime()
public function testTimeWithSeconds()
{
- $form = $this->factory->createNamed('time', 'na&me', '04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('time', 'name', '04:05:06', array(
'input' => 'string',
'with_seconds' => true,
));
@@ -1589,17 +1492,17 @@ public function testTimeWithSeconds()
'/div
[
./select
- [@id="na&me_hour"]
+ [@id="name_hour"]
[@size="1"]
[./option[@value="4"][@selected="selected"]]
[count(./option)>23]
/following-sibling::select
- [@id="na&me_minute"]
+ [@id="name_minute"]
[@size="1"]
[./option[@value="5"][@selected="selected"]]
[count(./option)>59]
/following-sibling::select
- [@id="na&me_second"]
+ [@id="name_second"]
[@size="1"]
[./option[@value="6"][@selected="selected"]]
[count(./option)>59]
@@ -1611,8 +1514,7 @@ public function testTimeWithSeconds()
public function testTimeText()
{
- $form = $this->factory->createNamed('time', 'na&me', '04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('time', 'name', '04:05:06', array(
'input' => 'string',
'widget' => 'text',
));
@@ -1622,15 +1524,15 @@ public function testTimeText()
[
./input
[@type="text"]
- [@id="na&me_hour"]
- [@name="na&me[hour]"]
+ [@id="name_hour"]
+ [@name="name[hour]"]
[@value="04"]
[@size="1"]
[@required="required"]
/following-sibling::input
[@type="text"]
- [@id="na&me_minute"]
- [@name="na&me[minute]"]
+ [@id="name_minute"]
+ [@name="name[minute]"]
[@value="05"]
[@size="1"]
[@required="required"]
@@ -1642,8 +1544,7 @@ public function testTimeText()
public function testTimeSingleText()
{
- $form = $this->factory->createNamed('time', 'na&me', '04:05:06', array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('time', 'name', '04:05:06', array(
'input' => 'string',
'widget' => 'single_text',
));
@@ -1651,7 +1552,7 @@ public function testTimeSingleText()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="text"]
- [@name="na&me"]
+ [@name="name"]
[@value="04:05:00"]
'
);
@@ -1659,8 +1560,7 @@ public function testTimeSingleText()
public function testTimeWithEmptyValueGlobal()
{
- $form = $this->factory->createNamed('time', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('time', 'name', null, array(
'input' => 'string',
'empty_value' => 'Change&Me',
'required' => false,
@@ -1670,11 +1570,11 @@ public function testTimeWithEmptyValueGlobal()
'/div
[
./select
- [@id="na&me_hour"]
+ [@id="name_hour"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
[count(./option)>24]
/following-sibling::select
- [@id="na&me_minute"]
+ [@id="name_minute"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
[count(./option)>60]
]
@@ -1685,8 +1585,7 @@ public function testTimeWithEmptyValueGlobal()
public function testTimeWithEmptyValueOnYear()
{
- $form = $this->factory->createNamed('time', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('time', 'name', null, array(
'input' => 'string',
'required' => false,
'empty_value' => array('hour' => 'Change&Me'),
@@ -1696,11 +1595,11 @@ public function testTimeWithEmptyValueOnYear()
'/div
[
./select
- [@id="na&me_hour"]
+ [@id="name_hour"]
[./option[@value=""][.="[trans]Change&Me[/trans]"]]
[count(./option)>24]
/following-sibling::select
- [@id="na&me_minute"]
+ [@id="name_minute"]
[./option[@value="1"]]
[count(./option)>59]
]
@@ -1722,13 +1621,11 @@ public function testTimeErrorBubbling()
public function testTimezone()
{
- $form = $this->factory->createNamed('timezone', 'na&me', 'Europe/Vienna', array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('timezone', 'name', 'Europe/Vienna');
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
- [@name="na&me"]
+ [@name="name"]
[@required="required"]
[./optgroup
[@label="[trans]Europe[/trans]"]
@@ -1742,8 +1639,7 @@ public function testTimezone()
public function testTimezoneWithEmptyValue()
{
- $form = $this->factory->createNamed('timezone', 'na&me', null, array(
- 'property_path' => 'name',
+ $form = $this->factory->createNamed('timezone', 'name', null, array(
'empty_value' => 'Select&Timezone',
'required' => false,
));
@@ -1760,14 +1656,12 @@ public function testTimezoneWithEmptyValue()
public function testUrl()
{
$url = 'http://www.google.com?foo1=bar1&foo2=bar2';
- $form = $this->factory->createNamed('url', 'na&me', $url, array(
- 'property_path' => 'name',
- ));
+ $form = $this->factory->createNamed('url', 'name', $url);
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/input
[@type="url"]
- [@name="na&me"]
+ [@name="name"]
[@value="http://www.google.com?foo1=bar1&foo2=bar2"]
'
);
@@ -1775,7 +1669,7 @@ public function testUrl()
public function testCollectionPrototype()
{
- $form = $this->factory->createNamedBuilder('form', 'na&me', array('items' => array('one', 'two', 'three')))
+ $form = $this->factory->createNamedBuilder('form', 'name', array('items' => array('one', 'two', 'three')))
->add('items', 'collection', array('allow_add' => true))
->getForm()
->createView();
@@ -1783,9 +1677,9 @@ public function testCollectionPrototype()
$html = $this->renderWidget($form);
$this->assertMatchesXpath($html,
- '//div[@id="na&me_items"][@data-prototype]
+ '//div[@id="name_items"][@data-prototype]
|
- //table[@id="na&me_items"][@data-prototype]
+ //table[@id="name_items"][@data-prototype]
'
);
@@ -1793,9 +1687,7 @@ public function testCollectionPrototype()
public function testEmptyRootFormName()
{
- $form = $this->factory->createNamedBuilder('form', '', '', array(
- 'property_path' => 'name',
- ))
+ $form = $this->factory->createNamedBuilder('form', '', '')
->add('child', 'text')
->getForm();
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 ccf4b7a6dddd..000000000000
--- 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/ChoiceListTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ChoiceListTest.php
new file mode 100644
index 000000000000..7774b876c110
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ChoiceListTest.php
@@ -0,0 +1,166 @@
+
+ *
+ * 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\View\ChoiceView;
+
+class ChoiceListTest 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 ChoiceList(
+ array(
+ 'Group 1' => array($this->obj1, $this->obj2),
+ 'Group 2' => array($this->obj3, $this->obj4),
+ ),
+ array(
+ 'Group 1' => array('A', 'B'),
+ 'Group 2' => array('C', 'D'),
+ ),
+ array($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 ChoiceList(
+ 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('0', '1', '2', '3'), $this->list->getValues());
+ $this->assertEquals(array(1 => new ChoiceView('1', 'B')), $this->list->getPreferredViews());
+ $this->assertEquals(array(0 => new ChoiceView('0', 'A'), 2 => new ChoiceView('2', 'C'), 3 => new ChoiceView('3', 'D')), $this->list->getRemainingViews());
+ }
+
+ public function testInitNestedArray()
+ {
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('0', '1', '2', '3'), $this->list->getValues());
+ $this->assertEquals(array(
+ 'Group 1' => array(1 => new ChoiceView('1', 'B')),
+ 'Group 2' => array(2 => new ChoiceView('2', 'C'))
+ ), $this->list->getPreferredViews());
+ $this->assertEquals(array(
+ 'Group 1' => array(0 => new ChoiceView('0', 'A')),
+ 'Group 2' => array(3 => new ChoiceView('3', 'D'))
+ ), $this->list->getRemainingViews());
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\InvalidConfigurationException
+ */
+ public function testInitIndexCopyChoiceWithInvalidIndex()
+ {
+ new ChoiceList(
+ array('a.'),
+ array('A'),
+ array(),
+ ChoiceList::GENERATE,
+ ChoiceList::COPY_CHOICE
+ );
+ }
+
+ /**
+ * @expectedException Symfony\Component\Form\Exception\InvalidConfigurationException
+ */
+ public function testInitValueCopyChoiceWithInvalidValue()
+ {
+ new ChoiceList(
+ array($this->obj1),
+ array('A'),
+ array(),
+ ChoiceList::COPY_CHOICE,
+ ChoiceList::GENERATE
+ );
+ }
+
+ 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 5fd37fdea412..000000000000
--- 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 000000000000..5635cab57c95
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/ObjectChoiceListTest.php
@@ -0,0 +1,231 @@
+
+ *
+ * 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;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+
+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('0', '1', '2', '3'), $this->list->getValues());
+ $this->assertEquals(array(1 => new ChoiceView('1', 'B')), $this->list->getPreferredViews());
+ $this->assertEquals(array(0 => new ChoiceView('0', 'A'), 2 => new ChoiceView('2', 'C'), 3 => new ChoiceView('3', 'D')), $this->list->getRemainingViews());
+ }
+
+ public function testInitNestedArray()
+ {
+ $this->assertSame(array($this->obj1, $this->obj2, $this->obj3, $this->obj4), $this->list->getChoices());
+ $this->assertSame(array('0', '1', '2', '3'), $this->list->getValues());
+ $this->assertEquals(array(
+ 'Group 1' => array(1 => new ChoiceView('1', 'B')),
+ 'Group 2' => array(2 => new ChoiceView('2', 'C'))
+ ), $this->list->getPreferredViews());
+ $this->assertEquals(array(
+ 'Group 1' => array(0 => new ChoiceView('0', 'A')),
+ 'Group 2' => array(3 => new ChoiceView('3', 'D'))
+ ), $this->list->getRemainingViews());
+ }
+
+ 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('0', '1', '2', '3', '4', '5'), $this->list->getValues());
+ $this->assertEquals(array(
+ 'Group 1' => array(1 => new ChoiceView('1', 'B')),
+ 'Group 2' => array(2 => new ChoiceView('2', 'C'))
+ ), $this->list->getPreferredViews());
+ $this->assertEquals(array(
+ 'Group 1' => array(0 => new ChoiceView('0', 'A')),
+ 'Group 2' => array(3 => new ChoiceView('3', 'D')),
+ 4 => new ChoiceView('4', 'E'),
+ 5 => new ChoiceView('5', 'F'),
+ ), $this->list->getRemainingViews());
+ }
+
+ /**
+ * @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('10', '20', '30', '40'), $this->list->getValues());
+ $this->assertEquals(array(1 => new ChoiceView('20', 'B'), 2 => new ChoiceView('30', 'C')), $this->list->getPreferredViews());
+ $this->assertEquals(array(0 => new ChoiceView('10', 'A'), 3 => new ChoiceView('40', 'D')), $this->list->getRemainingViews());
+ }
+
+ 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 => '0', 20 => '1', 30 => '2', 40 => '3'), $this->list->getValues());
+ $this->assertEquals(array(20 => new ChoiceView('1', 'B'), 30 => new ChoiceView('2', 'C')), $this->list->getPreferredViews());
+ $this->assertEquals(array(10 => new ChoiceView('0', 'A'), 40 => new ChoiceView('3', 'D')), $this->list->getRemainingViews());
+ }
+
+ 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('0', '1', '2', '3'), $this->list->getValues());
+ $this->assertEquals(array(0 => new ChoiceView('0', 'A'), 1 => new ChoiceView('1', 'B'), 2 => new ChoiceView('2', 'C'), 3 => new ChoiceView('3', 'D')), $this->list->getRemainingViews());
+ }
+
+ /**
+ * @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');
+
+ new ObjectChoiceList(
+ array($this->obj1, $this->obj2, $this->obj3, $this->obj4)
+ );
+ }
+}
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 6bc115012a4f..000000000000
--- 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 000000000000..cb6afcd8157b
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/ChoiceList/SimpleChoiceListTest.php
@@ -0,0 +1,210 @@
+
+ *
+ * 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;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+
+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 => '0', 1 => '1', 2 => '2'), $this->list->getValues());
+ $this->assertEquals(array(1 => new ChoiceView('1', 'B')), $this->list->getPreferredViews());
+ $this->assertEquals(array(0 => new ChoiceView('0', 'A'), 2 => new ChoiceView('2', 'C')), $this->list->getRemainingViews());
+ }
+
+ 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->getValues());
+ $this->assertEquals(array(1 => new ChoiceView('b', 'B')), $this->list->getPreferredViews());
+ $this->assertEquals(array(0 => new ChoiceView('a', 'A'), 2 => new ChoiceView('c', 'C')), $this->list->getRemainingViews());
+ }
+
+ 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' => '0', 'b' => '1', 'c' => '2'), $this->list->getValues());
+ $this->assertEquals(array('b' => new ChoiceView('1', 'B')), $this->list->getPreferredViews());
+ $this->assertEquals(array('a' => new ChoiceView('0', 'A'), 'c' => new ChoiceView('2', 'C')), $this->list->getRemainingViews());
+ }
+
+ public function testInitNestedArray()
+ {
+ $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $this->list->getChoices());
+ $this->assertSame(array(0 => '0', 1 => '1', 2 => '2', 3 => '3'), $this->list->getValues());
+ $this->assertEquals(array(
+ 'Group 1' => array(1 => new ChoiceView('1', 'B')),
+ 'Group 2' => array(2 => new ChoiceView('2', 'C'))
+ ), $this->list->getPreferredViews());
+ $this->assertEquals(array(
+ 'Group 1' => array(0 => new ChoiceView('0', 'A')),
+ 'Group 2' => array(3 => new ChoiceView('3', 'D'))
+ ), $this->list->getRemainingViews());
+ }
+
+ 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 b72341930353..2885121534dd 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 @@ public function transformProvider()
{
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 @@ public function reverseTransformProvider()
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 @@ public function reverseTransformProvider()
*/
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 992dac577689..0166458043b3 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 @@ protected function tearDown()
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 @@ public function testTransformExpectsArray()
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 9f9fc252466c..41949e9f01a8 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 @@
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 @@ public function testFixZero()
$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 @@ public function testIgnoreEmptyString()
$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 605fd3f3225d..cf26d5ea2979 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,9 @@
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\Extension\Core\View\ChoiceView;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
class ChoiceTypeTest extends TypeTestCase
@@ -31,6 +34,8 @@ class ChoiceTypeTest extends TypeTestCase
4 => 'Roman',
);
+ private $objectChoices;
+
private $stringButNumericChoices = array(
'0' => 'Bernhard',
'1' => 'Fabien',
@@ -51,8 +56,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 +97,15 @@ public function testChoiceListOptionExpectsChoiceListInterface()
));
}
+ /**
+ * @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 +125,7 @@ public function testExpandedChoicesOptionsAreFlattened()
$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 +185,33 @@ public function testBindSingleNonExpanded()
'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 +222,32 @@ public function testBindMultipleNonExpanded()
'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 +258,19 @@ public function testBindSingleExpanded()
'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 +287,57 @@ public function testBindSingleExpandedWithFalseDoesNotHaveExtraFields()
$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 +348,7 @@ public function testBindSingleExpandedNumericChoices()
$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 +371,7 @@ public function testBindSingleExpandedStringsButNumericChoices()
$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 +392,50 @@ public function testBindMultipleExpanded()
'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 +446,7 @@ public function testBindMultipleExpandedNumericChoices()
'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 +599,12 @@ public function testPassChoicesToView()
));
$view = $form->createView();
- $this->assertSame($choices, $view->get('choices'));
+ $this->assertEquals(array(
+ new ChoiceView('0', 'A'),
+ new ChoiceView('1', 'B'),
+ new ChoiceView('2', 'C'),
+ new ChoiceView('3', 'D'),
+ ), $view->get('choices'));
}
public function testPassPreferredChoicesToView()
@@ -449,8 +616,41 @@ public function testPassPreferredChoicesToView()
));
$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->assertEquals(array(
+ 0 => new ChoiceView('0', 'A'),
+ 2 => new ChoiceView('2', 'C'),
+ ), $view->get('choices'));
+ $this->assertEquals(array(
+ 1 => new ChoiceView('1', 'B'),
+ 3 => new ChoiceView('3', 'D'),
+ ), $view->get('preferred_choices'));
+ }
+
+ public function testPassHierarchicalChoicesToView()
+ {
+ $form = $this->factory->create('choice', null, array(
+ 'choices' => $this->groupedChoices,
+ 'preferred_choices' => array('b', 'd'),
+ ));
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ 'Symfony' => array(
+ 0 => new ChoiceView('0', 'Bernhard'),
+ 2 => new ChoiceView('2', 'Kris'),
+ ),
+ 'Doctrine' => array(
+ 4 => new ChoiceView('4', 'Roman'),
+ ),
+ ), $view->get('choices'));
+ $this->assertEquals(array(
+ 'Symfony' => array(
+ 1 => new ChoiceView('1', 'Fabien'),
+ ),
+ 'Doctrine' => array(
+ 3 => new ChoiceView('3', 'Jon'),
+ ),
+ ), $view->get('preferred_choices'));
}
public function testAdjustFullNameForMultipleNonExpanded()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php
index 8253ba04fdd1..615a1d4fdc41 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CollectionTypeTest.php
@@ -125,7 +125,7 @@ public function testAllowAddButNoPrototype()
'prototype' => false,
));
- $this->assertFalse($form->has('$$name$$'));
+ $this->assertFalse($form->has('__name__'));
}
public function testPrototypeMultipartPropagation()
@@ -150,7 +150,7 @@ public function testGetDataDoesNotContainsProtypeNameBeforeDataAreSet()
));
$data = $form->getData();
- $this->assertFalse(isset($data['$$name$$']));
+ $this->assertFalse(isset($data['__name__']));
}
public function testGetDataDoesNotContainsPrototypeNameAfterDataAreSet()
@@ -163,7 +163,7 @@ public function testGetDataDoesNotContainsPrototypeNameAfterDataAreSet()
$form->setData(array('foobar.png'));
$data = $form->getData();
- $this->assertFalse(isset($data['$$name$$']));
+ $this->assertFalse(isset($data['__name__']));
}
public function testPrototypeNameOption()
@@ -174,15 +174,15 @@ public function testPrototypeNameOption()
'allow_add' => true,
));
- $this->assertSame('$$name$$', $form->getAttribute('prototype')->getName(), '$$name$$ is the default');
+ $this->assertSame('__name__', $form->getAttribute('prototype')->getName(), '__name__ is the default');
$form = $this->factory->create('collection', null, array(
'type' => 'field',
'prototype' => true,
'allow_add' => true,
- 'prototype_name' => 'test',
+ 'prototype_name' => '__test__',
));
- $this->assertSame('$$test$$', $form->getAttribute('prototype')->getName());
+ $this->assertSame('__test__', $form->getAttribute('prototype')->getName());
}
}
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 ff79433e0e63..5d819af56676 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CountryTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/CountryTypeTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class CountryTypeTest extends LocalizedTestCase
{
@@ -22,16 +23,11 @@ public function testCountriesAreSelectable()
$view = $form->createView();
$choices = $view->get('choices');
- $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->assertEquals(new ChoiceView('DE', 'Deutschland'), $choices['DE']);
+ $this->assertEquals(new ChoiceView('GB', 'Vereinigtes Königreich'), $choices['GB']);
+ $this->assertEquals(new ChoiceView('US', 'Vereinigte Staaten'), $choices['US']);
+ $this->assertEquals(new ChoiceView('FR', 'Frankreich'), $choices['FR']);
+ $this->assertEquals(new ChoiceView('MY', 'Malaysia'), $choices['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 e6046c987b39..27f3108d9a7f 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 @@ public function testSubmit_invalidDateTime()
{
$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 71e8bc38f132..1be7affc70e6 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/DateTypeTest.php
@@ -11,8 +11,9 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
-require_once __DIR__ . '/LocalizedTestCase.php';
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+require_once __DIR__ . '/LocalizedTestCase.php';
class DateTypeTest extends LocalizedTestCase
{
@@ -325,7 +326,10 @@ public function testYearsOption()
$view = $form->createView();
- $this->assertSame(array(2010 => '2010', 2011 => '2011'), $view->getChild('year')->get('choices'));
+ $this->assertEquals(array(
+ 2010 => new ChoiceView('2010', '2010'),
+ 2011 => new ChoiceView('2011', '2011'),
+ ), $view->getChild('year')->get('choices'));
}
public function testMonthsOption()
@@ -336,7 +340,70 @@ public function testMonthsOption()
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('month')->get('choices'));
+ $this->assertEquals(array(
+ 6 => new ChoiceView('6', '06'),
+ 7 => new ChoiceView('7', '07'),
+ ), $view->getChild('month')->get('choices'));
+ }
+
+ public function testMonthsOptionNumericIfFormatContainsNoMonth()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(6, 7),
+ 'format' => 'yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ 6 => new ChoiceView('6', '06'),
+ 7 => new ChoiceView('7', '07'),
+ ), $view->getChild('month')->get('choices'));
+ }
+
+ public function testMonthsOptionShortFormat()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(1, 4),
+ 'format' => 'dd.MMM.yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ 1 => new ChoiceView('1', 'Jän'),
+ 4 => new ChoiceView('4', 'Apr')
+ ), $view->getChild('month')->get('choices'));
+ }
+
+ public function testMonthsOptionLongFormat()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(1, 4),
+ 'format' => 'dd.MMMM.yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ 1 => new ChoiceView('1', 'Jänner'),
+ 4 => new ChoiceView('4', 'April'),
+ ), $view->getChild('month')->get('choices'));
+ }
+
+ public function testMonthsOptionLongFormatWithDifferentTimezone()
+ {
+ $form = $this->factory->create('date', null, array(
+ 'months' => array(1, 4),
+ 'format' => 'dd.MMMM.yy',
+ ));
+
+ $view = $form->createView();
+
+ $this->assertEquals(array(
+ 1 => new ChoiceView('1', 'Jänner'),
+ 4 => new ChoiceView('4', 'April'),
+ ), $view->getChild('month')->get('choices'));
}
public function testIsDayWithinRangeReturnsTrueIfWithin()
@@ -347,7 +414,10 @@ public function testIsDayWithinRangeReturnsTrueIfWithin()
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('day')->get('choices'));
+ $this->assertEquals(array(
+ 6 => new ChoiceView('6', '06'),
+ 7 => new ChoiceView('7', '07'),
+ ), $view->getChild('day')->get('choices'));
}
public function testIsPartiallyFilledReturnsFalseIfSingleText()
diff --git a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/FieldTypeTest.php b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/FieldTypeTest.php
index a308215e1504..690167679e4b 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/FieldTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/FieldTypeTest.php
@@ -117,6 +117,16 @@ public function testPassIdAndNameToView()
$this->assertEquals('name', $view->get('full_name'));
}
+ public function testStripLeadingUnderscoresAndDigitsFromId()
+ {
+ $form = $this->factory->createNamed('field', '_09name');
+ $view = $form->createView();
+
+ $this->assertEquals('name', $view->get('id'));
+ $this->assertEquals('_09name', $view->get('name'));
+ $this->assertEquals('_09name', $view->get('full_name'));
+ }
+
public function testPassIdAndNameToViewWithParent()
{
$parent = $this->factory->createNamed('field', 'parent');
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 096b568edd05..fc80a0e06c59 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LanguageTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LanguageTypeTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class LanguageTypeTest extends LocalizedTestCase
{
@@ -21,17 +22,13 @@ public function testCountriesAreSelectable()
$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(new ChoiceView('en', 'Englisch'), $choices, '', false, false);
+ $this->assertContains(new ChoiceView('en_GB', 'Britisches Englisch'), $choices, '', false, false);
+ $this->assertContains(new ChoiceView('en_US', 'Amerikanisches Englisch'), $choices, '', false, false);
+ $this->assertContains(new ChoiceView('fr', 'Französisch'), $choices, '', false, false);
+ $this->assertContains(new ChoiceView('my', 'Birmanisch'), $choices, '', false, false);
}
public function testMultipleLanguagesIsNotIncluded()
@@ -40,6 +37,6 @@ public function testMultipleLanguagesIsNotIncluded()
$view = $form->createView();
$choices = $view->get('choices');
- $this->assertArrayNotHasKey('mul', $choices);
+ $this->assertNotContains(new ChoiceView('mul', 'Mehrsprachig'), $choices, '', false, false);
}
}
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 c813c6c3cc1e..8f4bd978692b 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LocaleTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/LocaleTypeTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class LocaleTypeTest extends LocalizedTestCase
{
@@ -22,11 +23,8 @@ public function testLocalesAreSelectable()
$view = $form->createView();
$choices = $view->get('choices');
- $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(new ChoiceView('en', 'Englisch'), $choices, '', false, false);
+ $this->assertContains(new ChoiceView('en_GB', 'Englisch (Vereinigtes Königreich)'), $choices, '', false, false);
+ $this->assertContains(new ChoiceView('zh_Hant_MO', 'Chinesisch (traditionell, Sonderverwaltungszone Macao)'), $choices, '', false, false);
}
}
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 869a33b02e1e..0d3b9d2e694d 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 @@
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 @@ public function testPassParentFullNameToView()
$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 c9ba9a02d006..34676ddf1914 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimeTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimeTypeTest.php
@@ -11,6 +11,8 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
+
require_once __DIR__ . '/LocalizedTestCase.php';
@@ -243,7 +245,10 @@ public function testHoursOption()
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('hour')->get('choices'));
+ $this->assertEquals(array(
+ 6 => new ChoiceView('6', '06'),
+ 7 => new ChoiceView('7', '07'),
+ ), $view->getChild('hour')->get('choices'));
}
public function testIsMinuteWithinRange_returnsTrueIfWithin()
@@ -254,7 +259,10 @@ public function testIsMinuteWithinRange_returnsTrueIfWithin()
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('minute')->get('choices'));
+ $this->assertEquals(array(
+ 6 => new ChoiceView('6', '06'),
+ 7 => new ChoiceView('7', '07'),
+ ), $view->getChild('minute')->get('choices'));
}
public function testIsSecondWithinRange_returnsTrueIfWithin()
@@ -266,7 +274,10 @@ public function testIsSecondWithinRange_returnsTrueIfWithin()
$view = $form->createView();
- $this->assertSame(array(6 => '06', 7 => '07'), $view->getChild('second')->get('choices'));
+ $this->assertEquals(array(
+ 6 => new ChoiceView('6', '06'),
+ 7 => new ChoiceView('7', '07'),
+ ), $view->getChild('second')->get('choices'));
}
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 94e32f9a8792..f8f5126c3213 100644
--- a/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimezoneTypeTest.php
+++ b/tests/Symfony/Tests/Component/Form/Extension/Core/Type/TimezoneTypeTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Tests\Component\Form\Extension\Core\Type;
+use Symfony\Component\Form\Extension\Core\View\ChoiceView;
class TimezoneTypeTest extends TypeTestCase
{
@@ -19,13 +20,12 @@ public function testTimezonesAreSelectable()
$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(new ChoiceView('Africa/Kinshasa', 'Kinshasa'), $choices['Africa'], '', false, false);
$this->assertArrayHasKey('America', $choices);
- $this->assertArrayHasKey('America/New_York', $choices['America']);
- $this->assertEquals('New York', $choices['America']['America/New_York']);
+ $this->assertContains(new ChoiceView('America/New_York', 'New York'), $choices['America'], '', false, false);
}
}
diff --git a/tests/Symfony/Tests/Component/Form/FormBuilderTest.php b/tests/Symfony/Tests/Component/Form/FormBuilderTest.php
index 87a94348cd4f..505022d32a61 100644
--- a/tests/Symfony/Tests/Component/Form/FormBuilderTest.php
+++ b/tests/Symfony/Tests/Component/Form/FormBuilderTest.php
@@ -36,6 +36,37 @@ protected function tearDown()
$this->builder = null;
}
+ public function getHtml4Ids()
+ {
+ // The full list is tested in FormTest, since both Form and FormBuilder
+ // use the same implementation internally
+ return array(
+ array('#', false),
+ array('a ', false),
+ array("a\t", false),
+ array("a\n", false),
+ array('a.', false),
+ );
+ }
+
+ /**
+ * @dataProvider getHtml4Ids
+ */
+ public function testConstructAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted)
+ {
+ try {
+ new FormBuilder($name, $this->factory, $this->dispatcher);
+ if (!$accepted) {
+ $this->fail(sprintf('The value "%s" should not be accepted', $name));
+ }
+ } catch (\InvalidArgumentException $e) {
+ // if the value was not accepted, but should be, rethrow exception
+ if ($accepted) {
+ throw $e;
+ }
+ }
+ }
+
/**
* Changing the name is not allowed, otherwise the name and property path
* are not synchronized anymore
diff --git a/tests/Symfony/Tests/Component/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php
index e662848a4a18..e1eb109e3ad9 100644
--- a/tests/Symfony/Tests/Component/Form/FormTest.php
+++ b/tests/Symfony/Tests/Component/Form/FormTest.php
@@ -60,6 +60,62 @@ public function testConstructExpectsValidValidators()
new Form('name', $this->dispatcher, array(), array(), array(), null, $validators);
}
+ public function getHtml4Ids()
+ {
+ return array(
+ array('a0', true),
+ array('a9', true),
+ array('z0', true),
+ array('A0', true),
+ array('A9', true),
+ array('Z0', true),
+ array('#', false),
+ array('a#', false),
+ array('a$', false),
+ array('a%', false),
+ array('a ', false),
+ array("a\t", false),
+ array("a\n", false),
+ array('a-', true),
+ array('a_', true),
+ array('a:', true),
+ // Periods are allowed by the HTML4 spec, but disallowed by us
+ // because they break the generated property paths
+ array('a.', false),
+ // Contrary to the HTML4 spec, we allow names starting with a
+ // number, otherwise naming fields by collection indices is not
+ // possible.
+ // For root forms, leading digits will be stripped from the
+ // "id" attribute to produce valid HTML4.
+ array('0', true),
+ array('9', true),
+ // Contrary to the HTML4 spec, we allow names starting with an
+ // underscore, since this is already a widely used practice in
+ // Symfony2.
+ // For root forms, leading underscores will be stripped from the
+ // "id" attribute to produce valid HTML4.
+ array('_', true),
+ );
+ }
+
+ /**
+ * @dataProvider getHtml4Ids
+ */
+ public function testConstructAcceptsOnlyNamesValidAsIdsInHtml4($name, $accepted)
+ {
+ try {
+ new Form($name, $this->dispatcher);
+ if (!$accepted) {
+ $this->fail(sprintf('The value "%s" should not be accepted', $name));
+ }
+ } catch (\InvalidArgumentException $e) {
+ // if the value was not accepted, but should be, rethrow exception
+ if ($accepted) {
+ throw $e;
+ }
+ }
+ }
+
public function testDataIsInitializedEmpty()
{
$norm = new FixedDataTransformer(array(
diff --git a/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php b/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php
index 3938e1311772..20e110a7911c 100644
--- a/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php
+++ b/tests/Symfony/Tests/Component/Form/Util/FormUtilTest.php
@@ -15,42 +15,6 @@
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 @@ public function testIsChoiceGroupPart2()
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'),