[Validator] Group "Default" is now propagated to validated references when group sequences are validated

This conforms to JSR303 (see version 1.0 final, page 39).
This commit is contained in:
Bernhard Schussek 2010-11-17 00:51:45 +01:00 committed by Fabien Potencier
parent 6a148465da
commit 940ce9aedf
8 changed files with 257 additions and 135 deletions

View File

@ -42,6 +42,22 @@ class GraphWalker
{
$this->context->setCurrentClass($metadata->getClassName());
if ($group === Constraint::DEFAULT_GROUP && $metadata->hasGroupSequence()) {
$groups = $metadata->getGroupSequence();
foreach ($groups as $group) {
$this->walkClassForGroup($metadata, $object, $group, $propertyPath, Constraint::DEFAULT_GROUP);
if (count($this->getViolations()) > 0) {
break;
}
}
} else {
$this->walkClassForGroup($metadata, $object, $group, $propertyPath);
}
}
protected function walkClassForGroup(ClassMetadata $metadata, $object, $group, $propertyPath, $propagatedGroup = null)
{
foreach ($metadata->findConstraints($group) as $constraint) {
$this->walkConstraint($constraint, $object, $group, $propertyPath);
}
@ -50,15 +66,15 @@ class GraphWalker
foreach ($metadata->getConstrainedProperties() as $property) {
$localPropertyPath = empty($propertyPath) ? $property : $propertyPath.'.'.$property;
$this->walkProperty($metadata, $property, $object, $group, $localPropertyPath);
$this->walkProperty($metadata, $property, $object, $group, $localPropertyPath, $propagatedGroup);
}
}
}
public function walkProperty(ClassMetadata $metadata, $property, $object, $group, $propertyPath)
public function walkProperty(ClassMetadata $metadata, $property, $object, $group, $propertyPath, $propagatedGroup = null)
{
foreach ($metadata->getMemberMetadatas($property) as $member) {
$this->walkMember($member, $member->getValue($object), $group, $propertyPath);
$this->walkMember($member, $member->getValue($object), $group, $propertyPath, $propagatedGroup);
}
}
@ -69,7 +85,7 @@ class GraphWalker
}
}
protected function walkMember(MemberMetadata $metadata, $value, $group, $propertyPath)
protected function walkMember(MemberMetadata $metadata, $value, $group, $propertyPath, $propagatedGroup = null)
{
$this->context->setCurrentProperty($metadata->getPropertyName());
@ -78,7 +94,7 @@ class GraphWalker
}
if ($metadata->isCascaded()) {
$this->walkReference($value, $group, $propertyPath);
$this->walkReference($value, $propagatedGroup ?: $group, $propertyPath);
}
}

View File

