Skip to content

Commit 04ae391

Browse files
committed
feature #15019 [Form] Deprecated "cascade_validation" (webmozart)
This PR was merged into the 2.8 branch. Discussion ---------- [Form] Deprecated "cascade_validation" | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #11268 (requires explicit work though) | License | MIT | Doc PR | TODO This is #12237 rebased on 2.8. The "cascade_validation" option was designed for a 1% use case and comparatively used way too often when the `Valid` constraint should have been used instead. Also, there seem to be bugs with that option (#5204). The option is now deprecated. When using the 2.5 Validator API, you can set the "constraints" option of the respective child to a `Valid` constraint instead. Alternatively, set the constraint in the model (as most people hopefully do). Commits ------- 6c554c6 [Form] Deprecated "cascade_validation"
2 parents 54bd6f7 + 6c554c6 commit 04ae391

File tree

7 files changed

+180
-31
lines changed

7 files changed

+180
-31
lines changed

UPGRADE-2.8.md

+43
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,51 @@
11
UPGRADE FROM 2.7 to 2.8
22
=======================
33

4+
Form
5+
----
6+
7+
* The "cascade_validation" option was deprecated. Use the "constraints"
8+
option together with the `Valid` constraint instead. Contrary to
9+
"cascade_validation", "constraints" must be set on the respective child forms,
10+
not the parent form.
11+
12+
Before:
13+
14+
```php
15+
$form = $this->createForm('form', $article, array('cascade_validation' => true))
16+
->add('author', new AuthorType())
17+
->getForm();
18+
```
19+
20+
After:
21+
22+
```php
23+
use Symfony\Component\Validator\Constraints\Valid;
24+
25+
$form = $this->createForm('form', $article)
26+
->add('author', new AuthorType(), array(
27+
'constraints' => new Valid(),
28+
))
29+
->getForm();
30+
```
31+
32+
Alternatively, you can set the `Valid` constraint in the model itself:
33+
34+
```php
35+
use Symfony\Component\Validator\Constraints as Assert;
36+
37+
class Article
38+
{
39+
/**
40+
* @Assert\Valid
41+
*/
42+
private $author;
43+
}
44+
```
45+
446
Translator
547
----------
48+
649
* The `getMessages()` method of the `Symfony\Component\Translation\Translator` was deprecated and will be removed in
750
Symfony 3.0. You should use the `getCatalogue()` method of the `Symfony\Component\Translation\TranslatorBagInterface`.
851

src/Symfony/Component/Form/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ CHANGELOG
66

77
* deprecated option "read_only" in favor of "attr['readonly']"
88
* added the html5 "range" FormType
9+
* deprecated the "cascade_validation" option in favor of setting "constraints"
10+
with the Valid constraint
911

1012
2.7.0
1113
-----

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

