[Validator] Add target guards for Composite nested constraints

This commit is contained in:
Maxime Steinhausser 2020-02-21 15:30:21 +01:00
parent a8b5b15c25
commit a08ddf7636
5 changed files with 126 additions and 6 deletions

View File

@ -136,6 +136,17 @@ abstract class Composite extends Constraint
*/
abstract protected function getCompositeOption();
/**
* @internal Used by metadata
*
* @return Constraint[]
*/
public function getNestedContraints()
{
/* @var Constraint[] $nestedConstraints */
return $this->{$this->getCompositeOption()};
}
/**
* Initializes the nested constraints.
*

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@ -178,9 +179,7 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
*/
public function addConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
}
$this->checkConstraint($constraint);
if ($constraint instanceof Traverse) {
if ($constraint->traverse) {
@ -495,4 +494,17 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
$this->members[$property][] = $metadata;
}
private function checkConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets(), true)) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
}
if ($constraint instanceof Composite) {
foreach ($constraint->getNestedContraints() as $nestedContraint) {
$this->checkConstraint($nestedContraint);
}
}
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
@ -71,9 +72,7 @@ abstract class MemberMetadata extends GenericMetadata implements PropertyMetadat
*/
public function addConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets())) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on properties or getters.', \get_class($constraint)));
}
$this->checkConstraint($constraint);
parent::addConstraint($constraint);
@ -181,4 +180,17 @@ abstract class MemberMetadata extends GenericMetadata implements PropertyMetadat
* @return \ReflectionMethod|\ReflectionProperty The reflection instance
*/
abstract protected function newReflectionMember($objectOrClassName);
private function checkConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets(), true)) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on properties or getters.', \get_class($constraint)));
}
if ($constraint instanceof Composite) {
foreach ($constraint->getNestedContraints() as $nestedContraint) {
$this->checkConstraint($nestedContraint);
}
}
}
}

View File

@ -13,8 +13,11 @@ namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
@ -52,6 +55,20 @@ class ClassMetadataTest extends TestCase
$this->metadata->addConstraint(new PropertyConstraint());
}
public function testAddCompositeConstraintRejectsNestedPropertyConstraints()
{
$this->expectException(ConstraintDefinitionException::class);
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint" cannot be put on classes.');
$this->metadata->addConstraint(new ClassCompositeConstraint([new PropertyConstraint()]));
}
public function testAddCompositeConstraintAcceptsNestedClassConstraints()
{
$this->metadata->addConstraint($constraint = new ClassCompositeConstraint([new ClassConstraint()]));
$this->assertSame($this->metadata->getConstraints(), [$constraint]);
}
public function testAddPropertyConstraints()
{
$this->metadata->addPropertyConstraint('firstName', new ConstraintA());
@ -311,3 +328,23 @@ class ClassMetadataTest extends TestCase
$this->assertCount(0, $this->metadata->getPropertyMetadata('foo'), '->getPropertyMetadata() returns an empty collection if no metadata is configured for the given property');
}
}
class ClassCompositeConstraint extends Composite
{
public $nested;
public function getDefaultOption()
{
return $this->getCompositeOption();
}
protected function getCompositeOption()
{
return 'nested';
}
public function getTargets()
{
return [self::CLASS_CONSTRAINT];
}
}

View File

@ -12,11 +12,16 @@
namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\Required;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\MemberMetadata;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
class MemberMetadataTest extends TestCase
{
@ -43,6 +48,34 @@ class MemberMetadataTest extends TestCase
$this->metadata->addConstraint(new ClassConstraint());
}
public function testAddCompositeConstraintRejectsNestedClassConstraints()
{
$this->expectException(ConstraintDefinitionException::class);
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Tests\Fixtures\ClassConstraint" cannot be put on properties or getters.');
$this->metadata->addConstraint(new PropertyCompositeConstraint([new ClassConstraint()]));
}
public function testAddCompositeConstraintRejectsDeepNestedClassConstraints()
{
$this->expectException(ConstraintDefinitionException::class);
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Tests\Fixtures\ClassConstraint" cannot be put on properties or getters.');
$this->metadata->addConstraint(new Collection(['field1' => new Required([new ClassConstraint()])]));
}
public function testAddCompositeConstraintAcceptsNestedPropertyConstraints()
{
$this->metadata->addConstraint($constraint = new PropertyCompositeConstraint([new PropertyConstraint()]));
$this->assertSame($this->metadata->getConstraints(), [$constraint]);
}
public function testAddCompositeConstraintAcceptsDeepNestedPropertyConstraints()
{
$this->metadata->addConstraint($constraint = new Collection(['field1' => new Required([new PropertyConstraint()])]));
$this->assertSame($this->metadata->getConstraints(), [$constraint]);
}
public function testSerialize()
{
$this->metadata->addConstraint(new ConstraintA(['property1' => 'A']));
@ -82,3 +115,18 @@ class TestMemberMetadata extends MemberMetadata
{
}
}
class PropertyCompositeConstraint extends Composite
{
public $nested;
public function getDefaultOption()
{
return $this->getCompositeOption();
}
protected function getCompositeOption()
{
return 'nested';
}
}