Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a289deb

Browse files
committed
[Form] Fixed new ArrayChoiceList to compare choices by their values, if enabled
1 parent e6739bf commit a289deb

File tree

6 files changed

+156
-143
lines changed

6 files changed

+156
-143
lines changed

src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Component\Form\ChoiceList;
1313

14-
use Symfony\Component\Form\Exception\InvalidArgumentException;
14+
use Symfony\Component\Form\Exception\UnexpectedTypeException;
1515

1616
/**
1717
* A list of choices with arbitrary data types.
@@ -39,33 +39,46 @@ class ArrayChoiceList implements ChoiceListInterface
3939
*/
4040
protected $values = array();
4141

42+
/**
43+
* The callback for creating the value for a choice.
44+
*
45+
* @var callable
46+
*/
47+
protected $valueCallback;
48+
4249
/**
4350
* Creates a list with the given choices and values.
4451
*
4552
* The given choice array must have the same array keys as the value array.
4653
*
47-
* @param array $choices The selectable choices
48-
* @param string[] $values The string values of the choices
49-
*
50-
* @throws InvalidArgumentException If the keys of the choices don't match
51-
* the keys of the values
54+
* @param array $choices The selectable choices
55+
* @param callable $value The callable for creating the value for a
56+
* choice. If `null` is passed, incrementing
57+
* integers are used as values
58+
* @param bool $compareByValue Whether to use the value callback to
59+
* compare choices. If `null`, choices are
60+
* compared by identity
5261
*/
53-
public function __construct(array $choices, array $values)
62+
public function __construct(array $choices, $value = null, $compareByValue = false)
5463
{
55-
$choiceKeys = array_keys($choices);
56-
$valueKeys = array_keys($values);
57-
58-
if ($choiceKeys !== $valueKeys) {
59-
throw new InvalidArgumentException(sprintf(
60-
'The keys of the choices and the values must match. The choice '.
61-
'keys are: "%s". The value keys are: "%s".',
62-
implode('", "', $choiceKeys),
63-
implode('", "', $valueKeys)
64-
));
64+
if (null !== $value && !is_callable($value)) {
65+
throw new UnexpectedTypeException($value, 'null or callable');
6566
}
6667

6768
$this->choices = $choices;
68-
$this->values = array_map('strval', $values);
69+
$this->values = array();
70+
$this->valueCallback = $compareByValue ? $value : null;
71+
72+
if (null === $value) {
73+
$i = 0;
74+
foreach ($this->choices as $key => $choice) {
75+
$this->values[$key] = (string) $i++;
76+
}
77+
} else {
78+
foreach ($choices as $key => $choice) {
79+
$this->values[$key] = (string) call_user_func($value, $choice, $key);
80+
}
81+
}
6982
}
7083

7184
/**
@@ -116,6 +129,17 @@ public function getValuesForChoices(array $choices)
116129
{
117130
$values = array();
118131

132+
// Use the value callback to compare choices by their values, if present
133+
if ($this->valueCallback) {
134+
$givenValues = array();
135+
foreach ($choices as $key => $choice) {
136+
$givenValues[$key] = (string) call_user_func($this->valueCallback, $choice, $key);
137+
}
138+
139+
return array_intersect($givenValues, $this->values);
140+
}
141+
142+
// Otherwise compare choices by identity
119143
foreach ($choices as $i => $givenChoice) {
120144
foreach ($this->choices as $j => $choice) {
121145
if ($choice !== $givenChoice) {

src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php

Lines changed: 31 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,14 @@
4343
* @deprecated Added for backwards compatibility in Symfony 2.7, to be removed
4444
* in Symfony 3.0.
4545
*/
46-
class ArrayKeyChoiceList implements ChoiceListInterface
46+
class ArrayKeyChoiceList extends ArrayChoiceList
4747
{
4848
/**
49-
* The selectable choices.
49+
* Whether the choices are used as values.
5050
*
51-
* @var array
51+
* @var bool
5252
*/
53-
private $choices = array();
54-
55-
/**
56-
* The values of the choices.
57-
*
58-
* @var string[]
59-
*/
60-
private $values = array();
53+
private $useChoicesAsValues = false;
6154

6255
/**
6356
* Casts the given choice to an array key.
@@ -100,63 +93,42 @@ public static function toArrayKey($choice)
10093
* values.
10194
*
10295
* @param array $choices The selectable choices
103-
* @param string[] $values Optional. The string values of the choices
96+
* @param callable $value The callable for creating the value for a
97+
* choice. If `null` is passed, the choices are
98+
* cast to strings and used as values
10499
*
105100
* @throws InvalidArgumentException If the keys of the choices don't match
106101
* the keys of the values or if any of the
107102
* choices is not scalar
108103
*/
109-
public function __construct(array $choices, array $values = array())
104+
public function __construct(array $choices, $value = null)
110105
{
111-
if (empty($values)) {
112-
// The cast to strings happens later
113-
$values = $choices;
114-
} else {
115-
$choiceKeys = array_keys($choices);
116-
$valueKeys = array_keys($values);
117-
118-
if ($choiceKeys !== $valueKeys) {
119-
throw new InvalidArgumentException(
120-
sprintf(
121-
'The keys of the choices and the values must match. The choice '.
122-
'keys are: "%s". The value keys are: "%s".',
123-
implode('", "', $choiceKeys),
124-
implode('", "', $valueKeys)
125-
)
126-
);
127-
}
128-
}
129-
130-
$this->choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
131-
$this->values = array_map('strval', $values);
132-
}
106+
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
133107

134-
/**
135-
* {@inheritdoc}
136-
*/
137-
public function getChoices()
138-
{
139-
return $this->choices;
140-
}
108+
if (null === $value) {
109+
$value = function ($choice) {
110+
return (string) $choice;
111+
};
112+
$this->useChoicesAsValues = true;
113+
}
141114

142-
/**
143-
* {@inheritdoc}
144-
*/
145-
public function getValues()
146-
{
147-
return $this->values;
115+
parent::__construct($choices, $value);
148116
}
149117

150118
/**
151119
* {@inheritdoc}
152120
*/
153121
public function getChoicesForValues(array $values)
154122
{
155-
$values = array_map('strval', $values);
123+
if ($this->useChoicesAsValues) {
124+
$values = array_map('strval', $values);
125+
126+
// If the values are identical to the choices, so we can just return
127+
// them to improve performance a little bit
128+
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values));
129+
}
156130

157-
// The values are identical to the choices, so we can just return them
158-
// to improve performance a little bit
159-
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values));
131+
return parent::getChoicesForValues($values);
160132
}
161133

