merged branch blogsh/dynamic_constraints (PR #3114)

Commits
-------

92f820a Renamed registerConstraints to loadDynamicValidatorMetadata
dd12ff8 CS fix, getConstraints renamed
09c1911 [Validator] Improved dynamic constraints
54cb6e4 [Validator] Added dynamic constraints

Discussion
----------

[Validator] Dynamic constraints

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: yes

By now the Validator component is based on a per-class configuration of
constraints, but in some cases it might be neccessary to add new constraints
dynamically at runtime.
This pull request adds a "ConstraintProviderInterface" to the Validator component. If an object is validated that implements this interface the method "getConstraints" is used to add dynamic constraints:

    class User implements ConstraintProviderInterface
    {
        protected $isPremium;
        protected $paymentInformation;

        public function getConstraints(ClassMetadata $metadata)
        {
            if ($this->isPremium) {
                $metadata->addPropertyConstraint('paymentInformation', new NotBlank());
            }
        }
    }

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

by alexandresalome at 2012-01-15T11:20:04Z

Related to #1151

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

by canni at 2012-01-16T09:22:28Z

👍

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

by bschussek at 2012-01-16T12:32:44Z

I think this is a good addition. I think we still have a naming problem though. When constraints are loaded using a static method, the default name for the loader method is `loadValidatorMetadata`. Since the method for dynamic constraint loading is basically the same, I think the two names should be related.

Solution (1): Rename the method in your interface to `loadDynamicValidatorMetadata`. Ugly and long.

    class MyClass implements ConstraintProviderInterface
    {
        public static loadValidatorMetadata(ClassMetadata $metadata) ...

        public loadDynamicValidatorMetadata(ClassMetadata $metadata) ...
    }

Solution (2): Rename the default method name in `StaticMethodLoader` to `registerConstraints` and adjust the docs. Breaks BC.

    class MyClass implements ConstraintProviderInterface
    {
        public static registerConstraints(ClassMetadata $metadata) ...

        public registerDynamicConstraints(ClassMetadata $metadata) ...
    }

@fabpot: Are we allowed to break BC here? If not, we should probably stick to (1).

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

by fabpot at 2012-01-16T12:36:14Z

I would prefer to not break BC if possible.

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

by blogsh at 2012-01-16T15:25:46Z

So "loadDynamicValidatorMetadata" would be the best solution?

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

by althaus at 2012-01-17T13:39:19Z

>So "loadDynamicValidatorMetadata" would be the best solution?

Sounds fine for me based on @bschussek's comment.
This commit is contained in:
Fabien Potencier 2012-01-22 10:26:39 +01:00
commit 6b9a355fb0
4 changed files with 99 additions and 0 deletions

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Makes it possible to define dynamic constraints for an object.
*/
interface ConstraintProviderInterface
{
/**
* Lets the user create dynamic constraints
*
* @param Mapping\ClassMetadata
*/
function loadDynamicValidatorMetadata(ClassMetadata $metadata);
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* The default implementation of the ValidatorInterface.
@ -57,6 +58,13 @@ class Validator implements ValidatorInterface
public function validate($object, $groups = null)
{
$metadata = $this->metadataFactory->getClassMetadata(get_class($object));
if ($object instanceof ConstraintProviderInterface) {
$dynamicMetadata = new ClassMetadata(get_class($object));
$dynamicMetadata->mergeConstraints($metadata);
$object->loadDynamicValidatorMetadata($dynamicMetadata);
$metadata = $dynamicMetadata;
}
$walk = function(GraphWalker $walker, $group) use ($metadata, $object) {
return $walker->walkObject($metadata, $object, $group, '');

View File

@ -0,0 +1,30 @@
<?php
namespace Symfony\Tests\Component\Validator\Fixtures;
use Symfony\Component\Validator\ConstraintProviderInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class DynamicConstraintsEntity implements ConstraintProviderInterface
{
protected $validationEnabled = false;
protected $firstValue;
public function setValidation($enabled)
{
$this->validationEnabled = $enabled;
}
public function getSecondValue()
{
return null;
}
public function loadDynamicValidatorMetadata(ClassMetadata $metadata)
{
if ($this->validationEnabled) {
$metadata->addPropertyConstraint('firstValue', new FailingConstraint());
$metadata->addGetterConstraint('secondValue', new FailingConstraint());
}
}
}

View File

@ -15,10 +15,12 @@ require_once __DIR__.'/Fixtures/Entity.php';
require_once __DIR__.'/Fixtures/FailingConstraint.php';
require_once __DIR__.'/Fixtures/FailingConstraintValidator.php';
require_once __DIR__.'/Fixtures/FakeClassMetadataFactory.php';
require_once __DIR__.'/Fixtures/DynamicConstraintsEntity.php';
use Symfony\Tests\Component\Validator\Fixtures\Entity;
use Symfony\Tests\Component\Validator\Fixtures\FakeClassMetadataFactory;
use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint;
use Symfony\Tests\Component\Validator\Fixtures\DynamicConstraintsEntity;
use Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
@ -121,6 +123,38 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($violations, $result);
}
public function testValidate_constraintProvider()
{
$entity = new DynamicConstraintsEntity();
$metadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($metadata);
$entity->setValidation(true);
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
$entity,
'firstValue',
''
));
$violations->add(new ConstraintViolation(
'',
array(),
$entity,
'secondValue',
''
));
$this->assertEquals($violations, $this->validator->validate($entity));
$entity->setValidation(false);
$violations = new ConstraintViolationList();
$this->assertEquals($violations, $this->validator->validate($entity));
}
public function testValidateProperty()
{