+15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Form\FormInterface;
1515
use Symfony\Component\Validator\Constraint;
16+
use Symfony\Component\Validator\Constraints\Valid;
1617
use Symfony\Component\Validator\ConstraintValidator;
1718
use Symfony\Component\Validator\Context\ExecutionContextInterface;
1819
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -63,6 +64,20 @@ public function validate($form, Constraint $constraint)
6364
// in the form
6465
$constraints = $config->getOption('constraints');
6566
foreach ($constraints as $constraint) {
67+
// For the "Valid" constraint, validate the data in all groups
68+
if ($constraint instanceof Valid) {
69+
if ($validator) {
70+
$validator->atPath('data')->validate($form->getData(), $constraint, $groups);
71+
} else {
72+
// 2.4 API
73+
$this->context->validateValue($form->getData(), $constraint, 'data', $groups);
74+
}
75+
76+
continue;
77+
}
78+
79+
// Otherwise validate a constraint only once for the first
80+
// matching group
6681
foreach ($groups as $group) {
6782
if (in_array($group, $constraint->groups)) {
6883
if ($validator) {

src/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,26 @@ public function configureOptions(OptionsResolver $resolver)
6767
return is_object($constraints) ? array($constraints) : (array) $constraints;
6868
};
6969

70+
$cascadeValidationNormalizer = function (Options $options, $cascadeValidation) {
71+
if (null !== $cascadeValidation) {
72+
@trigger_error('The "cascade_validation" option is deprecated since version 2.8 and will be removed in 3.0. Use "constraints" with a Valid constraint instead.', E_USER_DEPRECATED);
73+
}
74+
75+
return null === $cascadeValidation ? false : $cascadeValidation;
76+
};
77+
7078
$resolver->setDefaults(array(
7179
'error_mapping' => array(),
7280
'constraints' => array(),
73-
'cascade_validation' => false,
81+
'cascade_validation' => null,
7482
'invalid_message' => 'This value is not valid.',
7583
'invalid_message_parameters' => array(),
7684
'allow_extra_fields' => false,
7785
'extra_fields_message' => 'This form should not contain extra fields.',
7886
));
7987

8088
$resolver->setNormalizer('constraints', $constraintsNormalizer);
89+
$resolver->setNormalizer('cascade_validation', $cascadeValidationNormalizer);
8190
}
8291

8392
/**

src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php

+64-22
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
1919
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
2020
use Symfony\Component\Form\SubmitButtonBuilder;
21-
use Symfony\Component\Validator\Context\ExecutionContextInterface;
2221
use Symfony\Component\Validator\Constraints\NotNull;
2322
use Symfony\Component\Validator\Constraints\NotBlank;
23+
use Symfony\Component\Validator\Constraints\Valid;
2424
use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
2525
use Symfony\Component\Validator\Validation;
2626

@@ -109,6 +109,52 @@ public function testValidateConstraints()
109109
$this->assertNoViolation();
110110
}
111111

112+
public function testValidateIfParentWithCascadeValidation()
113+
{
114+
$object = $this->getMock('\stdClass');
115+
116+
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
117+
->setCompound(true)
118+
->setDataMapper($this->getDataMapper())
119+
->getForm();
120+
$options = array('validation_groups' => array('group1', 'group2'));
121+
$form = $this->getBuilder('name', '\stdClass', $options)->getForm();
122+
$parent->add($form);
123+
124+
$form->setData($object);
125+
126+
$this->expectValidateAt(0, 'data', $object, 'group1');
127+
$this->expectValidateAt(1, 'data', $object, 'group2');
128+
129+
$this->validator->validate($form, new Form());
130+
131+
$this->assertNoViolation();
132+
}
133+
134+
public function testValidateIfChildWithValidConstraint()
135+
{
136+
$object = $this->getMock('\stdClass');
137+
138+
$parent = $this->getBuilder('parent')
139+
->setCompound(true)
140+
->setDataMapper($this->getDataMapper())
141+
->getForm();
142+
$options = array(
143+
'validation_groups' => array('group1', 'group2'),
144+
'constraints' => array(new Valid()),
145+
);
146+
$form = $this->getBuilder('name', '\stdClass', $options)->getForm();
147+
$parent->add($form);
148+
149+
$form->setData($object);
150+
151+
$this->expectValidateAt(0, 'data', $object, array('group1', 'group2'));
152+
153+
$this->validator->validate($form, new Form());
154+
155+
$this->assertNoViolation();
156+
}
157+
112158
public function testDontValidateIfParentWithoutCascadeValidation()
113159
{
114160
$object = $this->getMock('\stdClass');
@@ -387,12 +433,13 @@ public function testUseValidationGroupOfClickedButton()
387433
{
388434
$object = $this->getMock('\stdClass');
389435

390-
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
436+
$parent = $this->getBuilder('parent')
391437
->setCompound(true)
392438
->setDataMapper($this->getDataMapper())
393439
->getForm();
394440
$form = $this->getForm('name', '\stdClass', array(
395441
'validation_groups' => 'form_group',
442+
'constraints' => array(new Valid()),
396443
));
397444

398445
$parent->add($form);
@@ -402,7 +449,7 @@ public function testUseValidationGroupOfClickedButton()
402449

403450
$parent->submit(array('name' => $object, 'submit' => ''));
404451

405-
$this->expectValidateAt(0, 'data', $object, 'button_group');
452+
$this->expectValidateAt(0, 'data', $object, array('button_group'));
406453

407454
$this->validator->validate($form, new Form());
408455

@@ -413,12 +460,13 @@ public function testDontUseValidationGroupOfUnclickedButton()
413460
{
414461
$object = $this->getMock('\stdClass');
415462

416-
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
463+
$parent = $this->getBuilder('parent')
417464
->setCompound(true)
418465
->setDataMapper($this->getDataMapper())
419466
->getForm();
420467
$form = $this->getForm('name', '\stdClass', array(
421468
'validation_groups' => 'form_group',
469+
'constraints' => array(new Valid()),
422470
));
423471

424472
$parent->add($form);
@@ -428,7 +476,7 @@ public function testDontUseValidationGroupOfUnclickedButton()
428476

429477
$form->setData($object);
430478

431-
$this->expectValidateAt(0, 'data', $object, 'form_group');
479+
$this->expectValidateAt(0, 'data', $object, array('form_group'));
432480

433481
$this->validator->validate($form, new Form());
434482

@@ -439,20 +487,18 @@ public function testUseInheritedValidationGroup()
439487
{
440488
$object = $this->getMock('\stdClass');
441489

442-
$parentOptions = array(
443-
'validation_groups' => 'group',
444-
'cascade_validation' => true,
445-
);
490+
$parentOptions = array('validation_groups' => 'group');
446491
$parent = $this->getBuilder('parent', null, $parentOptions)
447492
->setCompound(true)
448493
->setDataMapper($this->getDataMapper())
449494
->getForm();
450-
$form = $this->getBuilder('name', '\stdClass')->getForm();
495+
$formOptions = array('constraints' => array(new Valid()));
496+
$form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm();
451497
$parent->add($form);
452498

453499
$form->setData($object);
454500

455-
$this->expectValidateAt(0, 'data', $object, 'group');
501+
$this->expectValidateAt(0, 'data', $object, array('group'));
456502

457503
$this->validator->validate($form, new Form());
458504

@@ -463,21 +509,18 @@ public function testUseInheritedCallbackValidationGroup()
463509
{
464510
$object = $this->getMock('\stdClass');
465511

466-
$parentOptions = array(
467-
'validation_groups' => array($this, 'getValidationGroups'),
468-
'cascade_validation' => true,
469-
);
512+
$parentOptions = array('validation_groups' => array($this, 'getValidationGroups'));
470513
$parent = $this->getBuilder('parent', null, $parentOptions)
471514
->setCompound(true)
472515
->setDataMapper($this->getDataMapper())
473516
->getForm();
474-
$form = $this->getBuilder('name', '\stdClass')->getForm();
517+
$formOptions = array('constraints' => array(new Valid()));
518+
$form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm();
475519
$parent->add($form);
476520

477521
$form->setData($object);
478522

479-
$this->expectValidateAt(0, 'data', $object, 'group1');
480-
$this->expectValidateAt(1, 'data', $object, 'group2');
523+
$this->expectValidateAt(0, 'data', $object, array('group1', 'group2'));
481524

482525
$this->validator->validate($form, new Form());
483526

@@ -492,19 +535,18 @@ public function testUseInheritedClosureValidationGroup()
492535
'validation_groups' => function (FormInterface $form) {
493536
return array('group1', 'group2');
494537
},
495-
'cascade_validation' => true,
496538
);
497539
$parent = $this->getBuilder('parent', null, $parentOptions)
498540
->setCompound(true)
499541
->setDataMapper($this->getDataMapper())
500542
->getForm();
501-
$form = $this->getBuilder('name', '\stdClass')->getForm();
543+
$formOptions = array('constraints' => array(new Valid()));
544+
$form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm();
502545
$parent->add($form);
503546

504547
$form->setData($object);
505548

506-
$this->expectValidateAt(0, 'data', $object, 'group1');
507-
$this->expectValidateAt(1, 'data', $object, 'group2');
549+
$this->expectValidateAt(0, 'data', $object, array('group1', 'group2'));
508550

509551
$this->validator->validate($form, new Form());
510552

src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php

+28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Form\Tests\Extension\Validator\Type;
1313

1414
use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension;
15+
use Symfony\Component\Validator\Constraints\Valid;
1516
use Symfony\Component\Validator\ConstraintViolationList;
1617

1718
class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
@@ -37,6 +38,33 @@ public function testSubmitValidatesData()
3738
$form->submit(array());
3839
}
3940

41+
public function testValidConstraint()
42+
{
43+
$form = $this->createForm(array('constraints' => $valid = new Valid()));
44+
45+
$this->assertSame(array($valid), $form->getConfig()->getOption('constraints'));
46+
}
47+
48+
/**
49+
* @group legacy
50+
*/
51+
public function testCascadeValidationCanBeSetToTrue()
52+
{
53+
$form = $this->createForm(array('cascade_validation' => true));
54+
55+
$this->assertTrue($form->getConfig()->getOption('cascade_validation'));
56+
}
57+
58+
/**
59+
* @group legacy
60+
*/
61+
public function testCascadeValidationCanBeSetToFalse()
62+
{
63+
$form = $this->createForm(array('cascade_validation' => false));
64+
65+
$this->assertFalse($form->getConfig()->getOption('cascade_validation'));
66+
}
67+
4068
public function testValidatorInterfaceSinceSymfony25()
4169
{
4270
// Mock of ValidatorInterface since apiVersion 2.5

src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php

+18-8
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,24 @@ protected function expectNoValidate()
220220

221221
protected function expectValidateAt($i, $propertyPath, $value, $group)
222222
{
223-
$validator = $this->context->getValidator()->inContext($this->context);
224-
$validator->expects($this->at(2 * $i))
225-
->method('atPath')
226-
->with($propertyPath)
227-
->will($this->returnValue($validator));
228-
$validator->expects($this->at(2 * $i + 1))
229-
->method('validate')
230-
->with($value, $this->logicalOr(null, array()), $group);
223+
switch ($this->getApiVersion()) {
224+
case Validation::API_VERSION_2_4:
225+
$this->context->expects($this->at($i))
226+
->method('validate')
227+
->with($value, $propertyPath, $group);
228+
break;
229+
case Validation::API_VERSION_2_5:
230+
case Validation::API_VERSION_2_5_BC:
231+
$validator = $this->context->getValidator()->inContext($this->context);
232+
$validator->expects($this->at(2 * $i))
233+
->method('atPath')
234+
->with($propertyPath)
235+
->will($this->returnValue($validator));
236+
$validator->expects($this->at(2 * $i + 1))
237+
->method('validate')
238+
->with($value, $this->logicalOr(null, array(), $this->isInstanceOf('\Symfony\Component\Validator\Constraints\Valid')), $group);
239+
break;
240+
}
231241
}
232242

233243
protected function expectValidateValueAt($i, $propertyPath, $value, $constraints, $group = null)

0 commit comments

Comments
 (0)