Skip to content

Commit 34c88b9

Browse files
committed
Merge branch '4.4' into 5.2
* 4.4: Fix PHP 8.1 null values [Console] Fix PHP 8.1 null error for preg_match flag Fix: Article Definition::removeMethodCall should remove all matching calls mark the LazyIterator class as internal fix extracting mixed type-hinted property types keep valid submitted choices when additional choices are submitted
2 parents a1fb3ea + d1618a8 commit 34c88b9

File tree

6 files changed

+80
-30
lines changed

6 files changed

+80
-30
lines changed

Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class DateTimeToHtml5LocalDateTimeTransformer extends BaseDateTimeTransformer
2626
* Transforms a \DateTime into a local date and time string.
2727
*
2828
* According to the HTML standard, the input string of a datetime-local
29-
* input is a RFC3339 date followed by 'T', followed by a RFC3339 time.
29+
* input is an RFC3339 date followed by 'T', followed by an RFC3339 time.
3030
* https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
3131
*
3232
* @param \DateTime|\DateTimeInterface $dateTime A DateTime object

Extension/Core/Type/ChoiceType.php

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,25 @@
3636
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
3737
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
3838
use Symfony\Component\Form\FormBuilderInterface;
39+
use Symfony\Component\Form\FormError;
3940
use Symfony\Component\Form\FormEvent;
4041
use Symfony\Component\Form\FormEvents;
4142
use Symfony\Component\Form\FormInterface;
4243
use Symfony\Component\Form\FormView;
4344
use Symfony\Component\OptionsResolver\Options;
4445
use Symfony\Component\OptionsResolver\OptionsResolver;
4546
use Symfony\Component\PropertyAccess\PropertyPath;
47+
use Symfony\Contracts\Translation\TranslatorInterface;
4648

