[Form] Fixed handling of choices passed in choice groups

This commit is contained in:
Bernhard Schussek 2015-06-22 14:14:00 +02:00
parent cc13cc5584
commit 7623dc87e8
20 changed files with 818 additions and 353 deletions

View File

@ -30,14 +30,21 @@ class ArrayChoiceList implements ChoiceListInterface
*
* @var array
*/
protected $choices = array();
protected $choices;
/**
* The values of the choices.
* The values indexed by the original keys.
*
* @var string[]
* @var array
*/
protected $values = array();
protected $structuredValues;
/**
* The original keys of the choices array.
*
* @var int[]|string[]
*/
protected $originalKeys;
/**
* The callback for creating the value for a choice.
@ -51,31 +58,41 @@ class ArrayChoiceList implements ChoiceListInterface
*
* The given choice array must have the same array keys as the value array.
*
* @param array $choices The selectable choices
* @param callable|null $value The callable for creating the value for a
* choice. If `null` is passed, incrementing
* integers are used as values
* @param array|\Traversable $choices The selectable choices
* @param callable|null $value The callable for creating the value
* for a choice. If `null` is passed,
* incrementing integers are used as
* values
*/
public function __construct(array $choices, $value = null)
public function __construct($choices, $value = null)
{
if (null !== $value && !is_callable($value)) {
throw new UnexpectedTypeException($value, 'null or callable');
}
$this->choices = $choices;
$this->values = array();
$this->valueCallback = $value;
if (null === $value) {
$i = 0;
foreach ($this->choices as $key => $choice) {
$this->values[$key] = (string) $i++;
}
} else {
foreach ($choices as $key => $choice) {
$this->values[$key] = (string) call_user_func($value, $choice);
}
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
if (null !== $value) {
// If a deterministic value generator was passed, use it later
$this->valueCallback = $value;
} else {
// Otherwise simply generate incrementing integers as values
$i = 0;
$value = function () use (&$i) {
return $i++;
};
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
$this->flatten($choices, $value, $choicesByValues, $keysByValues, $structuredValues);
$this->choices = $choicesByValues;
$this->originalKeys = $keysByValues;
$this->structuredValues = $structuredValues;
}
/**
@ -91,7 +108,23 @@ class ArrayChoiceList implements ChoiceListInterface
*/
public function getValues()
{
return $this->values;
return array_map('strval', array_keys($this->choices));
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
return $this->structuredValues;
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
return $this->originalKeys;
}
/**
@ -102,17 +135,8 @@ class ArrayChoiceList implements ChoiceListInterface
$choices = array();
foreach ($values as $i => $givenValue) {
foreach ($this->values as $j => $value) {
if ($value !== (string) $givenValue) {
continue;
}
$choices[$i] = $this->choices[$j];
unset($values[$i]);
if (0 === count($values)) {
break 2;
}
if (isset($this->choices[$givenValue])) {
$choices[$i] = $this->choices[$givenValue];
}
}
@ -131,28 +155,56 @@ class ArrayChoiceList implements ChoiceListInterface
$givenValues = array();
foreach ($choices as $i => $givenChoice) {
$givenValues[$i] = (string) call_user_func($this->valueCallback, $givenChoice);
$givenValues[$i] = call_user_func($this->valueCallback, $givenChoice);
}
return array_intersect($givenValues, $this->values);
return array_intersect($givenValues, array_keys($this->choices));
}
// Otherwise compare choices by identity
foreach ($choices as $i => $givenChoice) {
foreach ($this->choices as $j => $choice) {
if ($choice !== $givenChoice) {
continue;
}
$values[$i] = $this->values[$j];
unset($choices[$i]);
if (0 === count($choices)) {
break 2;
foreach ($this->choices as $value => $choice) {
if ($choice === $givenChoice) {
$values[$i] = (string) $value;
break;
}
}
}
return $values;
}
/**
* Flattens an array into the given output variables.
*
* @param array $choices The array to flatten
* @param callable $value The callable for generating choice values
* @param array $choicesByValues The flattened choices indexed by the
* corresponding values
* @param array $keysByValues The original keys indexed by the
* corresponding values
*
* @internal Must not be used by user-land code
*/
protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues)
{
if (null === $choicesByValues) {
$choicesByValues = array();
$keysByValues = array();
$structuredValues = array();
}
foreach ($choices as $key => $choice) {
if (is_array($choice)) {
$this->flatten($choice, $value, $choicesByValues, $keysByValues, $structuredValues[$key]);
continue;
}
$choiceValue = (string) call_user_func($value, $choice);
$choicesByValues[$choiceValue] = $choice;
$keysByValues[$choiceValue] = $key;
$structuredValues[$key] = $choiceValue;
}
}
}

View File

