[Validator][Form] Removed support for match-all group "*"

The constraint "Valid" does not accept any options or groups anymore. As per
JSR303 1.0 final, section 3.5.1 "Object graph validation" (page 39),
properties  annotated with valid should be cascaded independent of the current
group (i.e. always). Thus the group "*" is not necessary anymore and was
removed from the "Valid" constraint in the Form validation.xml.
This commit is contained in:
Bernhard Schussek 2010-11-16 23:41:46 +01:00 committed by Fabien Potencier
parent 8df966f507
commit 6a148465da
16 changed files with 250 additions and 173 deletions

View File

@ -17,9 +17,7 @@
<class name="Symfony\Component\Form\Form">
<getter property="data">
<constraint name="Valid">
<option name="groups">*</option>
</constraint>
<constraint name="Valid" />
</getter>
<getter property="postMaxSizeReached">
<constraint name="AssertFalse">

View File

@ -30,7 +30,7 @@ abstract class Constraint
{
const DEFAULT_GROUP = 'Default';
public $groups = self::DEFAULT_GROUP;
public $groups = array(self::DEFAULT_GROUP);
/**
* Initializes the constraint with options.

View File

@ -11,8 +11,21 @@ namespace Symfony\Component\Validator\Constraints;
* with this source code in the file LICENSE.
*/
use Symfony\Component\Validator\Exception\InvalidOptionsException;
class Valid extends \Symfony\Component\Validator\Constraint
{
public $message = 'This value should be instance of class {{ class }}';
public $class;
/**
* This constraint does not accept any options
*
* @param mixed $options Unsupported argument!
*
* @throws InvalidOptionsException When the parameter $options is not NULL
*/
public function __construct($options = null)
{
if (null !== $options) {
throw new InvalidOptionsException('The constraint Valid does not accept any options');
}
}
}

View File

@ -1,49 +0,0 @@
<?php
namespace Symfony\Component\Validator\Constraints;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class ValidValidator extends ConstraintValidator
{
public function isValid($value, Constraint $constraint)
{
if ($value === null) {
return true;
}
$walker = $this->context->getGraphWalker();
$group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
$factory = $this->context->getClassMetadataFactory();
if (is_array($value)) {
foreach ($value as $key => $element) {
$walker->walkConstraint($constraint, $element, $group, $propertyPath.'['.$key.']');
}
} else if (!is_object($value)) {
throw new UnexpectedTypeException($value, 'object or array');
} else if ($constraint->class && !$value instanceof $constraint->class) {
$this->setMessage($constraint->message, array('{{ class }}' => $constraint->class));
return false;
} else {
$metadata = $factory->getClassMetadata(get_class($value));
$walker->walkClass($metadata, $value, $group, $propertyPath);
}
return true;
}
}

View File

@ -76,6 +76,26 @@ class GraphWalker
foreach ($metadata->findConstraints($group) as $constraint) {
$this->walkConstraint($constraint, $value, $group, $propertyPath);
}
if ($metadata->isCascaded()) {
$this->walkReference($value, $group, $propertyPath);
}
}
protected function walkReference($value, $group, $propertyPath)
{
if (null !== $value) {
if (is_array($value)) {
foreach ($value as $key => $element) {
$this->walkReference($element, $group, $propertyPath.'['.$key.']');
}
} else if (!is_object($value)) {
throw new UnexpectedTypeException($value, 'object or array');
} else {
$metadata = $this->metadataFactory->getClassMetadata(get_class($value));
$this->walkClass($metadata, $value, $group, $propertyPath);
}
}
}
public function walkConstraint(Constraint $constraint, $value, $group, $propertyPath)

View File

