@@ -30,14 +30,21 @@ class ArrayChoiceList implements ChoiceListInterface
3030 *
3131 * @var array
3232 */
33- protected $ choices = array () ;
33+ protected $ choices ;
3434
3535 /**
36- * The values of the choices .
36+ * The values indexed by the original keys .
3737 *
38- * @var string[]
38+ * @var array
39+ */
40+ protected $ structuredValues ;
41+
42+ /**
43+ * The original keys of the choices array.
44+ *
45+ * @var int[]|string[]
3946 */
40- protected $ values = array () ;
47+ protected $ originalKeys ;
4148
4249 /**
4350 * The callback for creating the value for a choice.
@@ -51,31 +58,41 @@ class ArrayChoiceList implements ChoiceListInterface
5158 *
5259 * The given choice array must have the same array keys as the value array.
5360 *
54- * @param array $choices The selectable choices
55- * @param callable|null $value The callable for creating the value for a
56- * choice. If `null` is passed, incrementing
57- * integers are used as values
61+ * @param array|\Traversable $choices The selectable choices
62+ * @param callable|null $value The callable for creating the value
63+ * for a choice. If `null` is passed,
64+ * incrementing integers are used as
65+ * values
5866 */
59- public function __construct (array $ choices , $ value = null )
67+ public function __construct ($ choices , $ value = null )
6068 {
6169 if (null !== $ value && !is_callable ($ value )) {
6270 throw new UnexpectedTypeException ($ value , 'null or callable ' );
6371 }
6472
65- $ this -> choices = $ choices ;
66- $ this -> values = array ( );
67- $ this -> valueCallback = $ value ;
73+ if ( $ choices instanceof \Traversable) {
74+ $ choices = iterator_to_array ( $ choices );
75+ }
6876
69- if (null === $ value ) {
70- $ i = 0 ;
71- foreach ($ this ->choices as $ key => $ choice ) {
72- $ this ->values [$ key ] = (string ) $ i ++;
73- }
77+ if (null !== $ value ) {
78+ // If a deterministic value generator was passed, use it later
79+ $ this ->valueCallback = $ value ;
7480 } else {
75- foreach ($ choices as $ key => $ choice ) {
76- $ this ->values [$ key ] = (string ) call_user_func ($ value , $ choice );
77- }
81+ // Otherwise simply generate incrementing integers as values
82+ $ i = 0 ;
83+ $ value = function () use (&$ i ) {
84+ return $ i ++;
85+ };
7886 }
87+
88+ // If the choices are given as recursive array (i.e. with explicit
89+ // choice groups), flatten the array. The grouping information is needed
90+ // in the view only.
91+ $ this ->flatten ($ choices , $ value , $ choicesByValues , $ keysByValues , $ structuredValues );
92+
93+ $ this ->choices = $ choicesByValues ;
94+ $ this ->originalKeys = $ keysByValues ;
95+ $ this ->structuredValues = $ structuredValues ;
7996 }
8097
8198 /**
@@ -91,7 +108,23 @@ public function getChoices()
91108 */
92109 public function getValues ()
93110 {
94- return $ this ->values ;
111+ return array_map ('strval ' , array_keys ($ this ->choices ));
112+ }
113+
114+ /**
115+ * {@inheritdoc}
116+ */
117+ public function getStructuredValues ()
118+ {
119+ return $ this ->structuredValues ;
120+ }
121+
122+ /**
123+ * {@inheritdoc}
124+ */
125+ public function getOriginalKeys ()
126+ {
127+ return $ this ->originalKeys ;
95128 }
96129
97130 /**
@@ -102,17 +135,8 @@ public function getChoicesForValues(array $values)
102135 $ choices = array ();
103136
104137 foreach ($ values as $ i => $ givenValue ) {
105- foreach ($ this ->values as $ j => $ value ) {
106- if ($ value !== (string ) $ givenValue ) {
107- continue ;
108- }
109-
110- $ choices [$ i ] = $ this ->choices [$ j ];
111- unset($ values [$ i ]);
112-
113- if (0 === count ($ values )) {
114- break 2 ;
115- }
138+ if (isset ($ this ->choices [$ givenValue ])) {
139+ $ choices [$ i ] = $ this ->choices [$ givenValue ];
116140 }
117141 }
118142
@@ -131,28 +155,56 @@ public function getValuesForChoices(array $choices)
131155 $ givenValues = array ();
132156
133157 foreach ($ choices as $ i => $ givenChoice ) {
134- $ givenValues [$ i ] = ( string ) call_user_func ($ this ->valueCallback , $ givenChoice );
158+ $ givenValues [$ i ] = call_user_func ($ this ->valueCallback , $ givenChoice );
135159 }
136160
137- return array_intersect ($ givenValues , $ this ->values );
161+ return array_intersect ($ givenValues , array_keys ( $ this ->choices ) );
138162 }
139163
140164 // Otherwise compare choices by identity
141165 foreach ($ choices as $ i => $ givenChoice ) {
142- foreach ($ this ->choices as $ j => $ choice ) {
143- if ($ choice !== $ givenChoice ) {
144- continue ;
145- }
146-
147- $ values [$ i ] = $ this ->values [$ j ];
148- unset($ choices [$ i ]);
149-
150- if (0 === count ($ choices )) {
151- break 2 ;
166+ foreach ($ this ->choices as $ value => $ choice ) {
167+ if ($ choice === $ givenChoice ) {
168+ $ values [$ i ] = (string ) $ value ;
169+ break ;
152170 }
153171 }
154172 }
155173
156174 return $ values ;
157175 }
176+
177+ /**
178+ * Flattens an array into the given output variables.
179+ *
180+ * @param array $choices The array to flatten
181+ * @param callable $value The callable for generating choice values
182+ * @param array $choicesByValues The flattened choices indexed by the
183+ * corresponding values
184+ * @param array $keysByValues The original keys indexed by the
185+ * corresponding values
186+ *
187+ * @internal Must not be used by user-land code
188+ */
189+ protected function flatten (array $ choices , $ value , &$ choicesByValues , &$ keysByValues , &$ structuredValues )
190+ {
191+ if (null === $ choicesByValues ) {
192+ $ choicesByValues = array ();
193+ $ keysByValues = array ();
194+ $ structuredValues = array ();
195+ }
196+
197+ foreach ($ choices as $ key => $ choice ) {
198+ if (is_array ($ choice )) {
199+ $ this ->flatten ($ choice , $ value , $ choicesByValues , $ keysByValues , $ structuredValues [$ key ]);
200+
201+ continue ;
202+ }
203+
204+ $ choiceValue = (string ) call_user_func ($ value , $ choice );
205+ $ choicesByValues [$ choiceValue ] = $ choice ;
206+ $ keysByValues [$ choiceValue ] = $ key ;
207+ $ structuredValues [$ key ] = $ choiceValue ;
208+ }
209+ }
158210}
0 commit comments