@ -62,6 +62,8 @@ class ArrayKeyChoiceList extends ArrayChoiceList
* @return int|string The choice as PHP array key
*
* @throws InvalidArgumentException If the choice is not scalar
*
* @internal Must not be used outside this class
*/
public static function toArrayKey($choice)
{
@ -89,23 +91,27 @@ class ArrayKeyChoiceList extends ArrayChoiceList
* If no values are given, the choices are cast to strings and used as
* values.
*
* @param array $choices The selectable choices
* @param callable $value The callable for creating the value for a
* choice. If `null` is passed, the choices are
* cast to strings and used as values
* @param array|\Traversable $choices The selectable choices
* @param callable $value The callable for creating the value
* for a choice. If `null` is passed, the
* choices are cast to strings and used
* as values
*
* @throws InvalidArgumentException If the keys of the choices don't match
* the keys of the values or if any of the
* choices is not scalar
*/
public function __construct(array $choices, $value = null)
public function __construct($choices, $value = null)
{
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
// If no values are given, use the choices as values
// Since the choices are stored in the collection keys, i.e. they are
// strings or integers, we are guaranteed to be able to convert them
// to strings
if (null === $value) {
$value = function ($choice) {
return (string) $choice;
};
$this->useChoicesAsValues = true;
}
@ -122,7 +128,7 @@ class ArrayKeyChoiceList extends ArrayChoiceList
// If the values are identical to the choices, so we can just return
// them to improve performance a little bit
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values));
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, array_keys($this->choices)));
}
return parent::getChoicesForValues($values);
@ -143,4 +149,38 @@ class ArrayKeyChoiceList extends ArrayChoiceList
return parent::getValuesForChoices($choices);
}
/**
* Flattens and flips an array into the given output variable.
*
* @param array $choices The array to flatten
* @param callable $value The callable for generating choice values
* @param array $choicesByValues The flattened choices indexed by the
* corresponding values
* @param array $keysByValues The original keys indexed by the
* corresponding values
*
* @internal Must not be used by user-land code
*/
protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues)
{
if (null === $choicesByValues) {
$choicesByValues = array();
$keysByValues = array();
$structuredValues = array();
}
foreach ($choices as $choice => $key) {
if (is_array($key)) {
$this->flatten($key, $value, $choicesByValues, $keysByValues, $structuredValues[$choice]);
continue;
}
$choiceValue = (string) call_user_func($value, $choice);
$choicesByValues[$choiceValue] = $choice;
$keysByValues[$choiceValue] = $key;
$structuredValues[$key] = $choiceValue;
}
}
}

View File

@ -14,16 +14,13 @@ namespace Symfony\Component\Form\ChoiceList;
/**
* A list of choices that can be selected in a choice field.
*
* A choice list assigns string values to each of a list of choices. These
* string values are displayed in the "value" attributes in HTML and submitted
* back to the server.
* A choice list assigns unique string values to each of a list of choices.
* These string values are displayed in the "value" attributes in HTML and
* submitted back to the server.
*
* The acceptable data types for the choices depend on the implementation.
* Values must always be strings and (within the list) free of duplicates.
*
* The choices returned by {@link getChoices()} and the values returned by
* {@link getValues()} must have the same array indices.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ChoiceListInterface
@ -31,23 +28,66 @@ interface ChoiceListInterface
/**
* Returns all selectable choices.
*
* The keys of the choices correspond to the keys of the values returned by
* {@link getValues()}.
*
* @return array The selectable choices
* @return array The selectable choices indexed by the corresponding values
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* The keys of the values correspond to the keys of the choices returned by
* {@link getChoices()}.
* The values are strings that do not contain duplicates.
*
* @return string[] The choice values
*/
public function getValues();
/**
* Returns the values in the structure originally passed to the list.
*
* Contrary to {@link getValues()}, the result is indexed by the original
* keys of the choices. If the original array contained nested arrays, these
* nested arrays are represented here as well:
*
* $form->add('field', 'choice', array(
* 'choices' => array(
* 'Decided' => array('Yes' => true, 'No' => false),
* 'Undecided' => array('Maybe' => null),
* ),
* ));
*
* In this example, the result of this method is:
*
* array(
* 'Decided' => array('Yes' => '0', 'No' => '1'),
* 'Undecided' => array('Maybe' => '2'),
* )
*
* @return string[] The choice values
*/
public function getStructuredValues();
/**
* Returns the original keys of the choices.
*
* The original keys are the keys of the choice array that was passed in the
* "choice" option of the choice type. Note that this array may contain
* duplicates if the "choice" option contained choice groups:
*
* $form->add('field', 'choice', array(
* 'choices' => array(
* 'Decided' => array(true, false),
* 'Undecided' => array(null),
* ),
* ));
*
* In this example, the original key 0 appears twice, once for `true` and
* once for `null`.
*
* @return int[]|string[] The original choice keys indexed by the
* corresponding choice values
*/
public function getOriginalKeys();
/**
* Returns the choices corresponding to the given values.
*

View File

@ -65,6 +65,30 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
return hash('sha256', $namespace.':'.json_encode($value));
}
/**
* Flattens an array into the given output variable.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
private static function flatten(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flatten($value, $output);
continue;
}
$output[$key] = $value;
}
}
/**
* Decorates the given factory.
*
@ -100,7 +124,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
// We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same
// choice list is returned.
DefaultChoiceListFactory::flatten($choices, $flatChoices);
self::flatten($choices, $flatChoices);
$hash = self::generateHash(array($flatChoices, $value), 'fromChoices');
@ -129,7 +153,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
// We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same
// choice list is returned.
DefaultChoiceListFactory::flattenFlipped($choices, $flatChoices);
self::flatten($choices, $flatChoices);
$hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices');
@ -161,7 +185,6 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
{
// The input is not validated on purpose. This way, the decorated
// factory may decide which input to accept and which not.
$hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr));
if (!isset($this->views[$hash])) {

View File

@ -69,7 +69,7 @@ interface ChoiceListFactoryInterface
* argument.
*
* @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable $value The callable generating the choice
* @param null|callable $value The callable generating the choice
* values
*
* @return ChoiceListInterface The choice list
@ -98,25 +98,20 @@ interface ChoiceListFactoryInterface
* The preferred choices can also be passed as array. Each choice that is
* contained in that array will be marked as preferred.
*
* The groups can be passed as a multi-dimensional array. In that case, a
* group will be created for each array entry containing a nested array.
* For all other entries, the choice for the corresponding key will be
* inserted at that position.
*
* The attributes can be passed as multi-dimensional array. The keys should
* match the keys of the choices. The values should be arrays of HTML
* attributes that should be added to the respective choice.
*
* @param ChoiceListInterface $list The choice list
* @param null|array|callable $preferredChoices The preferred choices
* @param null|callable $label The callable generating
* the choice labels
* @param null|callable $index The callable generating
* the view indices
* @param null|array|\Traversable|callable $groupBy The callable generating
* the group names
* @param null|array|callable $attr The callable generating
* the HTML attributes
* @param ChoiceListInterface $list The choice list
* @param null|array|callable $preferredChoices The preferred choices
* @param null|callable $label The callable generating the
* choice labels
* @param null|callable $index The callable generating the
* view indices
* @param null|callable $groupBy The callable generating the
* group names
* @param null|array|callable $attr The callable generating the
* HTML attributes
*
* @return ChoiceListView The choice list view
*/