4749
class ChoiceType extends AbstractType
4850
{
4951
private $choiceListFactory;
52+
private $translator;
5053

51-
public function __construct(ChoiceListFactoryInterface $choiceListFactory = null)
54+
/**
55+
* @param TranslatorInterface $translator
56+
*/
57+
public function __construct(ChoiceListFactoryInterface $choiceListFactory = null, $translator = null)
5258
{
5359
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(
5460
new PropertyAccessDecorator(
@@ -66,13 +72,19 @@ public function __construct(ChoiceListFactoryInterface $choiceListFactory = null
6672
if ($ref->getNumberOfParameters() < 3) {
6773
trigger_deprecation('symfony/form', '5.1', 'Not defining a third parameter "callable|null $filter" in "%s::%s()" is deprecated.', $ref->class, $ref->name);
6874
}
75+
76+
if (null !== $translator && !$translator instanceof TranslatorInterface) {
77+
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be han instance of "%s", "%s" given.', __METHOD__, TranslatorInterface::class, \is_object($translator) ? \get_class($translator) : \gettype($translator)));
78+
}
79+
$this->translator = $translator;
6980
}
7081

7182
/**
7283
* {@inheritdoc}
7384
*/
7485
public function buildForm(FormBuilderInterface $builder, array $options)
7586
{
87+
$unknownValues = [];
7688
$choiceList = $this->createChoiceList($options);
7789
$builder->setAttribute('choice_list', $choiceList);
7890

@@ -100,10 +112,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
100112

101113
$this->addSubForms($builder, $choiceListView->preferredChoices, $options);
102114
$this->addSubForms($builder, $choiceListView->choices, $options);
115+
}
103116

117+
if ($options['expanded'] || $options['multiple']) {
104118
// Make sure that scalar, submitted values are converted to arrays
105119
// which can be submitted to the checkboxes/radio buttons
106-
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
120+
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($choiceList, $options, &$unknownValues) {
107121
$form = $event->getForm();
108122
$data = $event->getData();
109123

@@ -118,6 +132,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
118132
// Convert the submitted data to a string, if scalar, before
119133
// casting it to an array
120134
if (!\is_array($data)) {
135+
if ($options['multiple']) {
136+
throw new TransformationFailedException('Expected an array.');
137+
}
138+
121139
$data = (array) (string) $data;
122140
}
123141

@@ -129,34 +147,61 @@ public function buildForm(FormBuilderInterface $builder, array $options)
129147
$unknownValues = $valueMap;
130148

131149
// Reconstruct the data as mapping from child names to values
132-
$data = [];
133-
134-
/** @var FormInterface $child */
135-
foreach ($form as $child) {
136-
$value = $child->getConfig()->getOption('value');
137-
138-
// Add the value to $data with the child's name as key
139-
if (isset($valueMap[$value])) {
140-
$data[$child->getName()] = $value;
141-
unset($unknownValues[$value]);
142-
continue;
150+
$knownValues = [];
151+
152+
if ($options['expanded']) {
153+
/** @var FormInterface $child */
154+
foreach ($form as $child) {
155+
$value = $child->getConfig()->getOption('value');
156+
157+
// Add the value to $data with the child's name as key
158+
if (isset($valueMap[$value])) {
159+
$knownValues[$child->getName()] = $value;
160+
unset($unknownValues[$value]);
161+
continue;
162+
}
163+
}
164+
} else {
165+
foreach ($data as $value) {
166+
if ($choiceList->getChoicesForValues([$value])) {
167+
$knownValues[] = $value;
168+
unset($unknownValues[$value]);
169+
}
143170
}
144171
}
145172

146173
// The empty value is always known, independent of whether a
147174
// field exists for it or not
148175
unset($unknownValues['']);
149176

150-
// Throw exception if unknown values were submitted
151-
if (\count($unknownValues) > 0) {
177+
// Throw exception if unknown values were submitted (multiple choices will be handled in a different event listener below)
178+
if (\count($unknownValues) > 0 && !$options['multiple']) {
152179
throw new TransformationFailedException(sprintf('The choices "%s" do not exist in the choice list.', implode('", "', array_keys($unknownValues))));
153180
}
154181

155-
$event->setData($data);
182+
$event->setData($knownValues);
156183
});
157184
}
158185

159186
if ($options['multiple']) {
187+
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$unknownValues) {
188+
// Throw exception if unknown values were submitted
189+
if (\count($unknownValues) > 0) {
190+
$form = $event->getForm();
191+
192+
$clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
193+
$messageTemplate = 'The value {{ value }} is not valid.';
194+
195+
if (null !== $this->translator) {
196+
$message = $this->translator->trans($messageTemplate, ['{{ value }}' => $clientDataAsString], 'validators');
197+
} else {
198+
$message = strtr($messageTemplate, ['{{ value }}' => $clientDataAsString]);
199+
}
200+
201+
$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))))));
202+
}
203+
});
204+
160205
// <select> tag with "multiple" option or list of checkbox inputs
161206
$builder->addViewTransformer(new ChoicesToValuesTransformer($choiceList));
162207
} else {

Extension/Core/Type/DateTimeType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public function buildView(FormView $view, FormInterface $form, array $options)
209209
{
210210
$view->vars['widget'] = $options['widget'];
211211

212-
// Change the input to a HTML5 datetime input if
212+
// Change the input to an HTML5 datetime input if
213213
// * the widget is set to "single_text"
214214
// * the format matches the one expected by HTML5
215215
// * the html5 is set to true

Extension/Core/Type/DateType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public function finishView(FormView $view, FormInterface $form, array $options)
189189
{
190190
$view->vars['widget'] = $options['widget'];
191191

192-
// Change the input to a HTML5 date input if
192+
// Change the input to an HTML5 date input if
193193
// * the widget is set to "single_text"
194194
// * the format matches the one expected by HTML5
195195
// * the html5 is set to true

Extension/Core/Type/TimeType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ public function buildView(FormView $view, FormInterface $form, array $options)
221221
'with_seconds' => $options['with_seconds'],
222222
]);
223223

224-
// Change the input to a HTML5 time input if
224+
// Change the input to an HTML5 time input if
225225
// * the widget is set to "single_text"
226226
// * the html5 is set to true
227227
if ($options['html5'] && 'single_text' === $options['widget']) {

Tests/Extension/Core/Type/ChoiceTypeTest.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -842,9 +842,9 @@ public function testSubmitMultipleNonExpandedInvalidArrayChoice()
842842

843843
$form->submit(['a', 'foobar']);
844844

845-
$this->assertNull($form->getData());
846-
$this->assertEquals(['a', 'foobar'], $form->getViewData());
847-
$this->assertFalse($form->isSynchronized());
845+
$this->assertEquals(['a'], $form->getData());
846+
$this->assertEquals(['a'], $form->getViewData());
847+
$this->assertFalse($form->isValid());
848848
}
849849

850850
public function testSubmitMultipleNonExpandedObjectChoices()
@@ -1385,17 +1385,17 @@ public function testSubmitMultipleExpandedInvalidArrayChoice()
13851385

13861386
$form->submit(['a', 'foobar']);
13871387

1388-
$this->assertNull($form->getData());
1389-
$this->assertSame(['a', 'foobar'], $form->getViewData());
1388+
$this->assertSame(['a'], $form->getData());
1389+
$this->assertSame(['a'], $form->getViewData());
13901390
$this->assertEmpty($form->getExtraData());
1391-
$this->assertFalse($form->isSynchronized());
1391+
$this->assertFalse($form->isValid());
13921392

1393-
$this->assertFalse($form[0]->getData());
1393+
$this->assertTrue($form[0]->getData());
13941394
$this->assertFalse($form[1]->getData());
13951395
$this->assertFalse($form[2]->getData());
13961396
$this->assertFalse($form[3]->getData());
13971397
$this->assertFalse($form[4]->getData());
1398-
$this->assertNull($form[0]->getViewData());
1398+
$this->assertSame('a', $form[0]->getViewData());
13991399
$this->assertNull($form[1]->getViewData());
14001400
$this->assertNull($form[2]->getViewData());
14011401
$this->assertNull($form[3]->getViewData());
@@ -2070,8 +2070,13 @@ public function testTrimIsDisabled($multiple, $expanded)
20702070
$form->submit($multiple ? (array) $submittedData : $submittedData);
20712071

20722072
// When the choice does not exist the transformation fails
2073-
$this->assertFalse($form->isSynchronized());
2074-
$this->assertNull($form->getData());
2073+
$this->assertFalse($form->isValid());
2074+
2075+
if ($multiple) {
2076+
$this->assertSame([], $form->getData());
2077+
} else {
2078+
$this->assertNull($form->getData());
2079+
}
20752080
}
20762081

20772082
/**

0 commit comments

Comments
 (0)