@ -32,108 +32,54 @@ class Validator implements ValidatorInterface
public function validate($object, $groups = null)
{
$metadata = $this->metadataFactory->getClassMetadata(get_class($object));
$groupChain = $this->buildGroupChain($metadata, $groups);
$closure = function(GraphWalker $walker, $group) use ($metadata, $object) {
$walk = function(GraphWalker $walker, $group) use ($metadata, $object) {
return $walker->walkClass($metadata, $object, $group, '');
};
return $this->validateGraph($object, $closure, $groupChain);
return $this->validateGraph($object, $walk, $groups);
}
public function validateProperty($object, $property, $groups = null)
{
$metadata = $this->metadataFactory->getClassMetadata(get_class($object));
$groupChain = $this->buildGroupChain($metadata, $groups);
$closure = function(GraphWalker $walker, $group) use ($metadata, $property, $object) {
$walk = function(GraphWalker $walker, $group) use ($metadata, $property, $object) {
return $walker->walkProperty($metadata, $property, $object, $group, '');
};
return $this->validateGraph($object, $closure, $groupChain);
return $this->validateGraph($object, $walk, $groups);
}
public function validatePropertyValue($class, $property, $value, $groups = null)
{
$metadata = $this->metadataFactory->getClassMetadata($class);
$groupChain = $this->buildGroupChain($metadata, $groups);
$closure = function(GraphWalker $walker, $group) use ($metadata, $property, $value) {
$walk = function(GraphWalker $walker, $group) use ($metadata, $property, $value) {
return $walker->walkPropertyValue($metadata, $property, $value, $group, '');
};
return $this->validateGraph($object, $closure, $groupChain);
return $this->validateGraph($class, $walk, $groups);
}
public function validateValue($value, Constraint $constraint, $groups = null)
{
$groupChain = $this->buildSimpleGroupChain($groups);
$closure = function(GraphWalker $walker, $group) use ($constraint, $value) {
$walk = function(GraphWalker $walker, $group) use ($constraint, $value) {
return $walker->walkConstraint($constraint, $value, $group, '');
};
return $this->validateGraph($value, $closure, $groupChain);
return $this->validateGraph($value, $walk, $groups);
}
protected function validateGraph($root, \Closure $closure, GroupChain $groupChain)
protected function validateGraph($root, \Closure $walk, $groups = null)
{
$walker = new GraphWalker($root, $this->metadataFactory, $this->validatorFactory);
$groups = $groups ? (array)$groups : array(Constraint::DEFAULT_GROUP);
foreach ($groupChain->getGroups() as $group) {
$closure($walker, $group);
}
foreach ($groupChain->getGroupSequences() as $sequence) {
$violationCount = count($walker->getViolations());
foreach ($sequence as $group) {
$closure($walker, $group);
if (count($walker->getViolations()) > $violationCount) {
break;
}
}
foreach ($groups as $group) {
$walk($walker, $group);
}
return $walker->getViolations();
}
protected function buildSimpleGroupChain($groups)
{
if (null === $groups) {
$groups = array(Constraint::DEFAULT_GROUP);
} else {
$groups = (array)$groups;
}
$chain = new GroupChain();
foreach ($groups as $group) {
$chain->addGroup($group);
}
return $chain;
}
protected function buildGroupChain(ClassMetadata $metadata, $groups)
{
if (null === $groups) {
$groups = array(Constraint::DEFAULT_GROUP);
} else {
$groups = (array)$groups;
}
$chain = new GroupChain();
foreach ($groups as $group) {
if ($group == Constraint::DEFAULT_GROUP && $metadata->hasGroupSequence()) {
$chain->addGroupSequence($metadata->getGroupSequence());
} else {
$chain->addGroup($group);
}
}
return $chain;
}
}

View File

@ -26,7 +26,7 @@ class Entity extends EntityParent implements EntityInterface
*/
protected $firstName;
protected $lastName;
protected $reference;
public $reference;
private $internal;

View File

@ -6,4 +6,5 @@ use Symfony\Component\Validator\Constraint;
class FailingConstraint extends Constraint
{
public $message = '';
}

View File

@ -9,6 +9,8 @@ class FailingConstraintValidator extends ConstraintValidator
{
public function isValid($value, Constraint $constraint)
{
$this->setMessage($constraint->message, array());
return false;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Tests\Component\Validator\Fixtures;
class Reference
{
}

View File

@ -3,6 +3,7 @@
namespace Symfony\Tests\Component\Validator;
require_once __DIR__.'/Fixtures/Entity.php';
require_once __DIR__.'/Fixtures/Reference.php';
require_once __DIR__.'/Fixtures/ConstraintA.php';
require_once __DIR__.'/Fixtures/ConstraintAValidator.php';
require_once __DIR__.'/Fixtures/FailingConstraint.php';
@ -10,6 +11,7 @@ require_once __DIR__.'/Fixtures/FailingConstraintValidator.php';
require_once __DIR__.'/Fixtures/FakeClassMetadataFactory.php';
use Symfony\Tests\Component\Validator\Fixtures\Entity;
use Symfony\Tests\Component\Validator\Fixtures\Reference;
use Symfony\Tests\Component\Validator\Fixtures\FakeClassMetadataFactory;
use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint;
@ -25,6 +27,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
const CLASSNAME = 'Symfony\Tests\Component\Validator\Fixtures\Entity';
protected $factory;
protected $walker;
protected $metadata;
public function setUp()
@ -61,6 +64,93 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($this->walker->getViolations()));
}
public function testWalkClassInDefaultGroupTraversesGroupSequence()
{
$entity = new Entity();
$this->metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
'groups' => 'First',
)));
$this->metadata->addGetterConstraint('lastName', new FailingConstraint(array(
'groups' => 'Second',
)));
$this->metadata->setGroupSequence(array('First', 'Second'));
$this->walker->walkClass($this->metadata, $entity, 'Default', '');
// After validation of group "First" failed, no more group was
// validated
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
'Root',
'firstName',
''
));
$this->assertEquals($violations, $this->walker->getViolations());
}
public function testWalkClassInGroupSequencePropagatesDefaultGroup()
{
$entity = new Entity();
$entity->reference = new Reference();
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->metadata->setGroupSequence(array('First'));
$referenceMetadata = new ClassMetadata(get_class($entity->reference));
$referenceMetadata->addConstraint(new FailingConstraint(array(
// this constraint is only evaluated if group "Default" is
// propagated to the reference
'groups' => 'Default',
)));
$this->factory->addClassMetadata($referenceMetadata);
$this->walker->walkClass($this->metadata, $entity, 'Default', '');
// The validation of the reference's FailingConstraint in group
// "Default" was launched
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
'Root',
'reference',
$entity->reference
));
$this->assertEquals($violations, $this->walker->getViolations());
}
public function testWalkClassInOtherGroupTraversesNoGroupSequence()
{
$entity = new Entity();
$this->metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
'groups' => 'First',
)));
$this->metadata->addGetterConstraint('lastName', new FailingConstraint(array(
'groups' => 'Second',
)));
$this->metadata->setGroupSequence(array('First', 'Second'));
$this->walker->walkClass($this->metadata, $entity, 'Second', '');
// Only group "Second" was validated
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
'Root',
'lastName',
''
));
$this->assertEquals($violations, $this->walker->getViolations());
}
public function testWalkPropertyValueValidatesConstraints()
{
$this->metadata->addPropertyConstraint('firstName', new ConstraintA());

View File

@ -2,79 +2,139 @@
namespace Symfony\Tests\Component\Validator;
use Symfony\Component\Validator\Constraint;
require_once __DIR__.'/Fixtures/Entity.php';
require_once __DIR__.'/Fixtures/FailingConstraint.php';
require_once __DIR__.'/Fixtures/FailingConstraintValidator.php';
require_once __DIR__.'/Fixtures/FakeClassMetadataFactory.php';
use Symfony\Tests\Component\Validator\Fixtures\Entity;
use Symfony\Tests\Component\Validator\Fixtures\FakeClassMetadataFactory;
use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint;
use Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Mapping\Metadata;
use Symfony\Component\Validator\Specification\PropertySpecification;
use Symfony\Component\Validator\Specification\ClassSpecification;
use Symfony\Component\Validator\Specification\Specification;
class ValidatorTest_Class
{
public $firstName = 'Bernhard';
public $reference;
public function getLastName()
{
return 'Schussek';
}
public function isAustralian()
{
return false;
}
}
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class ValidatorTest extends \PHPUnit_Framework_TestCase
{
public function testValidatePropertyConstraint()
protected $factory;
protected $validator;
public function setUp()
{
/*
$subject = new ValidatorTest_Class();
$subjectClass = get_class($subject);
$this->factory = new FakeClassMetadataFactory();
$this->validator = new Validator($this->factory, new ConstraintValidatorFactory());
}
$constraint = new Constraint();
$property = new PropertySpecification($subjectClass, 'firstName', array($constraint));
$class = new ClassSpecification($subjectClass, array($property));
$specification = new Specification(array($class));
$metadata = new Metadata($specification);
public function testValidate_defaultGroup()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint());
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
'groups' => 'Custom',
)));
$this->factory->addClassMetadata($metadata);
$validatorMock = $this->getMock('Symfony\Component\Validator\ConstraintValidatorInterface');
$validatorMock->expects($this->once())
->method('isValid')
->with($this->equalTo('Bernhard'), $this->equalTo($constraint))
->will($this->returnValue(false));
$validatorMock->expects($this->atLeastOnce())
->method('getMessageTemplate')
->will($this->returnValue('message'));
$validatorMock->expects($this->atLeastOnce())
->method('getMessageParameters')
->will($this->returnValue(array('param' => 'value')));
$factoryMock = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface');
$factoryMock->expects($this->once())
->method('getInstance')
->with($this->equalTo($constraint->validatedBy()))
->will($this->returnValue($validatorMock));
$validator = new Validator($metadata, $factoryMock);
$builder = new PropertyPathBuilder();
$expected = new ConstraintViolationList();
$expected->add(new ConstraintViolation(
'message',
array('param' => 'value'),
$subjectClass,
$builder->atProperty('firstName')->getPropertyPath(),
'Bernhard'
// Only the constraint of group "Default" failed
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
$entity,
'firstName',
''
));
$this->assertEquals($expected, $validator->validateProperty($subject, 'firstName'));
*/
$this->assertEquals($violations, $this->validator->validate($entity));
}
public function testValidate_oneGroup()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint());
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
'groups' => 'Custom',
)));
$this->factory->addClassMetadata($metadata);
// Only the constraint of group "Custom" failed
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
$entity,
'lastName',
''
));
$this->assertEquals($violations, $this->validator->validate($entity, 'Custom'));
}
public function testValidate_multipleGroups()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
'groups' => 'First',
)));
$metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
'groups' => 'Second',
)));
$this->factory->addClassMetadata($metadata);
// The constraints of both groups failed
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
$entity,
'firstName',
''
));
$violations->add(new ConstraintViolation(
'',
array(),
$entity,
'lastName',
''
));
$result = $this->validator->validate($entity, array('First', 'Second'));
$this->assertEquals($violations, $result);
}
public function testValidateProperty()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint());
$this->factory->addClassMetadata($metadata);
$result = $this->validator->validateProperty($entity, 'firstName');
$this->assertEquals(1, count($result));
}
public function testValidatePropertyValue()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$metadata->addPropertyConstraint('firstName', new FailingConstraint());
$this->factory->addClassMetadata($metadata);
$result = $this->validator->validatePropertyValue(get_class($entity), 'firstName', 'Bernhard');
$this->assertEquals(1, count($result));
}
public function testValidateValue()
{
$result = $this->validator->validateValue('Bernhard', new FailingConstraint());
$this->assertEquals(1, count($result));
}
}