@ -12,7 +12,8 @@ namespace Symfony\Component\Validator\Mapping;
*/
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ValidatorException;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
class ClassMetadata extends ElementMetadata
{
@ -77,6 +78,10 @@ class ClassMetadata extends ElementMetadata
*/
public function addConstraint(Constraint $constraint)
{
if ($constraint instanceof Valid) {
throw new ConstraintDefinitionException('The constraint Valid can only be put on properties or getters');
}
$constraint->addImplicitGroupName($this->getShortClassName());
parent::addConstraint($constraint);

View File

@ -95,14 +95,8 @@ abstract class ElementMetadata
*/
public function findConstraints($group)
{
$globalConstraints = isset($this->constraintsByGroup['*'])
? $this->constraintsByGroup['*']
: array();
$groupConstraints = isset($this->constraintsByGroup[$group])
return isset($this->constraintsByGroup[$group])
? $this->constraintsByGroup[$group]
: array();
return array_merge((array) $globalConstraints, (array) $groupConstraints);
}
}

View File

@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Mapping;
* with this source code in the file LICENSE.
*/
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ValidatorException;
abstract class MemberMetadata extends ElementMetadata
@ -18,6 +20,7 @@ abstract class MemberMetadata extends ElementMetadata
public $class;
public $name;
public $property;
public $cascaded = false;
private $reflMember;
/**
@ -34,6 +37,20 @@ abstract class MemberMetadata extends ElementMetadata
$this->property = $property;
}
/**
* {@inheritDoc}
*/
public function addConstraint(Constraint $constraint)
{
if ($constraint instanceof Valid) {
$this->cascaded = true;
} else {
parent::addConstraint($constraint);
}
return $this;
}
/**
* Returns the names of the properties that should be serialized
*
@ -44,7 +61,8 @@ abstract class MemberMetadata extends ElementMetadata
return array_merge(parent::__sleep(), array(
'class',
'name',
'property'
'property',
'cascaded', // TESTME
));
}
@ -108,6 +126,16 @@ abstract class MemberMetadata extends ElementMetadata
return $this->getReflectionMember()->isPrivate();
}
/**
* Returns whether objects stored in this member should be validated
*
* @return boolean
*/
public function isCascaded()
{
return $this->cascaded;
}
/**
* Returns the value of this property in the given object
*

View File

@ -1,105 +0,0 @@
<?php
namespace Symfony\Tests\Component\Validator;
require_once __DIR__.'/../Fixtures/Entity.php';
use Symfony\Component\Validator\ValidationContext;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Constraints\ValidValidator;
use Symfony\Tests\Component\Validator\Fixtures\Entity;
class ValidValidatorTest extends \PHPUnit_Framework_TestCase
{
const CLASSNAME = 'Symfony\Tests\Component\Validator\Fixtures\Entity';
protected $validator;
protected $factory;
protected $walker;
protected $context;
public function setUp()
{
$this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false);
$this->factory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
$this->context = new ValidationContext('Root', $this->walker, $this->factory);
$this->validator = new ValidValidator();
$this->validator->initialize($this->context);
}
public function testNullIsValid()
{
$this->assertTrue($this->validator->isValid(null, new Valid()));
}
public function testThrowsExceptionIfNotObjectOrArray()
{
$this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
$this->validator->isValid('foobar', new Valid());
}
public function testWalkObject()
{
$this->context->setGroup('MyGroup');
$this->context->setPropertyPath('foo');
$metadata = $this->createClassMetadata();
$entity = new Entity();
$this->factory->expects($this->once())
->method('getClassMetadata')
->with($this->equalTo(self::CLASSNAME))
->will($this->returnValue($metadata));
$this->walker->expects($this->once())
->method('walkClass')
->with($this->equalTo($metadata), $this->equalTo($entity), 'MyGroup', 'foo');
$this->assertTrue($this->validator->isValid($entity, new Valid()));
}
public function testWalkArray()
{
$this->context->setGroup('MyGroup');
$this->context->setPropertyPath('foo');
$constraint = new Valid();
$entity = new Entity();
// can only test for one object due to PHPUnit's mocking limitations
$array = array('key' => $entity);
$this->walker->expects($this->once())
->method('walkConstraint')
->with($this->equalTo($constraint), $this->equalTo($entity), 'MyGroup', 'foo[key]');
$this->assertTrue($this->validator->isValid($array, $constraint));
}
public function testValidateClass_Succeeds()
{
$metadata = $this->createClassMetadata();
$entity = new Entity();
$this->factory->expects($this->any())
->method('getClassMetadata')
->with($this->equalTo(self::CLASSNAME))
->will($this->returnValue($metadata));
$this->assertTrue($this->validator->isValid($entity, new Valid(array('class' => self::CLASSNAME))));
}
public function testValidateClass_Fails()
{
$entity = new \stdClass();
$this->assertFalse($this->validator->isValid($entity, new Valid(array('class' => self::CLASSNAME))));
}
protected function createClassMetadata()
{
return $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadata', array(), array(), '', false);
}
}

View File

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

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Tests\Component\Validator\Fixtures;
use Symfony\Component\Validator\Constraint;
class FailingConstraint extends Constraint
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace Symfony\Tests\Component\Validator\Fixtures;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class FailingConstraintValidator extends ConstraintValidator
{
public function isValid($value, Constraint $constraint)
{
return false;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Symfony\Tests\Component\Validator\Fixtures;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
class FakeClassMetadataFactory implements ClassMetadataFactoryInterface
{
protected $metadatas = array();
public function getClassMetadata($class)
{
if (!isset($this->metadatas[$class])) {
throw new \RuntimeException('No metadata for class ' . $class);
}
return $this->metadatas[$class];
}
public function addClassMetadata(ClassMetadata $metadata)
{
$this->metadatas[$metadata->getClassName()] = $metadata;
}
}

View File

@ -5,17 +5,19 @@ namespace Symfony\Tests\Component\Validator;
require_once __DIR__.'/Fixtures/Entity.php';
require_once __DIR__.'/Fixtures/ConstraintA.php';
require_once __DIR__.'/Fixtures/ConstraintAValidator.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\ConstraintA;
use Symfony\Tests\Component\Validator\Fixtures\FailingConstraint;
use Symfony\Component\Validator\GraphWalker;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\Any;
use Symfony\Component\Validator\Constraints\Valid;
class GraphWalkerTest extends \PHPUnit_Framework_TestCase
@ -27,7 +29,7 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
public function setUp()
{
$this->factory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface');
$this->factory = new FakeClassMetadataFactory();
$this->walker = new GraphWalker('Root', $this->factory, new ConstraintValidatorFactory());
$this->metadata = new ClassMetadata(self::CLASSNAME);
}
@ -68,6 +70,101 @@ class GraphWalkerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(1, count($this->walker->getViolations()));
}
public function testWalkCascadedPropertyValidatesReferences()
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
// validate entity when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
// invoke validation on an object
$this->walker->walkPropertyValue(
$this->metadata,
'reference',
$entity, // object!
'Default',
'path'
);
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
'Root',
'path',
$entity
));
$this->assertEquals($violations, $this->walker->getViolations());
}
public function testWalkCascadedPropertyValidatesArrays()
{
$entity = new Entity();
$entityMetadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($entityMetadata);
// add a constraint for the entity that always fails
$entityMetadata->addConstraint(new FailingConstraint());
// validate array when validating the property "reference"
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->walker->walkPropertyValue(
$this->metadata,
'reference',
array('key' => $entity), // array!
'Default',
'path'
);
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'',
array(),
'Root',
'path[key]',
$entity
));
$this->assertEquals($violations, $this->walker->getViolations());
}
public function testWalkCascadedPropertyDoesNotValidateNullValues()
{
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->walker->walkPropertyValue(
$this->metadata,
'reference',
null,
'Default',
''
);
$this->assertEquals(0, count($this->walker->getViolations()));
}
public function testWalkCascadedPropertyRequiresObjectOrArray()
{
$this->metadata->addPropertyConstraint('reference', new Valid());
$this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
$this->walker->walkPropertyValue(
$this->metadata,
'reference',
'no object',
'Default',
''
);
}
public function testWalkConstraintBuildsAViolationIfFailed()
{
$constraint = new ConstraintA();

View File

@ -9,6 +9,7 @@ require_once __DIR__.'/../Fixtures/ConstraintB.php';
use Symfony\Tests\Component\Validator\Fixtures\Entity;
use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
use Symfony\Tests\Component\Validator\Fixtures\ConstraintB;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\PropertyMetadata;
@ -24,6 +25,13 @@ class ClassMetadataTest extends \PHPUnit_Framework_TestCase
$this->metadata = new ClassMetadata(self::CLASSNAME);
}
public function testAddConstraintDoesNotAcceptValid()
{
$this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException');
$this->metadata->addConstraint(new Valid());
}
public function testAddPropertyConstraints()
{
$this->metadata->addPropertyConstraint('firstName', new ConstraintA());

View File

@ -7,6 +7,7 @@ require_once __DIR__.'/../Fixtures/ConstraintB.php';
use Symfony\Tests\Component\Validator\Fixtures\ConstraintA;
use Symfony\Tests\Component\Validator\Fixtures\ConstraintB;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\MemberMetadata;
class MemberMetadataTest extends \PHPUnit_Framework_TestCase
@ -22,6 +23,24 @@ class MemberMetadataTest extends \PHPUnit_Framework_TestCase
);
}
public function testAddValidSetsMemberToCascaded()
{
$result = $this->metadata->addConstraint(new Valid());
$this->assertEquals(array(), $this->metadata->getConstraints());
$this->assertEquals($result, $this->metadata);
$this->assertTrue($this->metadata->isCascaded());
}
public function testAddOtherConstraintDoesNotSetMemberToCascaded()
{
$result = $this->metadata->addConstraint($constraint = new ConstraintA());
$this->assertEquals(array($constraint), $this->metadata->getConstraints());
$this->assertEquals($result, $this->metadata);
$this->assertFalse($this->metadata->isCascaded());
}
public function testSerialize()
{
$this->metadata->addConstraint(new ConstraintA(array('property1' => 'A')));