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