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"
This commit is contained in:
Fabien Potencier 2015-06-17 23:27:07 +02:00
commit 04ae391318
7 changed files with 180 additions and 31 deletions

View File

@ -1,8 +1,51 @@
UPGRADE FROM 2.7 to 2.8
=======================
Form
----
* The "cascade_validation" option was deprecated. Use the "constraints"
option together with the `Valid` constraint instead. Contrary to
"cascade_validation", "constraints" must be set on the respective child forms,
not the parent form.
Before:
```php
$form = $this->createForm('form', $article, array('cascade_validation' => true))
->add('author', new AuthorType())
->getForm();
```
After:
```php
use Symfony\Component\Validator\Constraints\Valid;
$form = $this->createForm('form', $article)
->add('author', new AuthorType(), array(
'constraints' => new Valid(),
))
->getForm();
```
Alternatively, you can set the `Valid` constraint in the model itself:
```php
use Symfony\Component\Validator\Constraints as Assert;
class Article
{
/**
* @Assert\Valid
*/
private $author;
}
```
Translator
----------
* The `getMessages()` method of the `Symfony\Component\Translation\Translator` was deprecated and will be removed in
Symfony 3.0. You should use the `getCatalogue()` method of the `Symfony\Component\Translation\TranslatorBagInterface`.

View File

