merged branch bschussek/issue3428 (PR #4862)

Commits
-------

1fe3996 [Validator] Improved error messages displayed when the Valid constraint is misused

Discussion
----------

[Validator] Improved error messages displayed when the Valid constraint is misused

Bug fix: no
Feature addition: no
Backwards compatibility break: no
Symfony2 tests pass: yes
Fixes the following tickets: #3428
Todo: -
This commit is contained in:
Fabien Potencier 2012-07-11 19:02:28 +02:00
commit d04949f05e
10 changed files with 235 additions and 16 deletions

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @Annotation
@ -22,6 +23,28 @@ class All extends Constraint
{
public $constraints = array();
/**
* {@inheritDoc}
*/
public function __construct($options = null)
{
parent::__construct($options);
if (!is_array($this->constraints)) {
$this->constraints = array($this->constraints);
}
foreach ($this->constraints as $constraint) {
if (!$constraint instanceof Constraint) {
throw new ConstraintDefinitionException('The value ' . $constraint . ' is not an instance of Constraint in constraint ' . __CLASS__);
}
if ($constraint instanceof Valid) {
throw new ConstraintDefinitionException('The constraint Valid cannot be nested inside constraint ' . __CLASS__ . '. You can only declare the Valid constraint directly on a field or method.');
}
}
}
public function getDefaultOption()
{
return 'constraints';

View File

@ -44,12 +44,8 @@ class AllValidator extends ConstraintValidator
$group = $this->context->getGroup();
$propertyPath = $this->context->getPropertyPath();
// cannot simply cast to array, because then the object is converted to an
// array instead of wrapped inside
$constraints = is_array($constraint->constraints) ? $constraint->constraints : array($constraint->constraints);
foreach ($value as $key => $element) {
foreach ($constraints as $constr) {
foreach ($constraint->constraints as $constr) {
$walker->walkConstraint($constr, $element, $group, $propertyPath.'['.$key.']');
}
}

View File

@ -12,6 +12,9 @@
namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Collection\Required;
use Symfony\Component\Validator\Constraints\Collection\Optional;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @Annotation
@ -38,6 +41,30 @@ class Collection extends Constraint
}
parent::__construct($options);
if (!is_array($this->fields)) {
throw new ConstraintDefinitionException('The option "fields" is expected to be an array in constraint ' . __CLASS__);
}
foreach ($this->fields as $fieldName => $field) {
if (!$field instanceof Optional && !$field instanceof Required) {
$this->fields[$fieldName] = $field = new Required($field);
}
if (!is_array($field->constraints)) {
$field->constraints = array($field->constraints);
}
foreach ($field->constraints as $constraint) {
if (!$constraint instanceof Constraint) {
throw new ConstraintDefinitionException('The value ' . $constraint . ' of the field ' . $fieldName . ' is not an instance of Constraint in constraint ' . __CLASS__);
}
if ($constraint instanceof Valid) {
throw new ConstraintDefinitionException('The constraint Valid cannot be nested inside constraint ' . __CLASS__ . '. You can only declare the Valid constraint directly on a field or method.');
}
}
}
}
public function getRequiredOptions()

View File

@ -52,17 +52,7 @@ class CollectionValidator extends ConstraintValidator
(is_array($value) && array_key_exists($field, $value)) ||
($value instanceof \ArrayAccess && $value->offsetExists($field))
) {
if ($fieldConstraint instanceof Required || $fieldConstraint instanceof Optional) {
$constraints = $fieldConstraint->constraints;
} else {
$constraints = $fieldConstraint;
}
// cannot simply cast to array, because then the object is converted to an
// array instead of wrapped inside
$constraints = is_array($constraints) ? $constraints : array($constraints);
foreach ($constraints as $constr) {
foreach ($fieldConstraint->constraints as $constr) {
$walker->walkConstraint($constr, $value[$field], $group, $propertyPath.'['.$field.']');
}
} elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) {

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @Annotation
@ -23,4 +24,13 @@ class Valid extends Constraint
public $traverse = true;
public $deep = false;
public function __construct($options = null)
{
if (is_array($options) && array_key_exists('groups', $options)) {
throw new ConstraintDefinitionException('The option "groups" is not supported by the constraint ' . __CLASS__);
}
parent::__construct($options);
}
}

View File

@ -0,0 +1,41 @@
<?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\Allator\Tests\Constraints;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\Valid;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class AllTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectNonConstraints()
{
new All(array(
'foo',
));
}
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectValidConstraint()
{
new All(array(
new Valid(),
));
}
}

View File

@ -0,0 +1,73 @@
<?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\Collectionator\Tests\Constraints;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\Collection\Required;
use Symfony\Component\Validator\Constraints\Collection\Optional;
use Symfony\Component\Validator\Constraints\Valid;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CollectionTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectInvalidFieldsOption()
{
new Collection(array(
'fields' => 'foo',
));
}
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectNonConstraints()
{
new Collection(array(
'foo' => 'bar',
));
}
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectValidConstraint()
{
new Collection(array(
'foo' => new Valid(),
));
}
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectValidConstraintWithinOptional()
{
new Collection(array(
'foo' => new Optional(new Valid()),
));
}
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectValidConstraintWithinRequired()
{
new Collection(array(
'foo' => new Required(new Valid()),
));
}
}

View File

@ -0,0 +1,28 @@
<?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\Tests\Constraints;
use Symfony\Component\Validator\Constraints\Valid;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValidTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException Symfony\Component\Validator\Exception\ConstraintDefinitionException
*/
public function testRejectGroupsOption()
{
new Valid(array('groups' => 'foo'));
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadata;
class ValidatorTest extends \PHPUnit_Framework_TestCase
@ -200,6 +201,18 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($violations, $this->validator->validateValue('Bernhard', new FailingConstraint()));
}
/**
* @expectedException Symfony\Component\Validator\Exception\ValidatorException
*/
public function testValidateValueRejectsValid()
{
$entity = new Entity();
$metadata = new ClassMetadata(get_class($entity));
$this->factory->addClassMetadata($metadata);
$this->validator->validateValue($entity, new Valid());
}
public function testGetMetadataFactory()
{
$this->assertInstanceOf(

View File

@ -11,7 +11,9 @@
namespace Symfony\Component\Validator;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
use Symfony\Component\Validator\Exception\ValidatorException;
/**
* The default implementation of the ValidatorInterface.
@ -104,6 +106,22 @@ class Validator implements ValidatorInterface
*/
public function validateValue($value, Constraint $constraint, $groups = null)
{
if ($constraint instanceof Valid) {
// Why can't the Valid constraint be executed directly?
//
// It cannot be executed like regular other constraints, because regular
// constraints are only executed *if they belong to the validated group*.
// The Valid constraint, on the other hand, is always executed and propagates
// the group to the cascaded object. The propagated group depends on
//
// * Whether a group sequence is currently being executed. Then the default
// group is propagated.
//
// * Otherwise the validated group is propagated.
throw new ValidatorException('The constraint ' . get_class($constraint) . ' cannot be validated. Use the method validate() instead.');
}
$walk = function(GraphWalker $walker, $group) use ($constraint, $value) {
return $walker->walkConstraint($constraint, $value, $group, '');
};