View File

@ -15,11 +15,11 @@ use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView;
/**
@ -29,72 +29,12 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView;
*/
class DefaultChoiceListFactory implements ChoiceListFactoryInterface
{
/**
* Flattens an array into the given output variable.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
public static function flatten(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flatten($value, $output);
continue;
}
$output[$key] = $value;
}
}
/**
* Flattens and flips an array into the given output variable.
*
* During the flattening, the keys and values of the input array are
* flipped.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
public static function flattenFlipped(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flattenFlipped($value, $output);
continue;
}
$output[$value] = $key;
}
}
/**
* {@inheritdoc}
*/
public function createListFromChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
self::flatten($choices, $flatChoices);
return new ArrayChoiceList($flatChoices, $value);
return new ArrayChoiceList($choices, $value);
}
/**
@ -105,26 +45,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
*/
public function createListFromFlippedChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
self::flattenFlipped($choices, $flatChoices);
// If no values are given, use the choices as values
// Since the choices are stored in the collection keys, i.e. they are
// strings or integers, we are guaranteed to be able to convert them
// to strings
if (null === $value) {
$value = function ($choice) {
return (string) $choice;
};
}
return new ArrayKeyChoiceList($flatChoices, $value);
return new ArrayKeyChoiceList($choices, $value);
}
/**
@ -141,22 +62,24 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{
// Backwards compatibility
if ($list instanceof LegacyChoiceListInterface && empty($preferredChoices)
if ($list instanceof LegacyChoiceListAdapter && empty($preferredChoices)
&& null === $label && null === $index && null === $groupBy && null === $attr) {
$mapToNonLegacyChoiceView = function (LegacyChoiceView $choiceView) {
return new ChoiceView($choiceView->data, $choiceView->value, $choiceView->label);
};
$adaptedList = $list->getAdaptedList();
return new ChoiceListView(
array_map($mapToNonLegacyChoiceView, $list->getRemainingViews()),
array_map($mapToNonLegacyChoiceView, $list->getPreferredViews())
array_map($mapToNonLegacyChoiceView, $adaptedList->getRemainingViews()),
array_map($mapToNonLegacyChoiceView, $adaptedList->getPreferredViews())
);
}
$preferredViews = array();
$otherViews = array();
$choices = $list->getChoices();
$values = $list->getValues();
$keys = $list->getOriginalKeys();
if (!is_callable($preferredChoices) && !empty($preferredChoices)) {
$preferredChoices = function ($choice) use ($preferredChoices) {
@ -169,36 +92,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
$index = 0;
}
// If $groupBy is not given, no grouping is done
if (empty($groupBy)) {
foreach ($choices as $key => $choice) {
self::addChoiceView(
$choice,
$key,
$label,
$values,
$index,
$attr,
$preferredChoices,
$preferredViews,
$otherViews
);
}
return new ChoiceListView($otherViews, $preferredViews);
}
// If $groupBy is a callable, choices are added to the group with the
// name returned by the callable. If the callable returns null, the
// choice is not added to any group
if (is_callable($groupBy)) {
foreach ($choices as $key => $choice) {
foreach ($choices as $value => $choice) {
self::addChoiceViewGroupedBy(
$groupBy,
$choice,
$key,
(string) $value,
$label,
$values,
$keys,
$index,
$attr,
$preferredChoices,
@ -207,13 +111,12 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
);
}
} else {
// If $groupBy is passed as array, use that array as template for
// constructing the groups
// Otherwise use the original structure of the choices
self::addChoiceViewsGroupedBy(
$groupBy,
$list->getStructuredValues(),
$label,
$choices,
$values,
$keys,
$index,
$attr,
$preferredChoices,
@ -239,15 +142,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
return new ChoiceListView($otherViews, $preferredViews);
}
private static function addChoiceView($choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
private static function addChoiceView($choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{
$value = $values[$key];
// $value may be an integer or a string, since it's stored in the array
// keys. We want to guarantee it's a string though.
$key = $keys[$value];
$nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value);
$view = new ChoiceView(
$choice,
$value,
// If the labels are null, use the choice key by default
// If the labels are null, use the original choice key by default
null === $label ? (string) $key : (string) call_user_func($label, $choice, $key, $value),
// The attributes may be a callable or a mapping from choice indices
// to nested arrays
@ -262,19 +167,19 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
}
}
private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{
foreach ($groupBy as $key => $content) {
foreach ($groupBy as $key => $value) {
// Add the contents of groups to new ChoiceGroupView instances
if (is_array($content)) {
if (is_array($value)) {
$preferredViewsForGroup = array();
$otherViewsForGroup = array();
self::addChoiceViewsGroupedBy(
$content,
$value,
$label,
$choices,
$values,
$keys,
$index,
$attr,
$isPreferred,
@ -295,10 +200,10 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
// Add ungrouped items directly
self::addChoiceView(
$choices[$key],
$key,
$choices[$value],
$value,
$label,
$values,
$keys,
$index,
$attr,
$isPreferred,
@ -308,17 +213,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
}
}
private static function addChoiceViewGroupedBy($groupBy, $choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
private static function addChoiceViewGroupedBy($groupBy, $choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{
$groupLabel = call_user_func($groupBy, $choice, $key, $values[$key]);
$groupLabel = call_user_func($groupBy, $choice, $keys[$value], $value);
if (null === $groupLabel) {
// If the callable returns null, don't group the choice
self::addChoiceView(
$choice,
$key,
$value,
$label,
$values,
$keys,
$index,
$attr,
$isPreferred,
@ -338,9 +243,9 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
self::addChoiceView(
$choice,
$key,
$value,
$label,
$values,
$keys,
$index,
$attr,
$isPreferred,

View File

@ -54,8 +54,8 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface
/**
* Decorates the given factory.
*
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
* @param null|PropertyAccessorInterface $propertyAccessor The used property accessor
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
* @param null|PropertyAccessorInterface $propertyAccessor The used property accessor
*/
public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null)
{
@ -98,8 +98,6 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface
if (is_object($choice) || is_array($choice)) {
return $accessor->getValue($choice, $value);
}
return;
};
}
@ -128,9 +126,9 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface
/**
* {@inheritdoc}
*
* @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable|string|PropertyPath $value The callable or path for
* generating the choice values
* @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable|string|PropertyPath $value The callable or path for
* generating the choice values
*
* @return ChoiceListInterface The choice list
*/