162134
/**
@@ -166,8 +138,12 @@ public function getValuesForChoices(array $choices)
166138
{
167139
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices);
168140

169-
// The choices are identical to the values, so we can just return them
170-
// to improve performance a little bit
171-
return array_map('strval', array_intersect($choices, $this->choices));
141+
if ($this->useChoicesAsValues) {
142+
// If the choices are identical to the values, we can just return
143+
// them to improve performance a little bit
144+
return array_map('strval', array_intersect($choices, $this->choices));
145+
}
146+
147+
return parent::getValuesForChoices($choices);
172148
}
173149
}

src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,6 @@ public function createListFromChoices($choices, $value = null)
8989
throw new UnexpectedTypeException($choices, 'array or \Traversable');
9090
}
9191

92-
if (null !== $value && !is_callable($value)) {
93-
throw new UnexpectedTypeException($value, 'null or callable');
94-
}
95-
9692
if ($choices instanceof \Traversable) {
9793
$choices = iterator_to_array($choices);
9894
}
@@ -102,29 +98,7 @@ public function createListFromChoices($choices, $value = null)
10298
// in the view only.
10399
self::flatten($choices, $flatChoices);
104100

105-
// If no values are given, use incrementing integers as values
106-
// We can not use the choices themselves, because we don't know whether
107-
// choices can be converted to (duplicate-free) strings
108-
if (null === $value) {
109-
$values = $flatChoices;
110-
$i = 0;
111-
112-
foreach ($values as $key => $value) {
113-
$values[$key] = (string) $i++;
114-
}
115-
116-
return new ArrayChoiceList($flatChoices, $values);
117-
}
118-
119-
// Can't use array_map(), because array_map() doesn't pass the key
120-
// Can't use array_walk(), which ignores the return value of the
121-
// closure
122-
$values = array();
123-
foreach ($flatChoices as $key => $choice) {
124-
$values[$key] = call_user_func($value, $choice, $key);
125-
}
126-
127-
return new ArrayChoiceList($flatChoices, $values);
101+
return new ArrayChoiceList($flatChoices, $value);
128102
}
129103

130104
/**
@@ -139,10 +113,6 @@ public function createListFromFlippedChoices($choices, $value = null)
139113
throw new UnexpectedTypeException($choices, 'array or \Traversable');
140114
}
141115

142-
if (null !== $value && !is_callable($value)) {
143-
throw new UnexpectedTypeException($value, 'null or callable');
144-
}
145-
146116
if ($choices instanceof \Traversable) {
147117
$choices = iterator_to_array($choices);
148118
}
@@ -157,20 +127,12 @@ public function createListFromFlippedChoices($choices, $value = null)
157127
// strings or integers, we are guaranteed to be able to convert them
158128
// to strings
159129
if (null === $value) {
160-
$values = array_map('strval', $flatChoices);
161-
162-
return new ArrayKeyChoiceList($flatChoices, $values);
163-
}
164-
165-
// Can't use array_map(), because array_map() doesn't pass the key
166-
// Can't use array_walk(), which ignores the return value of the
167-
// closure
168-
$values = array();
169-
foreach ($flatChoices as $key => $choice) {
170-
$values[$key] = call_user_func($value, $choice, $key);
130+
$value = function ($choice) {
131+
return (string) $choice;
132+
};
171133
}
172134

173-
return new ArrayKeyChoiceList($flatChoices, $values);
135+
return new ArrayKeyChoiceList($flatChoices, $value);
174136
}
175137

176138
/**

src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ protected function setUp()
2929

3030
protected function createChoiceList()
3131
{
32-
return new ArrayChoiceList($this->getChoices(), $this->getValues());
32+
$i = 0;
33+
34+
return new ArrayChoiceList($this->getChoices());
3335
}
3436

3537
protected function getChoices()
@@ -49,4 +51,45 @@ public function testFailIfKeyMismatch()
4951
{
5052
new ArrayChoiceList(array(0 => 'a', 1 => 'b'), array(1 => 'a', 2 => 'b'));
5153
}
54+
55+
public function testCreateChoiceListWithValueCallback()
56+
{
57+
$callback = function ($choice, $key) {
58+
return $key.':'.$choice;
59+
};
60+
61+
$choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback);
62+
63+
$this->assertSame(array(2 => '2:foo', 7 => '7:bar', 10 => '10:baz'), $choiceList->getValues());
64+
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => '2:foo', 2 => '10:baz')));
65+
$this->assertSame(array(1 => '2:foo', 2 => '10:baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
66+
}
67+
68+
public function testCompareChoicesByIdentityByDefault()
69+
{
70+
$callback = function ($choice) {
71+
return $choice->value;
72+
};
73+
74+
$obj1 = (object) array('value' => 'value1');
75+
$obj2 = (object) array('value' => 'value2');
76+
77+
$choiceList = new ArrayChoiceList(array($obj1, $obj2), $callback);
78+
$this->assertSame(array(2 => 'value2'), $choiceList->getValuesForChoices(array(2 => $obj2)));
79+
$this->assertSame(array(), $choiceList->getValuesForChoices(array(2 => (object) array('value' => 'value2'))));
80+
}
81+
82+
public function testCompareChoicesWithValueCallbackIfCompareByValue()
83+
{
84+
$callback = function ($choice) {
85+
return $choice->value;
86+
};
87+
88+
$obj1 = (object) array('value' => 'value1');
89+
$obj2 = (object) array('value' => 'value2');
90+
91+
$choiceList = new ArrayChoiceList(array($obj1, $obj2), $callback, true);
92+
$this->assertSame(array(2 => 'value2'), $choiceList->getValuesForChoices(array(2 => $obj2)));
93+
$this->assertSame(array(2 => 'value2'), $choiceList->getValuesForChoices(array(2 => (object) array('value' => 'value2'))));
94+
}
5295
}

0 commit comments

Comments
 (0)