28
28
use Symfony \Component \Form \Extension \Core \DataTransformer \ChoiceToValueTransformer ;
29
29
use Symfony \Component \Form \Extension \Core \EventListener \MergeCollectionListener ;
30
30
use Symfony \Component \Form \FormBuilderInterface ;
31
+ use Symfony \Component \Form \FormError ;
31
32
use Symfony \Component \Form \FormEvent ;
32
33
use Symfony \Component \Form \FormEvents ;
33
34
use Symfony \Component \Form \FormInterface ;
34
35
use Symfony \Component \Form \FormView ;
35
36
use Symfony \Component \OptionsResolver \Options ;
36
37
use Symfony \Component \OptionsResolver \OptionsResolver ;
37
38
use Symfony \Component \PropertyAccess \PropertyPath ;
39
+ use Symfony \Component \Translation \TranslatorInterface as LegacyTranslatorInterface ;
40
+ use Symfony \Contracts \Translation \TranslatorInterface ;
38
41
39
42
class ChoiceType extends AbstractType
40
43
{
41
44
private $ choiceListFactory ;
45
+ private $ translator ;
42
46
43
- public function __construct (ChoiceListFactoryInterface $ choiceListFactory = null )
47
+ /**
48
+ * @param TranslatorInterface $translator
49
+ */
50
+ public function __construct (ChoiceListFactoryInterface $ choiceListFactory = null , $ translator = null )
44
51
{
45
52
$ this ->choiceListFactory = $ choiceListFactory ?: new CachingFactoryDecorator (
46
53
new PropertyAccessDecorator (
47
54
new DefaultChoiceListFactory ()
48
55
)
49
56
);
57
+
58
+ if (null !== $ translator && !$ translator instanceof LegacyTranslatorInterface && !$ translator instanceof TranslatorInterface) {
59
+ throw new \TypeError (sprintf ('Argument 2 passed to "%s()" must be an instance of "%s", "%s" given. ' , __METHOD__ , TranslatorInterface::class, \is_object ($ translator ) ? \get_class ($ translator ) : \gettype ($ translator )));
60
+ }
61
+ $ this ->translator = $ translator ;
50
62
}
51
63
52
64
/**
53
65
* {@inheritdoc}
54
66
*/
55
67
public function buildForm (FormBuilderInterface $ builder , array $ options )
56
68
{
69
+ $ unknownValues = [];
57
70
$ choiceList = $ this ->createChoiceList ($ options );
58
71
$ builder ->setAttribute ('choice_list ' , $ choiceList );
59
72
@@ -81,10 +94,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
81
94
82
95
$ this ->addSubForms ($ builder , $ choiceListView ->preferredChoices , $ options );
83
96
$ this ->addSubForms ($ builder , $ choiceListView ->choices , $ options );
97
+ }
84
98
99
+ if ($ options ['expanded ' ] || $ options ['multiple ' ]) {
85
100
// Make sure that scalar, submitted values are converted to arrays
86
101
// which can be submitted to the checkboxes/radio buttons
87
- $ builder ->addEventListener (FormEvents::PRE_SUBMIT , function (FormEvent $ event ) {
102
+ $ builder ->addEventListener (FormEvents::PRE_SUBMIT , function (FormEvent $ event ) use ( $ choiceList , $ options , & $ unknownValues ) {
88
103
$ form = $ event ->getForm ();
89
104
$ data = $ event ->getData ();
90
105
@@ -99,6 +114,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
99
114
// Convert the submitted data to a string, if scalar, before
100
115
// casting it to an array
101
116
if (!\is_array ($ data )) {
117
+ if ($ options ['multiple ' ]) {
118
+ throw new TransformationFailedException ('Expected an array. ' );
119
+ }
120
+
102
121
$ data = (array ) (string ) $ data ;
103
122
}
104
123
@@ -110,34 +129,61 @@ public function buildForm(FormBuilderInterface $builder, array $options)
110
129
$ unknownValues = $ valueMap ;
111
130
112
131
// Reconstruct the data as mapping from child names to values
113
- $ data = [];
114
-
115
- /** @var FormInterface $child */
116
- foreach ($ form as $ child ) {
117
- $ value = $ child ->getConfig ()->getOption ('value ' );
118
-
119
- // Add the value to $data with the child's name as key
120
- if (isset ($ valueMap [$ value ])) {
121
- $ data [$ child ->getName ()] = $ value ;
122
- unset($ unknownValues [$ value ]);
123
- continue ;
132
+ $ knownValues = [];
133
+
134
+ if ($ options ['expanded ' ]) {
135
+ /** @var FormInterface $child */
136
+ foreach ($ form as $ child ) {
137
+ $ value = $ child ->getConfig ()->getOption ('value ' );
138
+
139
+ // Add the value to $data with the child's name as key
140
+ if (isset ($ valueMap [$ value ])) {
141
+ $ knownValues [$ child ->getName ()] = $ value ;
142
+ unset($ unknownValues [$ value ]);
143
+ continue ;
144
+ }
145
+ }
146
+ } else {
147
+ foreach ($ data as $ value ) {
148
+ if ($ choiceList ->getChoicesForValues ([$ value ])) {
149
+ $ knownValues [] = $ value ;
150
+ unset($ unknownValues [$ value ]);
151
+ }
124
152
}
125
153
}
126
154
127
155
// The empty value is always known, independent of whether a
128
156
// field exists for it or not
129
157
unset($ unknownValues ['' ]);
130
158
131
- // Throw exception if unknown values were submitted
132
- if (\count ($ unknownValues ) > 0 ) {
159
+ // Throw exception if unknown values were submitted (multiple choices will be handled in a different event listener below)
160
+ if (\count ($ unknownValues ) > 0 && ! $ options [ ' multiple ' ] ) {
133
161
throw new TransformationFailedException (sprintf ('The choices "%s" do not exist in the choice list. ' , implode ('", " ' , array_keys ($ unknownValues ))));
134
162
}
135
163
136
- $ event ->setData ($ data );
164
+ $ event ->setData ($ knownValues );
137
165
});
138
166
}
139
167
140
168
if ($ options ['multiple ' ]) {
169
+ $ builder ->addEventListener (FormEvents::POST_SUBMIT , function (FormEvent $ event ) use (&$ unknownValues ) {
170
+ // Throw exception if unknown values were submitted
171
+ if (\count ($ unknownValues ) > 0 ) {
172
+ $ form = $ event ->getForm ();
173
+
174
+ $ clientDataAsString = is_scalar ($ form ->getViewData ()) ? (string ) $ form ->getViewData () : \gettype ($ form ->getViewData ());
175
+ $ messageTemplate = 'The value {{ value }} is not valid. ' ;
176
+
177
+ if (null !== $ this ->translator ) {
178
+ $ message = $ this ->translator ->trans ($ messageTemplate , ['{{ value }} ' => $ clientDataAsString ], 'validators ' );
179
+ } else {
180
+ $ message = strtr ($ messageTemplate , ['{{ value }} ' => $ clientDataAsString ]);
181
+ }
182
+
183
+ $ form ->addError (new FormError ($ message , $ messageTemplate , ['{{ value }} ' => $ clientDataAsString ], null , new TransformationFailedException (sprintf ('The choices "%s" do not exist in the choice list. ' , implode ('", " ' , array_keys ($ unknownValues ))))));
184
+ }
185
+ });
186
+
141
187
// <select> tag with "multiple" option or list of checkbox inputs
142
188
$ builder ->addViewTransformer (new ChoicesToValuesTransformer ($ choiceList ));
143
189
} else {
0 commit comments