View File

@ -43,13 +43,6 @@ class LazyChoiceList implements ChoiceListInterface
*/
private $value;
/**
* Whether to use the value callback to compare choices.
*
* @var bool
*/
private $compareByValue;
/**
* @var ChoiceListInterface|null
*/
@ -66,11 +59,10 @@ class LazyChoiceList implements ChoiceListInterface
* @param null|callable $value The callable generating the choice
* values
*/
public function __construct(ChoiceLoaderInterface $loader, $value = null, $compareByValue = false)
public function __construct(ChoiceLoaderInterface $loader, $value = null)
{
$this->loader = $loader;
$this->value = $value;
$this->compareByValue = $compareByValue;
}
/**
@ -97,6 +89,30 @@ class LazyChoiceList implements ChoiceListInterface
return $this->loadedList->getValues();
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
if (!$this->loadedList) {
$this->loadedList = $this->loader->loadChoiceList($this->value);
}
return $this->loadedList->getStructuredValues();
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
if (!$this->loadedList) {
$this->loadedList = $this->loader->loadChoiceList($this->value);
}
return $this->loadedList->getOriginalKeys();
}
/**
* {@inheritdoc}
*/

View File

@ -0,0 +1,144 @@
<?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\ChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
/**
* Adapts a legacy choice list implementation to {@link ChoiceListInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
class LegacyChoiceListAdapter implements ChoiceListInterface
{
/**
* @var LegacyChoiceListInterface
*/
private $adaptedList;
/**
* @var array|null
*/
private $choices;
/**
* @var array|null
*/
private $values;
/**
* @var array|null
*/
private $structuredValues;
/**
* Adapts a legacy choice list to {@link ChoiceListInterface}.
*
* @param LegacyChoiceListInterface $adaptedList The adapted list
*/
public function __construct(LegacyChoiceListInterface $adaptedList)
{
$this->adaptedList = $adaptedList;
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
if (!$this->choices) {
$this->initialize();
}
return $this->choices;
}
/**
* {@inheritdoc}
*/
public function getValues()
{
if (!$this->values) {
$this->initialize();
}
return $this->values;
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
if (!$this->structuredValues) {
$this->initialize();
}
return $this->structuredValues;
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
if (!$this->structuredValues) {
$this->initialize();
}
return array_flip($this->structuredValues);
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
return $this->adaptedList->getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
return $this->adaptedList->getValuesForChoices($choices);
}
/**
* Returns the adapted choice list.
*
* @return LegacyChoiceListInterface The adapted list
*/
public function getAdaptedList()
{
return $this->adaptedList;
}
private function initialize()
{
$this->choices = array();
$this->values = array();
$this->structuredValues = $this->adaptedList->getValues();
$innerChoices = $this->adaptedList->getChoices();
foreach ($innerChoices as $index => $choice) {
$value = $this->structuredValues[$index];
$this->values[] = $value;
$this->choices[$value] = $choice;
}
}
}

