merged branch canni/enable_validation_groups_callback (PR #2498)

Commits
-------

d08ec5e Add DelegatingValidator tests
e1822e7 Enable dynamic set of validation groups by a callback or Closure

Discussion
----------

[Form][Validator] Enable dynamic set of validation groups based on callback

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes
Symfony2 tests written for new feature: yes
closes tickets: #2498 #1151

This will allow developer to pass a Closure or a callback array as a Validation groups option of a form. Eg:

```
class ClientType extends AbstarctType
{
// ...
    public function getDefaultOptions(array $options)
    {
        return array(
            'validation_groups' => function(FormInterface $form){
                // return array of validation groups based on submitted user data (data is after transform)
                $data = $form->getData();
                if($data->getType() == Entity\Client::TYPE_PERSON)
                    return array('Default', 'person');
                else
                    return array('Default', 'company');
            },
        );
    }
// ...
}
```

```
class ClientType extends AbstarctType
{
// ...
    public function getDefaultOptions(array $options)
    {
        return array(
            'validation_groups' => array(
                'Acme\\AcmeBundle\\Entity\\Client',
                'determineValidationGroups'
            ),
        );
    }
// ...
}
```

This will make developers life easier !

---------------------------------------------------------------------------

by schmittjoh at 2011/10/27 06:39:56 -0700

Does that work if your ClientType were added to another form type?

e.g.

```php
<?php

class MyComplexType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('client', new ClientType());
    }

    // ...
}
```

---------------------------------------------------------------------------

by canni at 2011/10/27 06:44:33 -0700

This is doing nothing more than injecting array of validation groups, should work, but I have not tested this use case.

---------------------------------------------------------------------------

by canni at 2011/10/28 01:58:26 -0700

PHPUnit output

```
OK, but incomplete or skipped tests!
Tests: 5011, Assertions: 12356, Incomplete: 36, Skipped: 32.
```

---------------------------------------------------------------------------

by canni at 2011/11/02 11:37:47 -0700

Now functionality is complete, test are written, and implementation is clean. :)

---------------------------------------------------------------------------

by stloyd at 2011/11/02 11:50:44 -0700

Can tou `squash` your commits ? Thanks.

---------------------------------------------------------------------------

by canni at 2011/11/02 11:58:41 -0700

Done

---------------------------------------------------------------------------

by fabpot at 2011/11/07 07:51:18 -0800

Can you add some tests for the `DelegatingValidator` class, which is where we can ensure that the new feature actually works as expected?

---------------------------------------------------------------------------

by canni at 2011/11/07 13:53:16 -0800

OK, I've written proof-of-concept tests, also I've squashed few commits to make things clear.
Personally I think this should go straight into 2.0 series, as it do not beak BC, and a feature is really nice to use.

---------------------------------------------------------------------------

by stof at 2011/11/07 14:17:15 -0800

@canni the 2.0 branch is for bug fixes, not for new features. This is the difference between maintenance releases and minor releases.
This commit is contained in:
Fabien Potencier 2011-11-08 09:05:41 +01:00
commit 4f3e7bb698
4 changed files with 148 additions and 3 deletions

View File

@ -27,9 +27,13 @@ class FieldTypeValidatorExtension extends AbstractTypeExtension
public function buildForm(FormBuilder $builder, array $options)
{
$options['validation_groups'] = empty($options['validation_groups'])
? null
: (array)$options['validation_groups'];
if (empty($options['validation_groups'])) {
$options['validation_groups'] = null;
} else {
$options['validation_groups'] = is_callable($options['validation_groups'])
? $options['validation_groups']
: (array) $options['validation_groups'];
}
$builder
->setAttribute('validation_groups', $options['validation_groups'])

View File

@ -133,6 +133,10 @@ class DelegatingValidator implements FormValidatorInterface
if ($form->hasAttribute('validation_groups')) {
$groups = $form->getAttribute('validation_groups');
if (is_callable($groups)) {
$groups = (array) call_user_func($groups, $form);
}
}
$currentForm = $form;
@ -141,6 +145,10 @@ class DelegatingValidator implements FormValidatorInterface
if ($currentForm->hasAttribute('validation_groups')) {
$groups = $currentForm->getAttribute('validation_groups');
if (is_callable($groups)) {
$groups = (array) call_user_func($groups, $currentForm);
}
}
}

View File