@ -6,6 +6,8 @@ CHANGELOG
* deprecated option "read_only" in favor of "attr['readonly']"
* added the html5 "range" FormType
* deprecated the "cascade_validation" option in favor of setting "constraints"
with the Valid constraint
2.7.0
-----

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Validator\Constraints;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@ -63,6 +64,20 @@ class FormValidator extends ConstraintValidator
// in the form
$constraints = $config->getOption('constraints');
foreach ($constraints as $constraint) {
// For the "Valid" constraint, validate the data in all groups
if ($constraint instanceof Valid) {
if ($validator) {
$validator->atPath('data')->validate($form->getData(), $constraint, $groups);
} else {
// 2.4 API
$this->context->validateValue($form->getData(), $constraint, 'data', $groups);
}
continue;
}
// Otherwise validate a constraint only once for the first
// matching group
foreach ($groups as $group) {
if (in_array($group, $constraint->groups)) {
if ($validator) {

View File

@ -67,10 +67,18 @@ class FormTypeValidatorExtension extends BaseValidatorExtension
return is_object($constraints) ? array($constraints) : (array) $constraints;
};
$cascadeValidationNormalizer = function (Options $options, $cascadeValidation) {
if (null !== $cascadeValidation) {
@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);
}
return null === $cascadeValidation ? false : $cascadeValidation;
};
$resolver->setDefaults(array(
'error_mapping' => array(),
'constraints' => array(),
'cascade_validation' => false,
'cascade_validation' => null,
'invalid_message' => 'This value is not valid.',
'invalid_message_parameters' => array(),
'allow_extra_fields' => false,
@ -78,6 +86,7 @@ class FormTypeValidatorExtension extends BaseValidatorExtension
));
$resolver->setNormalizer('constraints', $constraintsNormalizer);
$resolver->setNormalizer('cascade_validation', $cascadeValidationNormalizer);
}
/**

View File

@ -18,9 +18,9 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
use Symfony\Component\Form\Extension\Validator\Constraints\FormValidator;
use Symfony\Component\Form\SubmitButtonBuilder;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest;
use Symfony\Component\Validator\Validation;
@ -109,6 +109,52 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
$this->assertNoViolation();
}
public function testValidateIfParentWithCascadeValidation()
{
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$options = array('validation_groups' => array('group1', 'group2'));
$form = $this->getBuilder('name', '\stdClass', $options)->getForm();
$parent->add($form);
$form->setData($object);
$this->expectValidateAt(0, 'data', $object, 'group1');
$this->expectValidateAt(1, 'data', $object, 'group2');
$this->validator->validate($form, new Form());
$this->assertNoViolation();
}
public function testValidateIfChildWithValidConstraint()
{
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder('parent')
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$options = array(
'validation_groups' => array('group1', 'group2'),
'constraints' => array(new Valid()),
);
$form = $this->getBuilder('name', '\stdClass', $options)->getForm();
$parent->add($form);
$form->setData($object);
$this->expectValidateAt(0, 'data', $object, array('group1', 'group2'));
$this->validator->validate($form, new Form());
$this->assertNoViolation();
}
public function testDontValidateIfParentWithoutCascadeValidation()
{
$object = $this->getMock('\stdClass');
@ -387,12 +433,13 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
{
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
$parent = $this->getBuilder('parent')
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getForm('name', '\stdClass', array(
'validation_groups' => 'form_group',
'constraints' => array(new Valid()),
));
$parent->add($form);
@ -402,7 +449,7 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
$parent->submit(array('name' => $object, 'submit' => ''));
$this->expectValidateAt(0, 'data', $object, 'button_group');
$this->expectValidateAt(0, 'data', $object, array('button_group'));
$this->validator->validate($form, new Form());
@ -413,12 +460,13 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
{
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder('parent', null, array('cascade_validation' => true))
$parent = $this->getBuilder('parent')
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getForm('name', '\stdClass', array(
'validation_groups' => 'form_group',
'constraints' => array(new Valid()),
));
$parent->add($form);
@ -428,7 +476,7 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
$form->setData($object);
$this->expectValidateAt(0, 'data', $object, 'form_group');
$this->expectValidateAt(0, 'data', $object, array('form_group'));
$this->validator->validate($form, new Form());
@ -439,20 +487,18 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
{
$object = $this->getMock('\stdClass');
$parentOptions = array(
'validation_groups' => 'group',
'cascade_validation' => true,
);
$parentOptions = array('validation_groups' => 'group');
$parent = $this->getBuilder('parent', null, $parentOptions)
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getBuilder('name', '\stdClass')->getForm();
$formOptions = array('constraints' => array(new Valid()));
$form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm();
$parent->add($form);
$form->setData($object);
$this->expectValidateAt(0, 'data', $object, 'group');
$this->expectValidateAt(0, 'data', $object, array('group'));
$this->validator->validate($form, new Form());
@ -463,21 +509,18 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
{
$object = $this->getMock('\stdClass');
$parentOptions = array(
'validation_groups' => array($this, 'getValidationGroups'),
'cascade_validation' => true,
);
$parentOptions = array('validation_groups' => array($this, 'getValidationGroups'));
$parent = $this->getBuilder('parent', null, $parentOptions)
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getBuilder('name', '\stdClass')->getForm();
$formOptions = array('constraints' => array(new Valid()));
$form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm();
$parent->add($form);
$form->setData($object);
$this->expectValidateAt(0, 'data', $object, 'group1');
$this->expectValidateAt(1, 'data', $object, 'group2');
$this->expectValidateAt(0, 'data', $object, array('group1', 'group2'));
$this->validator->validate($form, new Form());
@ -492,19 +535,18 @@ class FormValidatorTest extends AbstractConstraintValidatorTest
'validation_groups' => function (FormInterface $form) {
return array('group1', 'group2');
},
'cascade_validation' => true,
);
$parent = $this->getBuilder('parent', null, $parentOptions)
->setCompound(true)
->setDataMapper($this->getDataMapper())
->getForm();
$form = $this->getBuilder('name', '\stdClass')->getForm();
$formOptions = array('constraints' => array(new Valid()));
$form = $this->getBuilder('name', '\stdClass', $formOptions)->getForm();
$parent->add($form);
$form->setData($object);
$this->expectValidateAt(0, 'data', $object, 'group1');
$this->expectValidateAt(1, 'data', $object, 'group2');
$this->expectValidateAt(0, 'data', $object, array('group1', 'group2'));
$this->validator->validate($form, new Form());

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests\Extension\Validator\Type;
use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\ConstraintViolationList;
class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
@ -37,6 +38,33 @@ class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest
$form->submit(array());
}
public function testValidConstraint()
{
$form = $this->createForm(array('constraints' => $valid = new Valid()));
$this->assertSame(array($valid), $form->getConfig()->getOption('constraints'));
}
/**
* @group legacy
*/
public function testCascadeValidationCanBeSetToTrue()
{
$form = $this->createForm(array('cascade_validation' => true));
$this->assertTrue($form->getConfig()->getOption('cascade_validation'));
}
/**
* @group legacy
*/
public function testCascadeValidationCanBeSetToFalse()
{
$form = $this->createForm(array('cascade_validation' => false));
$this->assertFalse($form->getConfig()->getOption('cascade_validation'));
}
public function testValidatorInterfaceSinceSymfony25()
{
// Mock of ValidatorInterface since apiVersion 2.5

View File

@ -220,14 +220,24 @@ abstract class AbstractConstraintValidatorTest extends \PHPUnit_Framework_TestCa
protected function expectValidateAt($i, $propertyPath, $value, $group)
{
$validator = $this->context->getValidator()->inContext($this->context);
$validator->expects($this->at(2 * $i))
->method('atPath')
->with($propertyPath)
->will($this->returnValue($validator));
$validator->expects($this->at(2 * $i + 1))
->method('validate')
->with($value, $this->logicalOr(null, array()), $group);
switch ($this->getApiVersion()) {
case Validation::API_VERSION_2_4:
$this->context->expects($this->at($i))
->method('validate')
->with($value, $propertyPath, $group);
break;
case Validation::API_VERSION_2_5:
case Validation::API_VERSION_2_5_BC:
$validator = $this->context->getValidator()->inContext($this->context);
$validator->expects($this->at(2 * $i))
->method('atPath')
->with($propertyPath)
->will($this->returnValue($validator));
$validator->expects($this->at(2 * $i + 1))
->method('validate')
->with($value, $this->logicalOr(null, array(), $this->isInstanceOf('\Symfony\Component\Validator\Constraints\Valid')), $group);
break;
}
}
protected function expectValidateValueAt($i, $propertyPath, $value, $constraints, $group = null)