View File

@ -11,9 +11,6 @@
namespace Symfony\Component\Form\Extension\Core\ChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface as BaseChoiceListInterface;
use Symfony\Component\Form\FormConfigBuilder;
/**
* Contains choices that can be selected in a form field.
*
@ -30,10 +27,24 @@ use Symfony\Component\Form\FormConfigBuilder;
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 2.7, to be removed in 3.0.
* Use {@link BaseChoiceListInterface} instead.
* Use {@link \Symfony\Component\Form\ChoiceList\ChoiceListInterface} instead.
*/
interface ChoiceListInterface extends BaseChoiceListInterface
interface ChoiceListInterface
{
/**
* Returns the list of choices.
*
* @return array The choices with their indices as keys
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* @return array The values with the corresponding choice indices as keys
*/
public function getValues();
/**
* Returns the choice views of the preferred choices as nested array with
* the choice groups as top-level keys.
@ -84,6 +95,37 @@ interface ChoiceListInterface extends BaseChoiceListInterface
*/
public function getRemainingViews();
/**
* Returns the choices corresponding to the given values.
*
* The choices can have any data type.
*
* The choices must be returned with the same keys and in the same order
* as the corresponding values in the given array.
*
* @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
*/
public function getChoicesForValues(array $values);
/**
* Returns the values corresponding to the given choices.
*
* The values must be strings.
*
* The values must be returned with the same keys and in the same order
* as the corresponding choices in the given array.
*
* @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
*/
public function getValuesForChoices(array $choices);
/**
* Returns the indices corresponding to the given choices.
*

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
@ -27,6 +28,7 @@ use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
@ -259,6 +261,10 @@ class ChoiceType extends AbstractType
if ($choiceList) {
@trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED);
if ($choiceList instanceof LegacyChoiceListInterface) {
return new LegacyChoiceListAdapter($choiceList);
}
return $choiceList;
}
@ -338,7 +344,7 @@ class ChoiceType extends AbstractType
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
$resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface'));
$resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'));
$resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable'));
$resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string'));
$resolver->setAllowedTypes('choices_as_values', 'bool');

View File

@ -31,6 +31,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
*/
protected $values;
/**
* @var array
*/
protected $structuredValues;
/**
* @var array
*/
protected $keys;
/**
* @var mixed
*/
@ -71,25 +81,52 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
*/
protected $value4;
/**
* @var string
*/
protected $key1;
/**
* @var string
*/
protected $key2;
/**
* @var string
*/
protected $key3;
/**
* @var string
*/
protected $key4;
protected function setUp()
{
parent::setUp();
$this->list = $this->createChoiceList();
$this->choices = $this->getChoices();
$choices = $this->getChoices();
$this->values = $this->getValues();
$this->structuredValues = array_combine(array_keys($choices), $this->values);
$this->choices = array_combine($this->values, $choices);
$this->keys = array_combine($this->values, array_keys($choices));
// allow access to the individual entries without relying on their indices
reset($this->choices);
reset($this->values);
reset($this->keys);
for ($i = 1; $i <= 4; ++$i) {
$this->{'choice'.$i} = current($this->choices);
$this->{'value'.$i} = current($this->values);
$this->{'key'.$i} = current($this->keys);
next($this->choices);
next($this->values);
next($this->keys);
}
}
@ -103,6 +140,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame($this->values, $this->list->getValues());
}
public function testGetStructuredValues()
{
$this->assertSame($this->values, $this->list->getStructuredValues());
}
public function testGetOriginalKeys()
{
$this->assertSame($this->keys, $this->list->getOriginalKeys());
}
public function testGetChoicesForValues()
{
$values = array($this->value1, $this->value2);

View File

@ -29,8 +29,6 @@ class ArrayChoiceListTest extends AbstractChoiceListTest
protected function createChoiceList()
{
$i = 0;
return new ArrayChoiceList($this->getChoices());
}
@ -60,11 +58,31 @@ class ArrayChoiceListTest extends AbstractChoiceListTest
$choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback);
$this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues());
$this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues());
$this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices());
$this->assertSame(array(':foo' => 2, ':bar' => 7, ':baz' => 10), $choiceList->getOriginalKeys());
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz')));
$this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
}
public function testCreateChoiceListWithGroupedChoices()
{
$choiceList = new ArrayChoiceList(array(
'Group 1' => array('A' => 'a', 'B' => 'b'),
'Group 2' => array('C' => 'c', 'D' => 'd'),
));
$this->assertSame(array('0', '1', '2', '3'), $choiceList->getValues());
$this->assertSame(array(
'Group 1' => array('A' => '0', 'B' => '1'),
'Group 2' => array('C' => '2', 'D' => '3'),
), $choiceList->getStructuredValues());
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $choiceList->getChoices());
$this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $choiceList->getOriginalKeys());
$this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => '0', 2 => '1')));
$this->assertSame(array(1 => '0', 2 => '1'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b')));
}
public function testCompareChoicesByIdentityByDefault()
{
$callback = function ($choice) {

View File

@ -29,7 +29,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
protected function createChoiceList()
{
return new ArrayKeyChoiceList($this->getChoices());
return new ArrayKeyChoiceList(array_flip($this->getChoices()));
}
protected function getChoices()
@ -44,9 +44,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
public function testUseChoicesAsValuesByDefault()
{
$list = new ArrayKeyChoiceList(array(1 => '', 3 => 0, 7 => '1', 10 => 1.23));
$list = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float'));
$this->assertSame(array(1 => '', 3 => '0', 7 => '1', 10 => '1.23'), $list->getValues());
$this->assertSame(array('', '0', '1', '1.23'), $list->getValues());
$this->assertSame(array('' => '', 0 => 0, 1 => 1, '1.23' => '1.23'), $list->getChoices());
$this->assertSame(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float'), $list->getOriginalKeys());
}
public function testNoChoices()
@ -102,33 +104,22 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
public function provideConvertibleChoices()
{
return array(
array(array(0), array(0)),
array(array(1), array(1)),
array(array('0'), array(0)),
array(array('1'), array(1)),
array(array('1.23'), array('1.23')),
array(array('foobar'), array('foobar')),
array(array(0 => 'Label'), array(0 => 0)),
array(array(1 => 'Label'), array(1 => 1)),
array(array('1.23' => 'Label'), array('1.23' => '1.23')),
array(array('foobar' => 'Label'), array('foobar' => 'foobar')),
// The default value of choice fields is NULL. It should be treated
// like the empty value for this choice list type
array(array(null), array('')),
array(array(1.23), array('1.23')),
array(array(null => 'Label'), array('' => '')),
array(array('1.23' => 'Label'), array('1.23' => '1.23')),
// Always cast booleans to 0 and 1, because:
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
array(array(true), array(1)),
array(array(false), array(0)),
array(array(true => 'Label'), array(1 => 1)),
array(array(false => 'Label'), array(0 => 0)),
);
}
/**
* @dataProvider provideInvalidChoices
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
*/
public function testFailIfInvalidChoices(array $choices)
{
new ArrayKeyChoiceList($choices);
}
/**
* @dataProvider provideInvalidChoices
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
@ -155,7 +146,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
return $value;
};
$list = new ArrayKeyChoiceList(array('choice'), $callback);
$list = new ArrayKeyChoiceList(array('choice' => 'Label'), $callback);
$this->assertSame(array($converted), $list->getValues());
}
@ -169,15 +160,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
array('1', '1'),
array('1.23', '1.23'),
array('foobar', 'foobar'),
// The default value of choice fields is NULL. It should be treated
// like the empty value for this choice list type
array(null, ''),
array(1.23, '1.23'),
// Always cast booleans to 0 and 1, because:
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
array(true, '1'),
array(false, ''),
array('', ''),
);
}
@ -187,9 +170,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
return ':'.$choice;
};
$choiceList = new ArrayKeyChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback);
$choiceList = new ArrayKeyChoiceList(array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), $callback);
$this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues());
$this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues());
$this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices());
$this->assertSame(array(':foo' => 'Foo', ':bar' => 'Bar', ':baz' => 'Baz'), $choiceList->getOriginalKeys());
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz')));
$this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
}

View File

@ -204,8 +204,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
*/
public function testCreateFromFlippedChoicesSameChoices($choice1, $choice2)
{
$choices1 = array($choice1);
$choices2 = array($choice2);
$choices1 = array($choice1 => 'A');
$choices2 = array($choice2 => 'A');
$list = new \stdClass();
$this->decoratedFactory->expects($this->once())
@ -222,8 +222,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
*/
public function testCreateFromFlippedChoicesDifferentChoices($choice1, $choice2)
{
$choices1 = array($choice1);
$choices2 = array($choice2);
$choices1 = array($choice1 => 'A');
$choices2 = array($choice2 => 'A');
$list1 = new \stdClass();
$list2 = new \stdClass();

View File

@ -15,6 +15,7 @@ use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
@ -199,7 +200,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D')
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesFlatTraversable()
@ -208,7 +209,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
new \ArrayIterator(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'))
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesFlatValuesAsCallable()
@ -247,7 +248,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
)
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesGroupedTraversable()
@ -259,7 +260,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
))
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesGroupedValuesAsCallable()
@ -523,33 +524,16 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertFlatViewWithCustomIndices($view);
}
public function testCreateViewFlatGroupByAsArray()
public function testCreateViewFlatGroupByOriginalStructure()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
null, // index
array(
'Group 1' => array('A' => true, 'B' => true),
'Group 2' => array('C' => true, 'D' => true),
)
);
$list = new ArrayChoiceList(array(
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
));
$this->assertGroupedView($view);
}
public function testCreateViewFlatGroupByAsTraversable()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
null, // index
new \ArrayIterator(array(
'Group 1' => array('A' => true, 'B' => true),
'Group 2' => array('C' => true, 'D' => true),
))
$list,
array($this->obj2, $this->obj3)
);
$this->assertGroupedView($view);
@ -592,8 +576,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
null, // label
null, // index
function ($object) use ($obj1, $obj2) {
return $obj1 === $object || $obj2 === $object ? 'Group 1'
: 'Group 2';
return $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2';
}
);
@ -749,78 +732,86 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
->method('getRemainingViews')
->will($this->returnValue($other));
$view = $this->factory->createView($list);
$view = $this->factory->createView(new LegacyChoiceListAdapter($list));
$this->assertEquals(array(new ChoiceView('y', 'y', 'Other')), $view->choices);
$this->assertEquals(array(new ChoiceView('x', 'x', 'Preferred')), $view->preferredChoices);
}
private function assertScalarListWithGeneratedValues(ChoiceListInterface $list)
private function assertScalarListWithChoiceValues(ChoiceListInterface $list)
{
$this->assertSame(array('a', 'b', 'c', 'd'), $list->getValues());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'a' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
), $list->getChoices());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
), $list->getValues());
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
), $list->getOriginalKeys());
}
private function assertObjectListWithGeneratedValues(ChoiceListInterface $list)
{
$this->assertSame(array('0', '1', '2', '3'), $list->getValues());
$this->assertSame(array(
'A' => $this->obj1,
'B' => $this->obj2,
'C' => $this->obj3,
'D' => $this->obj4,
0 => $this->obj1,
1 => $this->obj2,
2 => $this->obj3,
3 => $this->obj4,
), $list->getChoices());
$this->assertSame(array(
'A' => '0',
'B' => '1',
'C' => '2',
'D' => '3',
), $list->getValues());
0 => 'A',
1 => 'B',
2 => 'C',
3 => 'D',
), $list->getOriginalKeys());
}
private function assertScalarListWithCustomValues(ChoiceListInterface $list)
{
$this->assertSame(array('a', 'b', '1', '2'), $list->getValues());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'a' => 'a',
'b' => 'b',
1 => 'c',
2 => 'd',
), $list->getChoices());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => '1',
'D' => '2',
), $list->getValues());
'a' => 'A',
'b' => 'B',
1 => 'C',
2 => 'D',
), $list->getOriginalKeys());
}
private function assertObjectListWithCustomValues(ChoiceListInterface $list)
{
$this->assertSame(array('a', 'b', '1', '2'), $list->getValues());
$this->assertSame(array(
'A' => $this->obj1,
'B' => $this->obj2,
'C' => $this->obj3,
'D' => $this->obj4,
'a' => $this->obj1,
'b' => $this->obj2,
1 => $this->obj3,
2 => $this->obj4,
), $list->getChoices());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => '1',
'D' => '2',
), $list->getValues());
'a' => 'A',
'b' => 'B',
1 => 'C',
2 => 'D',
), $list->getOriginalKeys());
}
private function assertFlatView($view)

