Skip to content

Commit 02df1c9

Browse files
Merge branch '3.4' into 4.4
* 3.4: switch the context when validating nested forms Fix typo [HttpKernel] Fix regression where Store does not return response body correctly rework form validator tests Update AbstractController.php
2 parents fd51aeb + 1f83212 commit 02df1c9

File tree

7 files changed

+427
-322
lines changed

7 files changed

+427
-322
lines changed

src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ protected function file($file, string $fileName = null, string $disposition = Re
156156
*
157157
* @final
158158
*/
159-
protected function addFlash(string $type, string $message)
159+
protected function addFlash(string $type, $message)
160160
{
161161
if (!$this->container->has('session')) {
162162
throw new \LogicException('You can not use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".');

src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public function validate($form, Constraint $formConstraint)
9090
// in different steps without breaking early enough
9191
$this->resolvedGroups[$field] = (array) $group;
9292
$fieldFormConstraint = new Form();
93+
$this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath());
9394
$validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint);
9495
}
9596
}
@@ -135,6 +136,7 @@ public function validate($form, Constraint $formConstraint)
135136
if ($field->isSubmitted()) {
136137
$this->resolvedGroups[$field] = $groups;
137138
$fieldFormConstraint = new Form();
139+
$this->context->setNode($this->context->getValue(), $field, $this->context->getMetadata(), $this->context->getPropertyPath());
138140
$validator->atPath(sprintf('children[%s]', $field->getName()))->validate($field, $fieldFormConstraint);
139141
}
140142
}
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Extension\Validator\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Form\AbstractType;
16+
use Symfony\Component\Form\Extension\Core\Type\FormType;
17+
use Symfony\Component\Form\Extension\Core\Type\TextType;
18+
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
19+
use Symfony\Component\Form\FormBuilderInterface;
20+
use Symfony\Component\Form\FormFactoryBuilder;
21+
use Symfony\Component\Form\Test\ForwardCompatTestTrait;
22+
use Symfony\Component\OptionsResolver\OptionsResolver;
23+
use Symfony\Component\Validator\Constraints\Collection;
24+
use Symfony\Component\Validator\Constraints\Expression;
25+
use Symfony\Component\Validator\Constraints\GroupSequence;
26+
use Symfony\Component\Validator\Constraints\Length;
27+
use Symfony\Component\Validator\Constraints\NotBlank;
28+
use Symfony\Component\Validator\Mapping\ClassMetadata;
29+
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
30+
use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
31+
use Symfony\Component\Validator\Validation;
32+
33+
class FormValidatorFunctionalTest extends TestCase
34+
{
35+
use ForwardCompatTestTrait;
36+
37+
private $validator;
38+
private $formFactory;
39+
40+
private function doSetUp()
41+
{
42+
$this->validator = Validation::createValidatorBuilder()
43+
->setMetadataFactory(new LazyLoadingMetadataFactory(new StaticMethodLoader()))
44+
->getValidator();
45+
$this->formFactory = (new FormFactoryBuilder())
46+
->addExtension(new ValidatorExtension($this->validator))
47+
->getFormFactory();
48+
}
49+
50+
public function testDataConstraintsInvalidateFormEvenIfFieldIsNotSubmitted()
51+
{
52+
$form = $this->formFactory->create(FooType::class);
53+
$form->submit(['baz' => 'foobar'], false);
54+
55+
$this->assertTrue($form->isSubmitted());
56+
$this->assertFalse($form->isValid());
57+
$this->assertFalse($form->get('bar')->isSubmitted());
58+
$this->assertCount(1, $form->get('bar')->getErrors());
59+
}
60+
61+
public function testFieldConstraintsDoNotInvalidateFormIfFieldIsNotSubmitted()
62+
{
63+
$form = $this->formFactory->create(FooType::class);
64+
$form->submit(['bar' => 'foobar'], false);
65+
66+
$this->assertTrue($form->isSubmitted());
67+
$this->assertTrue($form->isValid());
68+
}
69+
70+
public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
71+
{
72+
$form = $this->formFactory->create(FooType::class);
73+
$form->submit(['bar' => 'foobar', 'baz' => ''], false);
74+
75+
$this->assertTrue($form->isSubmitted());
76+
$this->assertFalse($form->isValid());
77+
$this->assertTrue($form->get('bar')->isSubmitted());
78+
$this->assertTrue($form->get('bar')->isValid());
79+
$this->assertTrue($form->get('baz')->isSubmitted());
80+
$this->assertFalse($form->get('baz')->isValid());
81+
}
82+
83+
public function testNonCompositeConstraintValidatedOnce()
84+
{
85+
$form = $this->formFactory->create(TextType::class, null, [
86+
'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
87+
'validation_groups' => ['foo', 'bar'],
88+
]);
89+
$form->submit('');
90+
91+
$violations = $this->validator->validate($form);
92+
93+
$this->assertCount(1, $violations);
94+
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
95+
$this->assertSame('data', $violations[0]->getPropertyPath());
96+
}
97+
98+
public function testCompositeConstraintValidatedInEachGroup()
99+
{
100+
$form = $this->formFactory->create(FormType::class, null, [
101+
'constraints' => [
102+
new Collection([
103+
'field1' => new NotBlank([
104+
'groups' => ['field1'],
105+
]),
106+
'field2' => new NotBlank([
107+
'groups' => ['field2'],
108+
]),
109+
]),
110+
],
111+
'validation_groups' => ['field1', 'field2'],
112+
]);
113+
$form->add('field1');
114+
$form->add('field2');
115+
$form->submit([
116+
'field1' => '',
117+
'field2' => '',
118+
]);
119+
120+
$violations = $this->validator->validate($form);
121+
122+
$this->assertCount(2, $violations);
123+
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
124+
$this->assertSame('data[field1]', $violations[0]->getPropertyPath());
125+
$this->assertSame('This value should not be blank.', $violations[1]->getMessage());
126+
$this->assertSame('data[field2]', $violations[1]->getPropertyPath());
127+
}
128+
129+
public function testCompositeConstraintValidatedInSequence()
130+
{
131+
$form = $this->formFactory->create(FormType::class, null, [
132+
'constraints' => [
133+
new Collection([
134+
'field1' => new NotBlank([
135+
'groups' => ['field1'],
136+
]),
137+
'field2' => new NotBlank([
138+
'groups' => ['field2'],
139+
]),
140+
]),
141+
],
142+
'validation_groups' => new GroupSequence(['field1', 'field2']),
143+
]);
144+
$form->add('field1');
145+
$form->add('field2');
146+
147+
$form->submit([
148+
'field1' => '',
149+
'field2' => '',
150+
]);
151+
152+
$violations = $this->validator->validate($form);
153+
154+
$this->assertCount(1, $violations);
155+
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
156+
$this->assertSame('data[field1]', $violations[0]->getPropertyPath());
157+
}
158+
159+
public function testFieldsValidateInSequence()
160+
{
161+
$allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
162+
163+
$form = $this->formFactory->create(FormType::class, null, [
164+
'validation_groups' => new GroupSequence(['group1', 'group2']),
165+
])
166+
->add('foo', TextType::class, [
167+
'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)],
168+
])
169+
->add('bar', TextType::class, [
170+
'constraints' => [new NotBlank(['groups' => ['group2']])],
171+
])
172+
;
173+
174+
$form->submit(['foo' => 'invalid', 'bar' => null]);
175+
176+
$errors = $form->getErrors(true);
177+
178+
$this->assertCount(1, $errors);
179+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
180+
}
181+
182+
public function testFieldsValidateInSequenceWithNestedGroupsArray()
183+
{
184+
$allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : [];
185+
186+
$form = $this->formFactory->create(FormType::class, null, [
187+
'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']),
188+
])
189+
->add('foo', TextType::class, [
190+
'constraints' => [new Length(['min' => 10, 'groups' => ['group1']] + $allowEmptyString)],
191+
])
192+
->add('bar', TextType::class, [
193+
'constraints' => [new Length(['min' => 10, 'groups' => ['group2']] + $allowEmptyString)],
194+
])
195+
->add('baz', TextType::class, [
196+
'constraints' => [new NotBlank(['groups' => ['group3']])],
197+
])
198+
;
199+
200+
$form->submit(['foo' => 'invalid', 'bar' => 'invalid', 'baz' => null]);
201+
202+
$errors = $form->getErrors(true);
203+
204+
$this->assertCount(2, $errors);
205+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
206+
$this->assertInstanceOf(Length::class, $errors[1]->getCause()->getConstraint());
207+
}
208+
209+
public function testConstraintsInDifferentGroupsOnSingleField()
210+
{
211+
$form = $this->formFactory->create(FormType::class, null, [
212+
'validation_groups' => new GroupSequence(['group1', 'group2']),
213+
])
214+
->add('foo', TextType::class, [
215+
'constraints' => [
216+
new NotBlank([
217+
'groups' => ['group1'],
218+
]),
219+
new Length([
220+
'groups' => ['group2'],
221+
'max' => 3,
222+
]),
223+
],
224+
]);
225+
$form->submit([
226+
'foo' => '[email protected]',
227+
]);
228+
229+
$errors = $form->getErrors(true);
230+
231+
$this->assertFalse($form->isValid());
232+
$this->assertCount(1, $errors);
233+
$this->assertInstanceOf(Length::class, $errors[0]->getCause()->getConstraint());
234+
}
235+
236+
public function testCascadeValidationToChildFormsUsingPropertyPaths()
237+
{
238+
$form = $this->formFactory->create(FormType::class, null, [
239+
'validation_groups' => ['group1', 'group2'],
240+
])
241+
->add('field1', null, [
242+
'constraints' => [new NotBlank(['groups' => 'group1'])],
243+
'property_path' => '[foo]',
244+
])
245+
->add('field2', null, [
246+
'constraints' => [new NotBlank(['groups' => 'group2'])],
247+
'property_path' => '[bar]',
248+
])
249+
;
250+
251+
$form->submit([
252+
'field1' => '',
253+
'field2' => '',
254+
]);
255+
256+
$violations = $this->validator->validate($form);
257+
258+
$this->assertCount(2, $violations);
259+
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
260+
$this->assertSame('children[field1].data', $violations[0]->getPropertyPath());
261+
$this->assertSame('This value should not be blank.', $violations[1]->getMessage());
262+
$this->assertSame('children[field2].data', $violations[1]->getPropertyPath());
263+
}
264+
265+
public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSequence()
266+
{
267+
$form = $this->formFactory->create(FormType::class, null, [
268+
'validation_groups' => new GroupSequence(['group1', 'group2']),
269+
])
270+
->add('field1', null, [
271+
'constraints' => [new NotBlank(['groups' => 'group1'])],
272+
'property_path' => '[foo]',
273+
])
274+
->add('field2', null, [
275+
'constraints' => [new NotBlank(['groups' => 'group2'])],
276+
'property_path' => '[bar]',
277+
])
278+
;
279+
280+
$form->submit([
281+
'field1' => '',
282+
'field2' => '',
283+
]);
284+
285+
$violations = $this->validator->validate($form);
286+
287+
$this->assertCount(1, $violations);
288+
$this->assertSame('This value should not be blank.', $violations[0]->getMessage());
289+
$this->assertSame('children[field1].data', $violations[0]->getPropertyPath());
290+
}
291+
292+
public function testContextIsPopulatedWithFormBeingValidated()
293+
{
294+
$form = $this->formFactory->create(FormType::class)
295+
->add('field1', null, [
296+
'constraints' => [new Expression([
297+
'expression' => '!this.getParent().get("field2").getData()',
298+
])],
299+
])
300+
->add('field2')
301+
;
302+
303+
$form->submit([
304+
'field1' => '',
305+
'field2' => '',
306+
]);
307+
308+
$violations = $this->validator->validate($form);
309+
310+
$this->assertCount(0, $violations);
311+
}
312+
313+
public function testContextIsPopulatedWithFormBeingValidatedUsingGroupSequence()
314+
{
315+
$form = $this->formFactory->create(FormType::class, null, [
316+
'validation_groups' => new GroupSequence(['group1']),
317+
])
318+
->add('field1', null, [
319+
'constraints' => [new Expression([
320+
'expression' => '!this.getParent().get("field2").getData()',
321+
'groups' => ['group1'],
322+
])],
323+
])
324+
->add('field2')
325+
;
326+
327+
$form->submit([
328+
'field1' => '',
329+
'field2' => '',
330+
]);
331+
332+
$violations = $this->validator->validate($form);
333+
334+
$this->assertCount(0, $violations);
335+
}
336+
}
337+
338+
class Foo
339+
{
340+
public $bar;
341+
public $baz;
342+
343+
public static function loadValidatorMetadata(ClassMetadata $metadata)
344+
{
345+
$metadata->addPropertyConstraint('bar', new NotBlank());
346+
}
347+
}
348+
349+
class FooType extends AbstractType
350+
{
351+
public function buildForm(FormBuilderInterface $builder, array $options)
352+
{
353+
$builder
354+
->add('bar')
355+
->add('baz', null, [
356+
'constraints' => [new NotBlank()],
357+
])
358+
;
359+
}
360+
361+
public function configureOptions(OptionsResolver $resolver)
362+
{
363+
$resolver->setDefault('data_class', Foo::class);
364+
}
365+
}

0 commit comments

Comments
 (0)