[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(); abstract protected function getCompositeOption();
/**
* @internal Used by metadata
*
* @return Constraint[]
*/
public function getNestedContraints()
{
/* @var Constraint[] $nestedConstraints */
return $this->{$this->getCompositeOption()};
}
/** /**
* Initializes the nested constraints. * Initializes the nested constraints.
* *

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Mapping; namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@ -178,9 +179,7 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
*/ */
public function addConstraint(Constraint $constraint) public function addConstraint(Constraint $constraint)
{ {
if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) { $this->checkConstraint($constraint);
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
}
if ($constraint instanceof Traverse) { if ($constraint instanceof Traverse) {
if ($constraint->traverse) { if ($constraint->traverse) {
@ -495,4 +494,17 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
$this->members[$property][] = $metadata; $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; namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/** /**
@ -71,9 +72,7 @@ abstract class MemberMetadata extends GenericMetadata implements PropertyMetadat
*/ */
public function addConstraint(Constraint $constraint) public function addConstraint(Constraint $constraint)
{ {
if (!\in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets())) { $this->checkConstraint($constraint);
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on properties or getters.', \get_class($constraint)));
}
parent::addConstraint($constraint); parent::addConstraint($constraint);
@ -181,4 +180,17 @@ abstract class MemberMetadata extends GenericMetadata implements PropertyMetadat
* @return \ReflectionMethod|\ReflectionProperty The reflection instance * @return \ReflectionMethod|\ReflectionProperty The reflection instance
*/ */
abstract protected function newReflectionMember($objectOrClassName); 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 PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\ClassMetadata; 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\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint; use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
@ -52,6 +55,20 @@ class ClassMetadataTest extends TestCase
$this->metadata->addConstraint(new PropertyConstraint()); $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() public function testAddPropertyConstraints()
{ {
$this->metadata->addPropertyConstraint('firstName', new ConstraintA()); $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'); $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; namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase; 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\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\MemberMetadata; use Symfony\Component\Validator\Mapping\MemberMetadata;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint; use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
class MemberMetadataTest extends TestCase class MemberMetadataTest extends TestCase
{ {
@ -43,6 +48,34 @@ class MemberMetadataTest extends TestCase
$this->metadata->addConstraint(new ClassConstraint()); $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() public function testSerialize()
{ {
$this->metadata->addConstraint(new ConstraintA(['property1' => 'A'])); $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';
}
}