propagate the object being validated to sub-constraints

This commit is contained in:
Christian Flothmann 2021-01-04 22:11:50 +01:00
parent 6928ddec29
commit 8c1bac90aa
3 changed files with 61 additions and 22 deletions

View File

@ -34,9 +34,11 @@ class AtLeastOneOfValidator extends ConstraintValidator
$messages = [$constraint->message];
foreach ($constraint->constraints as $key => $item) {
$violations = $validator->validate($value, $item, $this->context->getGroup());
$executionContext = clone $this->context;
$executionContext->setNode($value, $this->context->getObject(), $this->context->getMetadata(), $this->context->getPropertyPath());
$violations = $validator->inContext($executionContext)->validate($value, $item, $this->context->getGroup())->getViolations();
if (0 === \count($violations)) {
if (\count($this->context->getViolations()) === \count($violations)) {
return;
}

View File

@ -364,4 +364,9 @@ class ExecutionContext implements ExecutionContextInterface
return $this->cachedObjectsRefs[$object];
}
public function __clone()
{
$this->violations = clone $this->violations;
}
}

View File

@ -18,15 +18,22 @@ use Symfony\Component\Validator\Constraints\Count;
use Symfony\Component\Validator\Constraints\Country;
use Symfony\Component\Validator\Constraints\DivisibleBy;
use Symfony\Component\Validator\Constraints\EqualTo;
use Symfony\Component\Validator\Constraints\Expression;
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
use Symfony\Component\Validator\Constraints\IdenticalTo;
use Symfony\Component\Validator\Constraints\Language;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\LessThan;
use Symfony\Component\Validator\Constraints\Negative;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\Unique;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
use Symfony\Component\Validator\Validation;
@ -45,15 +52,7 @@ class AtLeastOneOfValidatorTest extends ConstraintValidatorTestCase
*/
public function testValidCombinations($value, $constraints)
{
$i = 0;
foreach ($constraints as $constraint) {
$this->expectViolationsAt($i++, $value, $constraint);
}
$this->validator->validate($value, new AtLeastOneOf($constraints));
$this->assertNoViolation();
$this->assertCount(0, Validation::createValidator()->validate($value, new AtLeastOneOf($constraints)));
}
public function getValidCombinations()
@ -96,18 +95,20 @@ class AtLeastOneOfValidatorTest extends ConstraintValidatorTestCase
public function testInvalidCombinationsWithDefaultMessage($value, $constraints)
{
$atLeastOneOf = new AtLeastOneOf(['constraints' => $constraints]);
$validator = Validation::createValidator();
$message = [$atLeastOneOf->message];
$i = 0;
foreach ($constraints as $constraint) {
$message[] = ' ['.($i + 1).'] '.$this->expectViolationsAt($i++, $value, $constraint)->get(0)->getMessage();
$message[] = sprintf(' [%d] %s', ++$i, $validator->validate($value, $constraint)->get(0)->getMessage());
}
$this->validator->validate($value, $atLeastOneOf);
$violations = $validator->validate($value, $atLeastOneOf);
$this->buildViolation(implode('', $message))->setCode(AtLeastOneOf::AT_LEAST_ONE_OF_ERROR)->assertRaised();
$this->assertCount(1, $violations, sprintf('1 violation expected. Got %u.', \count($violations)));
$this->assertEquals(new ConstraintViolation(implode('', $message), implode('', $message), [], $value, '', $value, null, AtLeastOneOf::AT_LEAST_ONE_OF_ERROR, $atLeastOneOf), $violations->get(0));
}
/**
@ -117,15 +118,10 @@ class AtLeastOneOfValidatorTest extends ConstraintValidatorTestCase
{
$atLeastOneOf = new AtLeastOneOf(['constraints' => $constraints, 'message' => 'foo', 'includeInternalMessages' => false]);
$i = 0;
$violations = Validation::createValidator()->validate($value, $atLeastOneOf);
foreach ($constraints as $constraint) {
$this->expectViolationsAt($i++, $value, $constraint);
}
$this->validator->validate($value, $atLeastOneOf);
$this->buildViolation('foo')->setCode(AtLeastOneOf::AT_LEAST_ONE_OF_ERROR)->assertRaised();
$this->assertCount(1, $violations, sprintf('1 violation expected. Got %u.', \count($violations)));
$this->assertEquals(new ConstraintViolation('foo', 'foo', [], $value, '', $value, null, AtLeastOneOf::AT_LEAST_ONE_OF_ERROR, $atLeastOneOf), $violations->get(0));
}
public function getInvalidCombinations()
@ -184,4 +180,40 @@ class AtLeastOneOfValidatorTest extends ConstraintValidatorTestCase
$this->assertCount(1, $violations);
}
public function testContextIsPropagatedToNestedConstraints()
{
$validator = Validation::createValidatorBuilder()
->setMetadataFactory(new class() implements MetadataFactoryInterface {
public function getMetadataFor($classOrObject): MetadataInterface
{
return (new ClassMetadata(ExpressionConstraintNested::class))
->addPropertyConstraint('foo', new AtLeastOneOf([
new NotNull(),
new Expression('this.getFoobar() in ["bar", "baz"]'),
]));
}
public function hasMetadataFor($classOrObject): bool
{
return ExpressionConstraintNested::class === $classOrObject;
}
})
->getValidator()
;
$violations = $validator->validate(new ExpressionConstraintNested(), new Valid());
$this->assertCount(0, $violations);
}
}
class ExpressionConstraintNested
{
private $foo;
public function getFoobar(): string
{
return 'bar';
}
}