View File

@ -73,6 +73,36 @@ class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame('RESULT', $this->list->getValues());
}
public function testGetStructuredValuesLoadsInnerListOnFirstCall()
{
$this->loader->expects($this->once())
->method('loadChoiceList')
->with($this->value)
->will($this->returnValue($this->innerList));
$this->innerList->expects($this->exactly(2))
->method('getStructuredValues')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->list->getStructuredValues());
$this->assertSame('RESULT', $this->list->getStructuredValues());
}
public function testGetOriginalKeysLoadsInnerListOnFirstCall()
{
$this->loader->expects($this->once())
->method('loadChoiceList')
->with($this->value)
->will($this->returnValue($this->innerList));
$this->innerList->expects($this->exactly(2))
->method('getOriginalKeys')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->list->getOriginalKeys());
$this->assertSame('RESULT', $this->list->getOriginalKeys());
}
public function testGetChoicesForValuesForwardsCallIfListNotLoaded()
{
$this->loader->expects($this->exactly(2))

View File

@ -0,0 +1,110 @@
<?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\Tests\ChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LegacyChoiceListAdapterTest extends \PHPUnit_Framework_TestCase
{
/**
* @var LegacyChoiceListAdapter
*/
private $list;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|ChoiceListInterface
*/
private $adaptedList;
protected function setUp()
{
$this->adaptedList = $this->getMock('Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface');
$this->list = new LegacyChoiceListAdapter($this->adaptedList);
}
public function testGetChoices()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(':a' => 'a', ':b' => 'b', ':c' => 'c'), $this->list->getChoices());
}
public function testGetValues()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(':a', ':b', ':c'), $this->list->getValues());
}
public function testGetStructuredValues()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(1 => ':a', 4 => ':b', 7 => ':c'), $this->list->getStructuredValues());
}
public function testGetOriginalKeys()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(':a' => 1, ':b' => 4, ':c' => 7), $this->list->getOriginalKeys());
}
public function testGetChoicesForValues()
{
$this->adaptedList->expects($this->once())
->method('getChoicesForValues')
->with(array(1 => ':a', 4 => ':b', 7 => ':c'))
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->assertSame(array(1 => 'a', 4 => 'b', 7 => 'c'), $this->list->getChoicesForValues(array(1 => ':a', 4 => ':b', 7 => ':c')));
}
public function testGetValuesForChoices()
{
$this->adaptedList->expects($this->once())
->method('getValuesForChoices')
->with(array(1 => 'a', 4 => 'b', 7 => 'c'))
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(1 => ':a', 4 => ':b', 7 => ':c'), $this->list->getValuesForChoices(array(1 => 'a', 4 => 'b', 7 => 'c')));
}
public function testGetAdaptedList()
{
$this->assertSame($this->adaptedList, $this->list->getAdaptedList());
}
}

