2828use Symfony \Component \Form \Extension \Core \DataTransformer \ChoiceToValueTransformer ;
2929use Symfony \Component \Form \Extension \Core \EventListener \MergeCollectionListener ;
3030use Symfony \Component \Form \FormBuilderInterface ;
31+ use Symfony \Component \Form \FormError ;
3132use Symfony \Component \Form \FormEvent ;
3233use Symfony \Component \Form \FormEvents ;
3334use Symfony \Component \Form \FormInterface ;
3435use Symfony \Component \Form \FormView ;
3536use Symfony \Component \OptionsResolver \Options ;
3637use Symfony \Component \OptionsResolver \OptionsResolver ;
3738use Symfony \Component \PropertyAccess \PropertyPath ;
39+ use Symfony \Component \Translation \TranslatorInterface as LegacyTranslatorInterface ;
40+ use Symfony \Contracts \Translation \TranslatorInterface ;
3841
3942class ChoiceType extends AbstractType
4043{
4144 private $ choiceListFactory ;
45+ private $ translator ;
4246
43- public function __construct (ChoiceListFactoryInterface $ choiceListFactory = null )
47+ /**
48+ * @param TranslatorInterface $translator
49+ */
50+ public function __construct (ChoiceListFactoryInterface $ choiceListFactory = null , $ translator = null )
4451 {
4552 $ this ->choiceListFactory = $ choiceListFactory ?: new CachingFactoryDecorator (
4653 new PropertyAccessDecorator (
4754 new DefaultChoiceListFactory ()
4855 )
4956 );
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 ;
5062 }
5163
5264 /**
5365 * {@inheritdoc}
5466 */
5567 public function buildForm (FormBuilderInterface $ builder , array $ options )
5668 {
69+ $ unknownValues = [];
5770 $ choiceList = $ this ->createChoiceList ($ options );
5871 $ builder ->setAttribute ('choice_list ' , $ choiceList );
5972
@@ -81,10 +94,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
8194
8295 $ this ->addSubForms ($ builder , $ choiceListView ->preferredChoices , $ options );
8396 $ this ->addSubForms ($ builder , $ choiceListView ->choices , $ options );
97+ }
8498
99+ if ($ options ['expanded ' ] || $ options ['multiple ' ]) {
85100 // Make sure that scalar, submitted values are converted to arrays
86101 // 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 ) {
88103 $ form = $ event ->getForm ();
89104 $ data = $ event ->getData ();
90105
@@ -99,6 +114,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
99114 // Convert the submitted data to a string, if scalar, before
100115 // casting it to an array
101116 if (!\is_array ($ data )) {
117+ if ($ options ['multiple ' ]) {
118+ throw new TransformationFailedException ('Expected an array. ' );
119+ }
120+
102121 $ data = (array ) (string ) $ data ;
103122 }
104123
@@ -110,34 +129,61 @@ public function buildForm(FormBuilderInterface $builder, array $options)
110129 $ unknownValues = $ valueMap ;
111130
112131 // 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+ }
124152 }
125153 }
126154
127155 // The empty value is always known, independent of whether a
128156 // field exists for it or not
129157 unset($ unknownValues ['' ]);
130158
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 ' ] ) {
133161 throw new TransformationFailedException (sprintf ('The choices "%s" do not exist in the choice list. ' , implode ('", " ' , array_keys ($ unknownValues ))));
134162 }
135163
136- $ event ->setData ($ data );
164+ $ event ->setData ($ knownValues );
137165 });
138166 }
139167
140168 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+
141187 // <select> tag with "multiple" option or list of checkbox inputs
142188 $ builder ->addViewTransformer (new ChoicesToValuesTransformer ($ choiceList ));
143189 } else {
0 commit comments