@ -38,6 +38,24 @@ class FieldTypeValidatorExtensionTest extends TypeTestCase
$this->assertEquals(array('group1', 'group2'), $form->getAttribute('validation_groups'));
}
public function testValidationGroupsCanBeSetToCallback()
{
$form = $this->factory->create('field', null, array(
'validation_groups' => array($this, 'testValidationGroupsCanBeSetToCallback'),
));
$this->assertTrue(is_callable($form->getAttribute('validation_groups')));
}
public function testValidationGroupsCanBeSetToClosure()
{
$form = $this->factory->create('field', null, array(
'validation_groups' => function($data, $extraData){ return null; },
));
$this->assertTrue(is_callable($form->getAttribute('validation_groups')));
}
public function testBindValidatesData()
{
$builder = $this->factory->createBuilder('field', null, array(

View File

@ -11,6 +11,7 @@
namespace Symfony\Tests\Component\Form\Extension\Validator\Validator;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Util\PropertyPath;
@ -90,6 +91,16 @@ class DelegatingValidatorTest extends \PHPUnit_Framework_TestCase
return $this->getMock('Symfony\Tests\Component\Form\FormInterface');
}
/**
* Access has to be public, as this method is called via callback array
* in {@link testValidateFormDataCanHandleCallbackValidationGroups()}
* and {@link testValidateFormDataUsesInheritedCallbackValidationGroup()}
*/
public function getValidationGroups(FormInterface $form)
{
return array('group1', 'group2');
}
public function testUseValidateValueWhenValidationConstraintExist()
{
$constraint = $this->getMockForAbstractClass('Symfony\Component\Validator\Constraint');
@ -597,6 +608,52 @@ class DelegatingValidatorTest extends \PHPUnit_Framework_TestCase
DelegatingValidator::validateFormData($form, $context);
}
public function testValidateFormDataCanHandleCallbackValidationGroups()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$object = $this->getMock('\stdClass');
$form = $this->getBuilder()
->setAttribute('validation_groups', array($this, 'getValidationGroups'))
->getForm();
$graphWalker->expects($this->at(0))
->method('walkReference')
->with($object, 'group1', 'data', true);
$graphWalker->expects($this->at(1))
->method('walkReference')
->with($object, 'group2', 'data', true);
$form->setData($object);
DelegatingValidator::validateFormData($form, $context);
}
public function testValidateFormDataCanHandleClosureValidationGroups()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$object = $this->getMock('\stdClass');
$form = $this->getBuilder()
->setAttribute('validation_groups', function(FormInterface $form){
return array('group1', 'group2');
})
->getForm();
$graphWalker->expects($this->at(0))
->method('walkReference')
->with($object, 'group1', 'data', true);
$graphWalker->expects($this->at(1))
->method('walkReference')
->with($object, 'group2', 'data', true);
$form->setData($object);
DelegatingValidator::validateFormData($form, $context);
}
public function testValidateFormDataUsesInheritedValidationGroup()
{
$graphWalker = $this->getMockGraphWalker();
@ -622,6 +679,64 @@ class DelegatingValidatorTest extends \PHPUnit_Framework_TestCase
DelegatingValidator::validateFormData($child, $context);
}
public function testValidateFormDataUsesInheritedCallbackValidationGroup()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$context->setPropertyPath('path');
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder()
->setAttribute('validation_groups', array($this, 'getValidationGroups'))
->getForm();
$child = $this->getBuilder()
->setAttribute('validation_groups', null)
->getForm();
$parent->add($child);
$child->setData($object);
$graphWalker->expects($this->at(0))
->method('walkReference')
->with($object, 'group1', 'path.data', true);
$graphWalker->expects($this->at(1))
->method('walkReference')
->with($object, 'group2', 'path.data', true);
DelegatingValidator::validateFormData($child, $context);
}
public function testValidateFormDataUsesInheritedClosureValidationGroup()
{
$graphWalker = $this->getMockGraphWalker();
$metadataFactory = $this->getMockMetadataFactory();
$context = new ExecutionContext('Root', $graphWalker, $metadataFactory);
$context->setPropertyPath('path');
$object = $this->getMock('\stdClass');
$parent = $this->getBuilder()
->setAttribute('validation_groups', function(FormInterface $form){
return array('group1', 'group2');
})
->getForm();
$child = $this->getBuilder()
->setAttribute('validation_groups', null)
->getForm();
$parent->add($child);
$child->setData($object);
$graphWalker->expects($this->at(0))
->method('walkReference')
->with($object, 'group1', 'path.data', true);
$graphWalker->expects($this->at(1))
->method('walkReference')
->with($object, 'group2', 'path.data', true);
DelegatingValidator::validateFormData($child, $context);
}
public function testValidateFormDataAppendsPropertyPath()
{
$graphWalker = $this->getMockGraphWalker();