View File

@ -11,9 +11,9 @@
namespace Symfony\Component\Form\Tests\Extension\Core\EventListener;
use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
/**
* @group legacy
@ -26,7 +26,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
{
parent::setUp();
$this->choiceList = new SimpleChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B'));
$this->choiceList = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B'));
}
protected function tearDown()
@ -45,7 +45,6 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = new FixRadioInputListener($this->choiceList, true);
$listener->preSubmit($event);
// Indices in SimpleChoiceList are zero-based generated integers
$this->assertEquals(array(2 => '1'), $event->getData());
}
@ -58,7 +57,6 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = new FixRadioInputListener($this->choiceList, true);
$listener->preSubmit($event);
// Indices in SimpleChoiceList are zero-based generated integers
$this->assertEquals(array(1 => '0'), $event->getData());
}
@ -71,13 +69,12 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = new FixRadioInputListener($this->choiceList, true);
$listener->preSubmit($event);
// Indices in SimpleChoiceList are zero-based generated integers
$this->assertEquals(array(0 => ''), $event->getData());
}
public function testConvertEmptyStringToPlaceholderIfNotFound()
{
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B'));
$list = new ArrayKeyChoiceList(array(0 => 'A', 1 => 'B'));
$data = '';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
@ -91,7 +88,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
public function testDontConvertEmptyStringToPlaceholderIfNoPlaceholderUsed()
{
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B'));
$list = new ArrayKeyChoiceList(array(0 => 'A', 1 => 'B'));
$data = '';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface');

View File

@ -186,6 +186,32 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
}
}
public function testExpandedChoicesOptionsAreFlattenedObjectChoices()
{
$obj1 = (object) array('id' => 1, 'name' => 'Bernhard');
$obj2 = (object) array('id' => 2, 'name' => 'Fabien');
$obj3 = (object) array('id' => 3, 'name' => 'Kris');
$obj4 = (object) array('id' => 4, 'name' => 'Jon');
$obj5 = (object) array('id' => 5, 'name' => 'Roman');
$form = $this->factory->create('choice', null, array(
'expanded' => true,
'choices' => array(
'Symfony' => array($obj1, $obj2, $obj3),
'Doctrine' => array($obj4, $obj5),
),
'choices_as_values' => true,
'choice_name' => 'id',
));
$this->assertSame(5, $form->count(), 'Each nested choice should become a new field, not the groups');
$this->assertTrue($form->has(1));
$this->assertTrue($form->has(2));
$this->assertTrue($form->has(3));
$this->assertTrue($form->has(4));
$this->assertTrue($form->has(5));
}
public function testExpandedCheckboxesAreNeverRequired()
{
$form = $this->factory->create('choice', null, array(