diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php deleted file mode 100644 index 06f7e85775..0000000000 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractTest.php +++ /dev/null @@ -1,722 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Tests\Validator; - -use Symfony\Component\Validator\Constraints\Callback; -use Symfony\Component\Validator\Constraints\Collection; -use Symfony\Component\Validator\Constraints\Expression; -use Symfony\Component\Validator\Constraints\GroupSequence; -use Symfony\Component\Validator\Constraints\NotBlank; -use Symfony\Component\Validator\Constraints\NotNull; -use Symfony\Component\Validator\Constraints\Traverse; -use Symfony\Component\Validator\Constraints\Valid; -use Symfony\Component\Validator\ConstraintViolationInterface; -use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Component\Validator\Mapping\ClassMetadata; -use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; -use Symfony\Component\Validator\Tests\Fixtures\Entity; -use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint; -use Symfony\Component\Validator\Tests\Fixtures\Reference; -use Symfony\Component\Validator\Validator\ValidatorInterface; - -/** - * @author Bernhard Schussek - */ -abstract class AbstractTest extends AbstractValidatorTest -{ - /** - * @var ValidatorInterface - */ - protected $validator; - - abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = []): ValidatorInterface; - - protected function setUp(): void - { - parent::setUp(); - - $this->validator = $this->createValidator($this->metadataFactory); - } - - protected function validate($value, $constraints = null, $groups = null) - { - return $this->validator->validate($value, $constraints, $groups); - } - - protected function validateProperty($object, $propertyName, $groups = null) - { - return $this->validator->validateProperty($object, $propertyName, $groups); - } - - protected function validatePropertyValue($object, $propertyName, $value, $groups = null) - { - return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups); - } - - public function testValidateConstraintWithoutGroup() - { - $violations = $this->validator->validate(null, new NotNull()); - - $this->assertCount(1, $violations); - } - - public function testValidateWithEmptyArrayAsConstraint() - { - $violations = $this->validator->validate('value', []); - $this->assertCount(0, $violations); - } - - public function testGroupSequenceAbortsAfterFailedGroup() - { - $entity = new Entity(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message 1'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message 2'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => function () {}, - 'groups' => 'Group 1', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group 2', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 3', - ])); - - $sequence = new GroupSequence(['Group 1', 'Group 2', 'Group 3']); - $violations = $this->validator->validate($entity, new Valid(), $sequence); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message 1', $violations[0]->getMessage()); - } - - public function testGroupSequenceIncludesReferences() - { - $entity = new Entity(); - $entity->reference = new Reference(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Reference violation 1'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Reference violation 2'); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group 1', - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 2', - ])); - - $sequence = new GroupSequence(['Group 1', 'Entity']); - $violations = $this->validator->validate($entity, new Valid(), $sequence); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Reference violation 1', $violations[0]->getMessage()); - } - - public function testValidateInSeparateContext() - { - $entity = new Entity(); - $entity->reference = new Reference(); - - $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) { - $violations = $context - ->getValidator() - // Since the validator is not context aware, the group must - // be passed explicitly - ->validate($value->reference, new Valid(), 'Group') - ; - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('', $violations[0]->getPropertyPath()); - - // The root is different as we're in a new context - $this->assertSame($entity->reference, $violations[0]->getRoot()); - $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - - // Verify that this method is called - $context->addViolation('Separate violation'); - }; - - $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity->reference, $context->getRoot()); - $this->assertSame($entity->reference, $context->getValue()); - $this->assertSame($entity->reference, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group', - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group', - ])); - - $violations = $this->validator->validate($entity, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Separate violation', $violations[0]->getMessage()); - } - - public function testValidateInContext() - { - $entity = new Entity(); - $entity->reference = new Reference(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $previousValue = $context->getValue(); - $previousObject = $context->getObject(); - $previousMetadata = $context->getMetadata(); - $previousPath = $context->getPropertyPath(); - $previousGroup = $context->getGroup(); - - $context - ->getValidator() - ->inContext($context) - ->atPath('subpath') - ->validate($value->reference) - ; - - // context changes shouldn't leak out of the validate() call - $this->assertSame($previousValue, $context->getValue()); - $this->assertSame($previousObject, $context->getObject()); - $this->assertSame($previousMetadata, $context->getMetadata()); - $this->assertSame($previousPath, $context->getPropertyPath()); - $this->assertSame($previousGroup, $context->getGroup()); - }; - - $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('subpath', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference, $context->getValue()); - $this->assertSame($entity->reference, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group', - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group', - ])); - - $violations = $this->validator->validate($entity, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('subpath', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testValidateArrayInContext() - { - $entity = new Entity(); - $entity->reference = new Reference(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $previousValue = $context->getValue(); - $previousObject = $context->getObject(); - $previousMetadata = $context->getMetadata(); - $previousPath = $context->getPropertyPath(); - $previousGroup = $context->getGroup(); - - $context - ->getValidator() - ->inContext($context) - ->atPath('subpath') - ->validate(['key' => $value->reference]) - ; - - // context changes shouldn't leak out of the validate() call - $this->assertSame($previousValue, $context->getValue()); - $this->assertSame($previousObject, $context->getObject()); - $this->assertSame($previousMetadata, $context->getMetadata()); - $this->assertSame($previousPath, $context->getPropertyPath()); - $this->assertSame($previousGroup, $context->getGroup()); - }; - - $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('subpath[key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference, $context->getValue()); - $this->assertSame($entity->reference, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group', - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group', - ])); - - $violations = $this->validator->validate($entity, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('subpath[key]', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testTraverseTraversableByDefault() - { - $entity = new Entity(); - $traversable = new \ArrayIterator(['key' => $entity]); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) { - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('[key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->metadata, $context->getMetadata()); - $this->assertSame($traversable, $context->getRoot()); - $this->assertSame($entity, $context->getValue()); - $this->assertSame($entity, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator')); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($traversable, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('[key]', $violations[0]->getPropertyPath()); - $this->assertSame($traversable, $violations[0]->getRoot()); - $this->assertSame($entity, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testTraversalEnabledOnClass() - { - $entity = new Entity(); - $traversable = new \ArrayIterator(['key' => $entity]); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $traversableMetadata = new ClassMetadata('ArrayIterator'); - $traversableMetadata->addConstraint(new Traverse(true)); - - $this->metadataFactory->addMetadata($traversableMetadata); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($traversable, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - public function testTraversalDisabledOnClass() - { - $entity = new Entity(); - $traversable = new \ArrayIterator(['key' => $entity]); - - $callback = function ($value, ExecutionContextInterface $context) { - $this->fail('Should not be called'); - }; - - $traversableMetadata = new ClassMetadata('ArrayIterator'); - $traversableMetadata->addConstraint(new Traverse(false)); - - $this->metadataFactory->addMetadata($traversableMetadata); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($traversable, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testExpectTraversableIfTraversalEnabledOnClass() - { - $this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); - $entity = new Entity(); - - $this->metadata->addConstraint(new Traverse(true)); - - $this->validator->validate($entity); - } - - public function testReferenceTraversalDisabledOnClass() - { - $entity = new Entity(); - $entity->reference = new \ArrayIterator(['key' => new Reference()]); - - $callback = function ($value, ExecutionContextInterface $context) { - $this->fail('Should not be called'); - }; - - $traversableMetadata = new ClassMetadata('ArrayIterator'); - $traversableMetadata->addConstraint(new Traverse(false)); - - $this->metadataFactory->addMetadata($traversableMetadata); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - $this->metadata->addPropertyConstraint('reference', new Valid()); - - $violations = $this->validate($entity, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testReferenceTraversalEnabledOnReferenceDisabledOnClass() - { - $entity = new Entity(); - $entity->reference = new \ArrayIterator(['key' => new Reference()]); - - $callback = function ($value, ExecutionContextInterface $context) { - $this->fail('Should not be called'); - }; - - $traversableMetadata = new ClassMetadata('ArrayIterator'); - $traversableMetadata->addConstraint(new Traverse(false)); - - $this->metadataFactory->addMetadata($traversableMetadata); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - $this->metadata->addPropertyConstraint('reference', new Valid([ - 'traverse' => true, - ])); - - $violations = $this->validate($entity, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testReferenceTraversalDisabledOnReferenceEnabledOnClass() - { - $entity = new Entity(); - $entity->reference = new \ArrayIterator(['key' => new Reference()]); - - $callback = function ($value, ExecutionContextInterface $context) { - $this->fail('Should not be called'); - }; - - $traversableMetadata = new ClassMetadata('ArrayIterator'); - $traversableMetadata->addConstraint(new Traverse(true)); - - $this->metadataFactory->addMetadata($traversableMetadata); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - $this->metadata->addPropertyConstraint('reference', new Valid([ - 'traverse' => false, - ])); - - $violations = $this->validate($entity, new Valid(), 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testAddCustomizedViolation() - { - $entity = new Entity(); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->buildViolation('Message %param%') - ->setParameter('%param%', 'value') - ->setInvalidValue('Invalid value') - ->setPlural(2) - ->setCode('42') - ->addViolation(); - }; - - $this->metadata->addConstraint(new Callback($callback)); - - $violations = $this->validator->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Invalid value', $violations[0]->getInvalidValue()); - $this->assertSame(2, $violations[0]->getPlural()); - $this->assertSame('42', $violations[0]->getCode()); - } - - public function testNoDuplicateValidationIfClassConstraintInMultipleGroups() - { - $entity = new Entity(); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => ['Group 1', 'Group 2'], - ])); - - $violations = $this->validator->validate($entity, new Valid(), ['Group 1', 'Group 2']); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - public function testNoDuplicateValidationIfPropertyConstraintInMultipleGroups() - { - $entity = new Entity(); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $this->metadata->addPropertyConstraint('firstName', new Callback([ - 'callback' => $callback, - 'groups' => ['Group 1', 'Group 2'], - ])); - - $violations = $this->validator->validate($entity, new Valid(), ['Group 1', 'Group 2']); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - public function testValidateFailsIfNoConstraintsAndNoObjectOrArray() - { - $this->expectException('Symfony\Component\Validator\Exception\RuntimeException'); - $this->validate('Foobar'); - } - - public function testAccessCurrentObject() - { - $called = false; - $entity = new Entity(); - $entity->firstName = 'Bernhard'; - $entity->data = ['firstName' => 'Bernhard']; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity, &$called) { - $called = true; - $this->assertSame($entity, $context->getObject()); - }; - - $this->metadata->addConstraint(new Callback($callback)); - $this->metadata->addPropertyConstraint('firstName', new Callback($callback)); - $this->metadata->addPropertyConstraint('data', new Collection(['firstName' => new Expression('value == this.firstName')])); - - $this->validator->validate($entity); - - $this->assertTrue($called); - } - - public function testInitializeObjectsOnFirstValidation() - { - $entity = new Entity(); - $entity->initialized = false; - - // prepare initializers that set "initialized" to true - $initializer1 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); - $initializer2 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); - - $initializer1->expects($this->once()) - ->method('initialize') - ->with($entity) - ->willReturnCallback(function ($object) { - $object->initialized = true; - }); - - $initializer2->expects($this->once()) - ->method('initialize') - ->with($entity); - - $this->validator = $this->createValidator($this->metadataFactory, [ - $initializer1, - $initializer2, - ]); - - // prepare constraint which - // * checks that "initialized" is set to true - // * validates the object again - $callback = function ($object, ExecutionContextInterface $context) { - $this->assertTrue($object->initialized); - - // validate again in same group - $validator = $context->getValidator()->inContext($context); - - $validator->validate($object); - - // validate again in other group - $validator->validate($object, null, 'SomeGroup'); - }; - - $this->metadata->addConstraint(new Callback($callback)); - - $this->validate($entity); - - $this->assertTrue($entity->initialized); - } - - public function testPassConstraintToViolation() - { - $constraint = new FailingConstraint(); - $violations = $this->validate('Foobar', $constraint); - - $this->assertCount(1, $violations); - $this->assertSame($constraint, $violations[0]->getConstraint()); - } - - public function testCollectionConstraitViolationHasCorrectContext() - { - $data = [ - 'foo' => 'fooValue', - ]; - - // Missing field must not be the first in the collection validation - $constraint = new Collection([ - 'foo' => new NotNull(), - 'bar' => new NotNull(), - ]); - - $violations = $this->validate($data, $constraint); - - $this->assertCount(1, $violations); - $this->assertSame($constraint, $violations[0]->getConstraint()); - } - - public function testNestedObjectIsNotValidatedIfGroupInValidConstraintIsNotValidated() - { - $entity = new Entity(); - $entity->firstName = ''; - $reference = new Reference(); - $reference->value = ''; - $entity->childA = $reference; - - $this->metadata->addPropertyConstraint('firstName', new NotBlank(['groups' => 'group1'])); - $this->metadata->addPropertyConstraint('childA', new Valid(['groups' => 'group1'])); - $this->referenceMetadata->addPropertyConstraint('value', new NotBlank()); - - $violations = $this->validator->validate($entity, null, []); - - $this->assertCount(0, $violations); - } - - public function testNestedObjectIsValidatedIfGroupInValidConstraintIsValidated() - { - $entity = new Entity(); - $entity->firstName = ''; - $reference = new Reference(); - $reference->value = ''; - $entity->childA = $reference; - - $this->metadata->addPropertyConstraint('firstName', new NotBlank(['groups' => 'group1'])); - $this->metadata->addPropertyConstraint('childA', new Valid(['groups' => 'group1'])); - $this->referenceMetadata->addPropertyConstraint('value', new NotBlank(['groups' => 'group1'])); - - $violations = $this->validator->validate($entity, null, ['Default', 'group1']); - - $this->assertCount(2, $violations); - } - - public function testNestedObjectIsValidatedInMultipleGroupsIfGroupInValidConstraintIsValidated() - { - $entity = new Entity(); - $entity->firstName = null; - - $reference = new Reference(); - $reference->value = null; - - $entity->childA = $reference; - - $this->metadata->addPropertyConstraint('firstName', new NotBlank()); - $this->metadata->addPropertyConstraint('childA', new Valid(['groups' => ['group1', 'group2']])); - - $this->referenceMetadata->addPropertyConstraint('value', new NotBlank(['groups' => 'group1'])); - $this->referenceMetadata->addPropertyConstraint('value', new NotNull(['groups' => 'group2'])); - - $violations = $this->validator->validate($entity, null, ['Default', 'group1', 'group2']); - - $this->assertCount(3, $violations); - } -} diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php deleted file mode 100644 index e9bad07096..0000000000 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php +++ /dev/null @@ -1,1281 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Tests\Validator; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Validator\Constraints\Callback; -use Symfony\Component\Validator\Constraints\GroupSequence; -use Symfony\Component\Validator\Constraints\Valid; -use Symfony\Component\Validator\ConstraintViolationInterface; -use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Component\Validator\Mapping\ClassMetadata; -use Symfony\Component\Validator\Tests\Fixtures\Entity; -use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory; -use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity; -use Symfony\Component\Validator\Tests\Fixtures\Reference; - -/** - * @author Bernhard Schussek - */ -abstract class AbstractValidatorTest extends TestCase -{ - const ENTITY_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; - - const REFERENCE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Reference'; - - const LAZY_PROPERTY = 'Symfony\Component\Validator\Validator\LazyProperty'; - - /** - * @var FakeMetadataFactory - */ - public $metadataFactory; - - /** - * @var ClassMetadata - */ - public $metadata; - - /** - * @var ClassMetadata - */ - public $referenceMetadata; - - protected function setUp(): void - { - $this->metadataFactory = new FakeMetadataFactory(); - $this->metadata = new ClassMetadata(self::ENTITY_CLASS); - $this->referenceMetadata = new ClassMetadata(self::REFERENCE_CLASS); - $this->metadataFactory->addMetadata($this->metadata); - $this->metadataFactory->addMetadata($this->referenceMetadata); - $this->metadataFactory->addMetadata(new ClassMetadata(self::LAZY_PROPERTY)); - } - - protected function tearDown(): void - { - $this->metadataFactory = null; - $this->metadata = null; - $this->referenceMetadata = null; - } - - abstract protected function validate($value, $constraints = null, $groups = null); - - abstract protected function validateProperty($object, $propertyName, $groups = null); - - abstract protected function validatePropertyValue($object, $propertyName, $value, $groups = null); - - public function testValidate() - { - $callback = function ($value, ExecutionContextInterface $context) { - $this->assertNull($context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame('Bernhard', $context->getRoot()); - $this->assertSame('Bernhard', $context->getValue()); - $this->assertSame('Bernhard', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $constraint = new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ]); - - $violations = $this->validate('Bernhard', $constraint, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('', $violations[0]->getPropertyPath()); - $this->assertSame('Bernhard', $violations[0]->getRoot()); - $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testClassConstraint() - { - $entity = new Entity(); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->metadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity, $context->getValue()); - $this->assertSame($entity, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testPropertyConstraint() - { - $entity = new Entity(); - $entity->firstName = 'Bernhard'; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); - - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertSame('firstName', $context->getPropertyName()); - $this->assertSame('firstName', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame('Bernhard', $context->getValue()); - $this->assertSame('Bernhard', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('firstName', new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('firstName', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testGetterConstraint() - { - $entity = new Entity(); - $entity->setLastName('Schussek'); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $propertyMetadatas = $this->metadata->getPropertyMetadata('lastName'); - - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertSame('lastName', $context->getPropertyName()); - $this->assertSame('lastName', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame('Schussek', $context->getValue()); - $this->assertSame('Schussek', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addGetterConstraint('lastName', new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('lastName', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Schussek', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testArray() - { - $entity = new Entity(); - $array = ['key' => $entity]; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity, $array) { - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('[key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->metadata, $context->getMetadata()); - $this->assertSame($array, $context->getRoot()); - $this->assertSame($entity, $context->getValue()); - $this->assertSame($entity, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($array, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('[key]', $violations[0]->getPropertyPath()); - $this->assertSame($array, $violations[0]->getRoot()); - $this->assertSame($entity, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testRecursiveArray() - { - $entity = new Entity(); - $array = [2 => ['key' => $entity]]; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity, $array) { - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('[2][key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->metadata, $context->getMetadata()); - $this->assertSame($array, $context->getRoot()); - $this->assertSame($entity, $context->getValue()); - $this->assertSame($entity, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($array, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('[2][key]', $violations[0]->getPropertyPath()); - $this->assertSame($array, $violations[0]->getRoot()); - $this->assertSame($entity, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testTraversable() - { - $entity = new Entity(); - $traversable = new \ArrayIterator(['key' => $entity]); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) { - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('[key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->metadata, $context->getMetadata()); - $this->assertSame($traversable, $context->getRoot()); - $this->assertSame($entity, $context->getValue()); - $this->assertSame($entity, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($traversable, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('[key]', $violations[0]->getPropertyPath()); - $this->assertSame($traversable, $violations[0]->getRoot()); - $this->assertSame($entity, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testRecursiveTraversable() - { - $entity = new Entity(); - $traversable = new \ArrayIterator([ - 2 => new \ArrayIterator(['key' => $entity]), - ]); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) { - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('[2][key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->metadata, $context->getMetadata()); - $this->assertSame($traversable, $context->getRoot()); - $this->assertSame($entity, $context->getValue()); - $this->assertSame($entity, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($traversable, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('[2][key]', $violations[0]->getPropertyPath()); - $this->assertSame($traversable, $violations[0]->getRoot()); - $this->assertSame($entity, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testReferenceClassConstraint() - { - $entity = new Entity(); - $entity->reference = new Reference(); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('reference', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference, $context->getValue()); - $this->assertSame($entity->reference, $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testReferencePropertyConstraint() - { - $entity = new Entity(); - $entity->reference = new Reference(); - $entity->reference->value = 'Foobar'; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $propertyMetadatas = $this->referenceMetadata->getPropertyMetadata('value'); - - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertSame('value', $context->getPropertyName()); - $this->assertSame('reference.value', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame('Foobar', $context->getValue()); - $this->assertSame('Foobar', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->referenceMetadata->addPropertyConstraint('value', new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference.value', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Foobar', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testReferenceGetterConstraint() - { - $entity = new Entity(); - $entity->reference = new Reference(); - $entity->reference->setPrivateValue('Bamboo'); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $propertyMetadatas = $this->referenceMetadata->getPropertyMetadata('privateValue'); - - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertSame('privateValue', $context->getPropertyName()); - $this->assertSame('reference.privateValue', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame('Bamboo', $context->getValue()); - $this->assertSame('Bamboo', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->referenceMetadata->addPropertyConstraint('privateValue', new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference.privateValue', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Bamboo', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testsIgnoreNullReference() - { - $entity = new Entity(); - $entity->reference = null; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testFailOnScalarReferences() - { - $this->expectException('Symfony\Component\Validator\Exception\NoSuchMetadataException'); - $entity = new Entity(); - $entity->reference = 'string'; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - - $this->validate($entity); - } - - /** - * @dataProvider getConstraintMethods - */ - public function testArrayReference($constraintMethod) - { - $entity = new Entity(); - $entity->reference = ['key' => new Reference()]; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('reference[key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference['key'], $context->getValue()); - $this->assertSame($entity->reference['key'], $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->$constraintMethod('reference', new Valid()); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference[key]', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - /** - * @dataProvider getConstraintMethods - */ - public function testRecursiveArrayReference($constraintMethod) - { - $entity = new Entity(); - $entity->reference = [2 => ['key' => new Reference()]]; - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('reference[2][key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference[2]['key'], $context->getValue()); - $this->assertSame($entity->reference[2]['key'], $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->$constraintMethod('reference', new Valid()); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testOnlyCascadedArraysAreTraversed() - { - $entity = new Entity(); - $entity->reference = ['key' => new Reference()]; - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('reference', new Callback([ - 'callback' => function () {}, - 'groups' => 'Group', - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - /** - * @dataProvider getConstraintMethods - */ - public function testArrayTraversalCannotBeDisabled($constraintMethod) - { - $entity = new Entity(); - $entity->reference = ['key' => new Reference()]; - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->$constraintMethod('reference', new Valid([ - 'traverse' => false, - ])); - $this->referenceMetadata->addConstraint(new Callback($callback)); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - /** - * @dataProvider getConstraintMethods - */ - public function testRecursiveArrayTraversalCannotBeDisabled($constraintMethod) - { - $entity = new Entity(); - $entity->reference = [2 => ['key' => new Reference()]]; - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->$constraintMethod('reference', new Valid([ - 'traverse' => false, - ])); - - $this->referenceMetadata->addConstraint(new Callback($callback)); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - /** - * @dataProvider getConstraintMethods - */ - public function testIgnoreScalarsDuringArrayTraversal($constraintMethod) - { - $entity = new Entity(); - $entity->reference = ['string', 1234]; - - $this->metadata->$constraintMethod('reference', new Valid()); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - /** - * @dataProvider getConstraintMethods - */ - public function testIgnoreNullDuringArrayTraversal($constraintMethod) - { - $entity = new Entity(); - $entity->reference = [null]; - - $this->metadata->$constraintMethod('reference', new Valid()); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testTraversableReference() - { - $entity = new Entity(); - $entity->reference = new \ArrayIterator(['key' => new Reference()]); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('reference[key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference['key'], $context->getValue()); - $this->assertSame($entity->reference['key'], $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference[key]', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testDisableTraversableTraversal() - { - $entity = new Entity(); - $entity->reference = new \ArrayIterator(['key' => new Reference()]); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator')); - $this->metadata->addPropertyConstraint('reference', new Valid([ - 'traverse' => false, - ])); - $this->referenceMetadata->addConstraint(new Callback($callback)); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(0, $violations); - } - - public function testMetadataMustExistIfTraversalIsDisabled() - { - $this->expectException('Symfony\Component\Validator\Exception\NoSuchMetadataException'); - $entity = new Entity(); - $entity->reference = new \ArrayIterator(); - - $this->metadata->addPropertyConstraint('reference', new Valid([ - 'traverse' => false, - ])); - - $this->validate($entity); - } - - public function testEnableRecursiveTraversableTraversal() - { - $entity = new Entity(); - $entity->reference = new \ArrayIterator([ - 2 => new \ArrayIterator(['key' => new Reference()]), - ]); - - $callback = function ($value, ExecutionContextInterface $context) use ($entity) { - $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); - $this->assertNull($context->getPropertyName()); - $this->assertSame('reference[2][key]', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($this->referenceMetadata, $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame($entity->reference[2]['key'], $context->getValue()); - $this->assertSame($entity->reference[2]['key'], $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid([ - 'traverse' => true, - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group', - ])); - - $violations = $this->validate($entity, null, 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testValidateProperty() - { - $entity = new Entity(); - $entity->firstName = 'Bernhard'; - $entity->setLastName('Schussek'); - - $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) { - $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); - - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertSame('firstName', $context->getPropertyName()); - $this->assertSame('firstName', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame('Bernhard', $context->getValue()); - $this->assertSame('Bernhard', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Other violation'); - }; - - $this->metadata->addPropertyConstraint('firstName', new Callback([ - 'callback' => $callback1, - 'groups' => 'Group', - ])); - $this->metadata->addPropertyConstraint('lastName', new Callback([ - 'callback' => $callback2, - 'groups' => 'Group', - ])); - - $violations = $this->validateProperty($entity, 'firstName', 'Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('firstName', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - /** - * https://github.com/symfony/symfony/issues/11604. - */ - public function testValidatePropertyWithoutConstraints() - { - $entity = new Entity(); - $violations = $this->validateProperty($entity, 'lastName'); - - $this->assertCount(0, $violations, '->validateProperty() returns no violations if no constraints have been configured for the property being validated'); - } - - public function testValidatePropertyValue() - { - $entity = new Entity(); - $entity->setLastName('Schussek'); - - $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) { - $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); - - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertSame('firstName', $context->getPropertyName()); - $this->assertSame('firstName', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame($entity, $context->getRoot()); - $this->assertSame('Bernhard', $context->getValue()); - $this->assertSame('Bernhard', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Other violation'); - }; - - $this->metadata->addPropertyConstraint('firstName', new Callback([ - 'callback' => $callback1, - 'groups' => 'Group', - ])); - $this->metadata->addPropertyConstraint('lastName', new Callback([ - 'callback' => $callback2, - 'groups' => 'Group', - ])); - - $violations = $this->validatePropertyValue( - $entity, - 'firstName', - 'Bernhard', - 'Group' - ); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('firstName', $violations[0]->getPropertyPath()); - $this->assertSame($entity, $violations[0]->getRoot()); - $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - public function testValidatePropertyValueWithClassName() - { - $callback1 = function ($value, ExecutionContextInterface $context) { - $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); - - $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); - $this->assertSame('firstName', $context->getPropertyName()); - $this->assertSame('', $context->getPropertyPath()); - $this->assertSame('Group', $context->getGroup()); - $this->assertSame($propertyMetadatas[0], $context->getMetadata()); - $this->assertSame('Bernhard', $context->getRoot()); - $this->assertSame('Bernhard', $context->getValue()); - $this->assertSame('Bernhard', $value); - - $context->addViolation('Message %param%', ['%param%' => 'value']); - }; - - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Other violation'); - }; - - $this->metadata->addPropertyConstraint('firstName', new Callback([ - 'callback' => $callback1, - 'groups' => 'Group', - ])); - $this->metadata->addPropertyConstraint('lastName', new Callback([ - 'callback' => $callback2, - 'groups' => 'Group', - ])); - - $violations = $this->validatePropertyValue( - self::ENTITY_CLASS, - 'firstName', - 'Bernhard', - 'Group' - ); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Message value', $violations[0]->getMessage()); - $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); - $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); - $this->assertSame('', $violations[0]->getPropertyPath()); - $this->assertSame('Bernhard', $violations[0]->getRoot()); - $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); - $this->assertNull($violations[0]->getPlural()); - $this->assertNull($violations[0]->getCode()); - } - - /** - * https://github.com/symfony/symfony/issues/11604. - */ - public function testValidatePropertyValueWithoutConstraints() - { - $entity = new Entity(); - $violations = $this->validatePropertyValue($entity, 'lastName', 'foo'); - - $this->assertCount(0, $violations, '->validatePropertyValue() returns no violations if no constraints have been configured for the property being validated'); - } - - public function testValidateObjectOnlyOncePerGroup() - { - $entity = new Entity(); - $entity->reference = new Reference(); - $entity->reference2 = $entity->reference; - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->metadata->addPropertyConstraint('reference2', new Valid()); - $this->referenceMetadata->addConstraint(new Callback($callback)); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - public function testValidateDifferentObjectsSeparately() - { - $entity = new Entity(); - $entity->reference = new Reference(); - $entity->reference2 = new Reference(); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->metadata->addPropertyConstraint('reference2', new Valid()); - $this->referenceMetadata->addConstraint(new Callback($callback)); - - $violations = $this->validate($entity); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(2, $violations); - } - - public function testValidateSingleGroup() - { - $entity = new Entity(); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group 1', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group 2', - ])); - - $violations = $this->validate($entity, null, 'Group 2'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - } - - public function testValidateMultipleGroups() - { - $entity = new Entity(); - - $callback = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Message'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group 1', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback, - 'groups' => 'Group 2', - ])); - - $violations = $this->validate($entity, null, ['Group 1', 'Group 2']); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(2, $violations); - } - - public function testReplaceDefaultGroupByGroupSequenceObject() - { - $entity = new Entity(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Group 2'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Group 3'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => function () {}, - 'groups' => 'Group 1', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group 2', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 3', - ])); - - $sequence = new GroupSequence(['Group 1', 'Group 2', 'Group 3', 'Entity']); - $this->metadata->setGroupSequence($sequence); - - $violations = $this->validate($entity, null, 'Default'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Violation in Group 2', $violations[0]->getMessage()); - } - - public function testReplaceDefaultGroupByGroupSequenceArray() - { - $entity = new Entity(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Group 2'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Group 3'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => function () {}, - 'groups' => 'Group 1', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group 2', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 3', - ])); - - $sequence = ['Group 1', 'Group 2', 'Group 3', 'Entity']; - $this->metadata->setGroupSequence($sequence); - - $violations = $this->validate($entity, null, 'Default'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Violation in Group 2', $violations[0]->getMessage()); - } - - public function testPropagateDefaultGroupToReferenceWhenReplacingDefaultGroup() - { - $entity = new Entity(); - $entity->reference = new Reference(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Default group'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in group sequence'); - }; - - $this->metadata->addPropertyConstraint('reference', new Valid()); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Default', - ])); - $this->referenceMetadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 1', - ])); - - $sequence = new GroupSequence(['Group 1', 'Entity']); - $this->metadata->setGroupSequence($sequence); - - $violations = $this->validate($entity, null, 'Default'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Violation in Default group', $violations[0]->getMessage()); - } - - public function testValidateCustomGroupWhenDefaultGroupWasReplaced() - { - $entity = new Entity(); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in other group'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in group sequence'); - }; - - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Other Group', - ])); - $this->metadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 1', - ])); - - $sequence = new GroupSequence(['Group 1', 'Entity']); - $this->metadata->setGroupSequence($sequence); - - $violations = $this->validate($entity, null, 'Other Group'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(1, $violations); - $this->assertSame('Violation in other group', $violations[0]->getMessage()); - } - - /** - * @dataProvider getTestReplaceDefaultGroup - */ - public function testReplaceDefaultGroup($sequence, array $assertViolations) - { - $entity = new GroupSequenceProviderEntity($sequence); - - $callback1 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Group 2'); - }; - $callback2 = function ($value, ExecutionContextInterface $context) { - $context->addViolation('Violation in Group 3'); - }; - - $metadata = new ClassMetadata(\get_class($entity)); - $metadata->addConstraint(new Callback([ - 'callback' => function () {}, - 'groups' => 'Group 1', - ])); - $metadata->addConstraint(new Callback([ - 'callback' => $callback1, - 'groups' => 'Group 2', - ])); - $metadata->addConstraint(new Callback([ - 'callback' => $callback2, - 'groups' => 'Group 3', - ])); - $metadata->setGroupSequenceProvider(true); - - $this->metadataFactory->addMetadata($metadata); - - $violations = $this->validate($entity, null, 'Default'); - - /* @var ConstraintViolationInterface[] $violations */ - $this->assertCount(\count($assertViolations), $violations); - foreach ($assertViolations as $key => $message) { - $this->assertSame($message, $violations[$key]->getMessage()); - } - } - - public function getConstraintMethods() - { - return [ - ['addPropertyConstraint'], - ['addGetterConstraint'], - ]; - } - - public function getTestReplaceDefaultGroup() - { - return [ - [ - 'sequence' => new GroupSequence(['Group 1', 'Group 2', 'Group 3', 'Entity']), - 'assertViolations' => [ - 'Violation in Group 2', - ], - ], - [ - 'sequence' => ['Group 1', 'Group 2', 'Group 3', 'Entity'], - 'assertViolations' => [ - 'Violation in Group 2', - ], - ], - [ - 'sequence' => new GroupSequence(['Group 1', ['Group 2', 'Group 3'], 'Entity']), - 'assertViolations' => [ - 'Violation in Group 2', - 'Violation in Group 3', - ], - ], - [ - 'sequence' => ['Group 1', ['Group 2', 'Group 3'], 'Entity'], - 'assertViolations' => [ - 'Violation in Group 2', - 'Violation in Group 3', - ], - ], - ]; - } -} diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index 48de5820fc..1af4c9acfa 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -11,9 +11,12 @@ namespace Symfony\Component\Validator\Tests\Validator; +use PHPUnit\Framework\TestCase; use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Expression; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; @@ -21,9 +24,12 @@ use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Required; +use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\Context\ExecutionContextFactory; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildA; @@ -31,11 +37,1938 @@ use Symfony\Component\Validator\Tests\Constraints\Fixtures\ChildB; use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Tests\Fixtures\EntityParent; use Symfony\Component\Validator\Tests\Fixtures\EntityWithGroupedConstraintOnMethods; +use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint; +use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory; +use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity; +use Symfony\Component\Validator\Tests\Fixtures\Reference; +use Symfony\Component\Validator\Validator\LazyProperty; use Symfony\Component\Validator\Validator\RecursiveValidator; use Symfony\Component\Validator\Validator\ValidatorInterface; -class RecursiveValidatorTest extends AbstractTest +class RecursiveValidatorTest extends TestCase { + private const ENTITY_CLASS = Entity::class; + private const REFERENCE_CLASS = Reference::class; + + /** + * @var FakeMetadataFactory + */ + private $metadataFactory; + + /** + * @var ClassMetadata + */ + private $metadata; + + /** + * @var ClassMetadata + */ + private $referenceMetadata; + + /** + * @var ValidatorInterface + */ + private $validator; + + protected function setUp(): void + { + $this->metadataFactory = new FakeMetadataFactory(); + $this->metadata = new ClassMetadata(self::ENTITY_CLASS); + $this->referenceMetadata = new ClassMetadata(self::REFERENCE_CLASS); + $this->metadataFactory->addMetadata($this->metadata); + $this->metadataFactory->addMetadata($this->referenceMetadata); + $this->metadataFactory->addMetadata(new ClassMetadata(LazyProperty::class)); + + $this->validator = $this->createValidator($this->metadataFactory); + } + + protected function tearDown(): void + { + $this->metadataFactory = null; + $this->metadata = null; + $this->referenceMetadata = null; + } + + protected function validate($value, $constraints = null, $groups = null) + { + return $this->validator->validate($value, $constraints, $groups); + } + + protected function validateProperty($object, $propertyName, $groups = null) + { + return $this->validator->validateProperty($object, $propertyName, $groups); + } + + protected function validatePropertyValue($object, $propertyName, $value, $groups = null) + { + return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups); + } + + public function testValidate() + { + $callback = function ($value, ExecutionContextInterface $context) { + $this->assertNull($context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame('Bernhard', $context->getRoot()); + $this->assertSame('Bernhard', $context->getValue()); + $this->assertSame('Bernhard', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $constraint = new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ]); + + $violations = $this->validate('Bernhard', $constraint, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('', $violations[0]->getPropertyPath()); + $this->assertSame('Bernhard', $violations[0]->getRoot()); + $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testClassConstraint() + { + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->metadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity, $context->getValue()); + $this->assertSame($entity, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testPropertyConstraint() + { + $entity = new Entity(); + $entity->firstName = 'Bernhard'; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); + + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertSame('firstName', $context->getPropertyName()); + $this->assertSame('firstName', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame('Bernhard', $context->getValue()); + $this->assertSame('Bernhard', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('firstName', new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('firstName', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testGetterConstraint() + { + $entity = new Entity(); + $entity->setLastName('Schussek'); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $propertyMetadatas = $this->metadata->getPropertyMetadata('lastName'); + + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertSame('lastName', $context->getPropertyName()); + $this->assertSame('lastName', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame('Schussek', $context->getValue()); + $this->assertSame('Schussek', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addGetterConstraint('lastName', new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('lastName', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Schussek', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testArray() + { + $entity = new Entity(); + $array = ['key' => $entity]; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity, $array) { + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('[key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->metadata, $context->getMetadata()); + $this->assertSame($array, $context->getRoot()); + $this->assertSame($entity, $context->getValue()); + $this->assertSame($entity, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($array, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('[key]', $violations[0]->getPropertyPath()); + $this->assertSame($array, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testRecursiveArray() + { + $entity = new Entity(); + $array = [2 => ['key' => $entity]]; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity, $array) { + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('[2][key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->metadata, $context->getMetadata()); + $this->assertSame($array, $context->getRoot()); + $this->assertSame($entity, $context->getValue()); + $this->assertSame($entity, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($array, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('[2][key]', $violations[0]->getPropertyPath()); + $this->assertSame($array, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testTraversable() + { + $entity = new Entity(); + $traversable = new \ArrayIterator(['key' => $entity]); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) { + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('[key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->metadata, $context->getMetadata()); + $this->assertSame($traversable, $context->getRoot()); + $this->assertSame($entity, $context->getValue()); + $this->assertSame($entity, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($traversable, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('[key]', $violations[0]->getPropertyPath()); + $this->assertSame($traversable, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testRecursiveTraversable() + { + $entity = new Entity(); + $traversable = new \ArrayIterator([ + 2 => new \ArrayIterator(['key' => $entity]), + ]); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) { + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('[2][key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->metadata, $context->getMetadata()); + $this->assertSame($traversable, $context->getRoot()); + $this->assertSame($entity, $context->getValue()); + $this->assertSame($entity, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($traversable, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('[2][key]', $violations[0]->getPropertyPath()); + $this->assertSame($traversable, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testReferenceClassConstraint() + { + $entity = new Entity(); + $entity->reference = new Reference(); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('reference', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference, $context->getValue()); + $this->assertSame($entity->reference, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testReferencePropertyConstraint() + { + $entity = new Entity(); + $entity->reference = new Reference(); + $entity->reference->value = 'Foobar'; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $propertyMetadatas = $this->referenceMetadata->getPropertyMetadata('value'); + + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertSame('value', $context->getPropertyName()); + $this->assertSame('reference.value', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame('Foobar', $context->getValue()); + $this->assertSame('Foobar', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->referenceMetadata->addPropertyConstraint('value', new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference.value', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Foobar', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testReferenceGetterConstraint() + { + $entity = new Entity(); + $entity->reference = new Reference(); + $entity->reference->setPrivateValue('Bamboo'); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $propertyMetadatas = $this->referenceMetadata->getPropertyMetadata('privateValue'); + + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertSame('privateValue', $context->getPropertyName()); + $this->assertSame('reference.privateValue', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame('Bamboo', $context->getValue()); + $this->assertSame('Bamboo', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->referenceMetadata->addPropertyConstraint('privateValue', new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference.privateValue', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Bamboo', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testsIgnoreNullReference() + { + $entity = new Entity(); + $entity->reference = null; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testFailOnScalarReferences() + { + $this->expectException('Symfony\Component\Validator\Exception\NoSuchMetadataException'); + $entity = new Entity(); + $entity->reference = 'string'; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + + $this->validate($entity); + } + + /** + * @dataProvider getConstraintMethods + */ + public function testArrayReference($constraintMethod) + { + $entity = new Entity(); + $entity->reference = ['key' => new Reference()]; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('reference[key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference['key'], $context->getValue()); + $this->assertSame($entity->reference['key'], $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->$constraintMethod('reference', new Valid()); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference[key]', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + /** + * @dataProvider getConstraintMethods + */ + public function testRecursiveArrayReference($constraintMethod) + { + $entity = new Entity(); + $entity->reference = [2 => ['key' => new Reference()]]; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('reference[2][key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference[2]['key'], $context->getValue()); + $this->assertSame($entity->reference[2]['key'], $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->$constraintMethod('reference', new Valid()); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testOnlyCascadedArraysAreTraversed() + { + $entity = new Entity(); + $entity->reference = ['key' => new Reference()]; + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Callback([ + 'callback' => function () {}, + 'groups' => 'Group', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + /** + * @dataProvider getConstraintMethods + */ + public function testArrayTraversalCannotBeDisabled($constraintMethod) + { + $entity = new Entity(); + $entity->reference = ['key' => new Reference()]; + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->$constraintMethod('reference', new Valid([ + 'traverse' => false, + ])); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + /** + * @dataProvider getConstraintMethods + */ + public function testRecursiveArrayTraversalCannotBeDisabled($constraintMethod) + { + $entity = new Entity(); + $entity->reference = [2 => ['key' => new Reference()]]; + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->$constraintMethod('reference', new Valid([ + 'traverse' => false, + ])); + + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + /** + * @dataProvider getConstraintMethods + */ + public function testIgnoreScalarsDuringArrayTraversal($constraintMethod) + { + $entity = new Entity(); + $entity->reference = ['string', 1234]; + + $this->metadata->$constraintMethod('reference', new Valid()); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + /** + * @dataProvider getConstraintMethods + */ + public function testIgnoreNullDuringArrayTraversal($constraintMethod) + { + $entity = new Entity(); + $entity->reference = [null]; + + $this->metadata->$constraintMethod('reference', new Valid()); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testTraversableReference() + { + $entity = new Entity(); + $entity->reference = new \ArrayIterator(['key' => new Reference()]); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('reference[key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference['key'], $context->getValue()); + $this->assertSame($entity->reference['key'], $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference[key]', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testDisableTraversableTraversal() + { + $entity = new Entity(); + $entity->reference = new \ArrayIterator(['key' => new Reference()]); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator')); + $this->metadata->addPropertyConstraint('reference', new Valid([ + 'traverse' => false, + ])); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testMetadataMustExistIfTraversalIsDisabled() + { + $this->expectException('Symfony\Component\Validator\Exception\NoSuchMetadataException'); + $entity = new Entity(); + $entity->reference = new \ArrayIterator(); + + $this->metadata->addPropertyConstraint('reference', new Valid([ + 'traverse' => false, + ])); + + $this->validate($entity); + } + + public function testEnableRecursiveTraversableTraversal() + { + $entity = new Entity(); + $entity->reference = new \ArrayIterator([ + 2 => new \ArrayIterator(['key' => new Reference()]), + ]); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('reference[2][key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference[2]['key'], $context->getValue()); + $this->assertSame($entity->reference[2]['key'], $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid([ + 'traverse' => true, + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($entity, null, 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testValidateProperty() + { + $entity = new Entity(); + $entity->firstName = 'Bernhard'; + $entity->setLastName('Schussek'); + + $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) { + $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); + + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertSame('firstName', $context->getPropertyName()); + $this->assertSame('firstName', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame('Bernhard', $context->getValue()); + $this->assertSame('Bernhard', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Other violation'); + }; + + $this->metadata->addPropertyConstraint('firstName', new Callback([ + 'callback' => $callback1, + 'groups' => 'Group', + ])); + $this->metadata->addPropertyConstraint('lastName', new Callback([ + 'callback' => $callback2, + 'groups' => 'Group', + ])); + + $violations = $this->validateProperty($entity, 'firstName', 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('firstName', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + /** + * https://github.com/symfony/symfony/issues/11604. + */ + public function testValidatePropertyWithoutConstraints() + { + $entity = new Entity(); + $violations = $this->validateProperty($entity, 'lastName'); + + $this->assertCount(0, $violations, '->validateProperty() returns no violations if no constraints have been configured for the property being validated'); + } + + public function testValidatePropertyValue() + { + $entity = new Entity(); + $entity->setLastName('Schussek'); + + $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) { + $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); + + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertSame('firstName', $context->getPropertyName()); + $this->assertSame('firstName', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame('Bernhard', $context->getValue()); + $this->assertSame('Bernhard', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Other violation'); + }; + + $this->metadata->addPropertyConstraint('firstName', new Callback([ + 'callback' => $callback1, + 'groups' => 'Group', + ])); + $this->metadata->addPropertyConstraint('lastName', new Callback([ + 'callback' => $callback2, + 'groups' => 'Group', + ])); + + $violations = $this->validatePropertyValue( + $entity, + 'firstName', + 'Bernhard', + 'Group' + ); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('firstName', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testValidatePropertyValueWithClassName() + { + $callback1 = function ($value, ExecutionContextInterface $context) { + $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName'); + + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertSame('firstName', $context->getPropertyName()); + $this->assertSame('', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($propertyMetadatas[0], $context->getMetadata()); + $this->assertSame('Bernhard', $context->getRoot()); + $this->assertSame('Bernhard', $context->getValue()); + $this->assertSame('Bernhard', $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Other violation'); + }; + + $this->metadata->addPropertyConstraint('firstName', new Callback([ + 'callback' => $callback1, + 'groups' => 'Group', + ])); + $this->metadata->addPropertyConstraint('lastName', new Callback([ + 'callback' => $callback2, + 'groups' => 'Group', + ])); + + $violations = $this->validatePropertyValue( + self::ENTITY_CLASS, + 'firstName', + 'Bernhard', + 'Group' + ); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('', $violations[0]->getPropertyPath()); + $this->assertSame('Bernhard', $violations[0]->getRoot()); + $this->assertSame('Bernhard', $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + /** + * https://github.com/symfony/symfony/issues/11604. + */ + public function testValidatePropertyValueWithoutConstraints() + { + $entity = new Entity(); + $violations = $this->validatePropertyValue($entity, 'lastName', 'foo'); + + $this->assertCount(0, $violations, '->validatePropertyValue() returns no violations if no constraints have been configured for the property being validated'); + } + + public function testValidateObjectOnlyOncePerGroup() + { + $entity = new Entity(); + $entity->reference = new Reference(); + $entity->reference2 = $entity->reference; + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->metadata->addPropertyConstraint('reference2', new Valid()); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + public function testValidateDifferentObjectsSeparately() + { + $entity = new Entity(); + $entity->reference = new Reference(); + $entity->reference2 = new Reference(); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->metadata->addPropertyConstraint('reference2', new Valid()); + $this->referenceMetadata->addConstraint(new Callback($callback)); + + $violations = $this->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(2, $violations); + } + + public function testValidateSingleGroup() + { + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group 1', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group 2', + ])); + + $violations = $this->validate($entity, null, 'Group 2'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + public function testValidateMultipleGroups() + { + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group 1', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group 2', + ])); + + $violations = $this->validate($entity, null, ['Group 1', 'Group 2']); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(2, $violations); + } + + public function testReplaceDefaultGroupByGroupSequenceObject() + { + $entity = new Entity(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Group 2'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Group 3'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => function () {}, + 'groups' => 'Group 1', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group 2', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 3', + ])); + + $sequence = new GroupSequence(['Group 1', 'Group 2', 'Group 3', 'Entity']); + $this->metadata->setGroupSequence($sequence); + + $violations = $this->validate($entity, null, 'Default'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Violation in Group 2', $violations[0]->getMessage()); + } + + public function testReplaceDefaultGroupByGroupSequenceArray() + { + $entity = new Entity(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Group 2'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Group 3'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => function () {}, + 'groups' => 'Group 1', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group 2', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 3', + ])); + + $sequence = ['Group 1', 'Group 2', 'Group 3', 'Entity']; + $this->metadata->setGroupSequence($sequence); + + $violations = $this->validate($entity, null, 'Default'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Violation in Group 2', $violations[0]->getMessage()); + } + + public function testPropagateDefaultGroupToReferenceWhenReplacingDefaultGroup() + { + $entity = new Entity(); + $entity->reference = new Reference(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Default group'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in group sequence'); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Default', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 1', + ])); + + $sequence = new GroupSequence(['Group 1', 'Entity']); + $this->metadata->setGroupSequence($sequence); + + $violations = $this->validate($entity, null, 'Default'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Violation in Default group', $violations[0]->getMessage()); + } + + public function testValidateCustomGroupWhenDefaultGroupWasReplaced() + { + $entity = new Entity(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in other group'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in group sequence'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Other Group', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 1', + ])); + + $sequence = new GroupSequence(['Group 1', 'Entity']); + $this->metadata->setGroupSequence($sequence); + + $violations = $this->validate($entity, null, 'Other Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Violation in other group', $violations[0]->getMessage()); + } + + /** + * @dataProvider getTestReplaceDefaultGroup + */ + public function testReplaceDefaultGroup($sequence, array $assertViolations) + { + $entity = new GroupSequenceProviderEntity($sequence); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Group 2'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Violation in Group 3'); + }; + + $metadata = new ClassMetadata(\get_class($entity)); + $metadata->addConstraint(new Callback([ + 'callback' => function () {}, + 'groups' => 'Group 1', + ])); + $metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group 2', + ])); + $metadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 3', + ])); + $metadata->setGroupSequenceProvider(true); + + $this->metadataFactory->addMetadata($metadata); + + $violations = $this->validate($entity, null, 'Default'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(\count($assertViolations), $violations); + foreach ($assertViolations as $key => $message) { + $this->assertSame($message, $violations[$key]->getMessage()); + } + } + + public function getConstraintMethods() + { + return [ + ['addPropertyConstraint'], + ['addGetterConstraint'], + ]; + } + + public function getTestReplaceDefaultGroup() + { + return [ + [ + 'sequence' => new GroupSequence(['Group 1', 'Group 2', 'Group 3', 'Entity']), + 'assertViolations' => [ + 'Violation in Group 2', + ], + ], + [ + 'sequence' => ['Group 1', 'Group 2', 'Group 3', 'Entity'], + 'assertViolations' => [ + 'Violation in Group 2', + ], + ], + [ + 'sequence' => new GroupSequence(['Group 1', ['Group 2', 'Group 3'], 'Entity']), + 'assertViolations' => [ + 'Violation in Group 2', + 'Violation in Group 3', + ], + ], + [ + 'sequence' => ['Group 1', ['Group 2', 'Group 3'], 'Entity'], + 'assertViolations' => [ + 'Violation in Group 2', + 'Violation in Group 3', + ], + ], + ]; + } + + public function testValidateConstraintWithoutGroup() + { + $violations = $this->validator->validate(null, new NotNull()); + + $this->assertCount(1, $violations); + } + + public function testValidateWithEmptyArrayAsConstraint() + { + $violations = $this->validator->validate('value', []); + $this->assertCount(0, $violations); + } + + public function testGroupSequenceAbortsAfterFailedGroup() + { + $entity = new Entity(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message 1'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message 2'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => function () {}, + 'groups' => 'Group 1', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group 2', + ])); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 3', + ])); + + $sequence = new GroupSequence(['Group 1', 'Group 2', 'Group 3']); + $violations = $this->validator->validate($entity, new Valid(), $sequence); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message 1', $violations[0]->getMessage()); + } + + public function testGroupSequenceIncludesReferences() + { + $entity = new Entity(); + $entity->reference = new Reference(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Reference violation 1'); + }; + $callback2 = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Reference violation 2'); + }; + + $this->metadata->addPropertyConstraint('reference', new Valid()); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group 1', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group 2', + ])); + + $sequence = new GroupSequence(['Group 1', 'Entity']); + $violations = $this->validator->validate($entity, new Valid(), $sequence); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Reference violation 1', $violations[0]->getMessage()); + } + + public function testValidateInSeparateContext() + { + $entity = new Entity(); + $entity->reference = new Reference(); + + $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) { + $violations = $context + ->getValidator() + // Since the validator is not context aware, the group must + // be passed explicitly + ->validate($value->reference, new Valid(), 'Group') + ; + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('', $violations[0]->getPropertyPath()); + + // The root is different as we're in a new context + $this->assertSame($entity->reference, $violations[0]->getRoot()); + $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + + // Verify that this method is called + $context->addViolation('Separate violation'); + }; + + $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity->reference, $context->getRoot()); + $this->assertSame($entity->reference, $context->getValue()); + $this->assertSame($entity->reference, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group', + ])); + + $violations = $this->validator->validate($entity, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Separate violation', $violations[0]->getMessage()); + } + + public function testValidateInContext() + { + $entity = new Entity(); + $entity->reference = new Reference(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $previousValue = $context->getValue(); + $previousObject = $context->getObject(); + $previousMetadata = $context->getMetadata(); + $previousPath = $context->getPropertyPath(); + $previousGroup = $context->getGroup(); + + $context + ->getValidator() + ->inContext($context) + ->atPath('subpath') + ->validate($value->reference) + ; + + // context changes shouldn't leak out of the validate() call + $this->assertSame($previousValue, $context->getValue()); + $this->assertSame($previousObject, $context->getObject()); + $this->assertSame($previousMetadata, $context->getMetadata()); + $this->assertSame($previousPath, $context->getPropertyPath()); + $this->assertSame($previousGroup, $context->getGroup()); + }; + + $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('subpath', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference, $context->getValue()); + $this->assertSame($entity->reference, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group', + ])); + + $violations = $this->validator->validate($entity, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('subpath', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testValidateArrayInContext() + { + $entity = new Entity(); + $entity->reference = new Reference(); + + $callback1 = function ($value, ExecutionContextInterface $context) { + $previousValue = $context->getValue(); + $previousObject = $context->getObject(); + $previousMetadata = $context->getMetadata(); + $previousPath = $context->getPropertyPath(); + $previousGroup = $context->getGroup(); + + $context + ->getValidator() + ->inContext($context) + ->atPath('subpath') + ->validate(['key' => $value->reference]) + ; + + // context changes shouldn't leak out of the validate() call + $this->assertSame($previousValue, $context->getValue()); + $this->assertSame($previousObject, $context->getObject()); + $this->assertSame($previousMetadata, $context->getMetadata()); + $this->assertSame($previousPath, $context->getPropertyPath()); + $this->assertSame($previousGroup, $context->getGroup()); + }; + + $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) { + $this->assertSame($this::REFERENCE_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('subpath[key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->referenceMetadata, $context->getMetadata()); + $this->assertSame($entity, $context->getRoot()); + $this->assertSame($entity->reference, $context->getValue()); + $this->assertSame($entity->reference, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback1, + 'groups' => 'Group', + ])); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback2, + 'groups' => 'Group', + ])); + + $violations = $this->validator->validate($entity, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('subpath[key]', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame($entity->reference, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testTraverseTraversableByDefault() + { + $entity = new Entity(); + $traversable = new \ArrayIterator(['key' => $entity]); + + $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) { + $this->assertSame($this::ENTITY_CLASS, $context->getClassName()); + $this->assertNull($context->getPropertyName()); + $this->assertSame('[key]', $context->getPropertyPath()); + $this->assertSame('Group', $context->getGroup()); + $this->assertSame($this->metadata, $context->getMetadata()); + $this->assertSame($traversable, $context->getRoot()); + $this->assertSame($entity, $context->getValue()); + $this->assertSame($entity, $value); + + $context->addViolation('Message %param%', ['%param%' => 'value']); + }; + + $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator')); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($traversable, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('[key]', $violations[0]->getPropertyPath()); + $this->assertSame($traversable, $violations[0]->getRoot()); + $this->assertSame($entity, $violations[0]->getInvalidValue()); + $this->assertNull($violations[0]->getPlural()); + $this->assertNull($violations[0]->getCode()); + } + + public function testTraversalEnabledOnClass() + { + $entity = new Entity(); + $traversable = new \ArrayIterator(['key' => $entity]); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $traversableMetadata = new ClassMetadata('ArrayIterator'); + $traversableMetadata->addConstraint(new Traverse(true)); + + $this->metadataFactory->addMetadata($traversableMetadata); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($traversable, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + public function testTraversalDisabledOnClass() + { + $entity = new Entity(); + $traversable = new \ArrayIterator(['key' => $entity]); + + $callback = function ($value, ExecutionContextInterface $context) { + $this->fail('Should not be called'); + }; + + $traversableMetadata = new ClassMetadata('ArrayIterator'); + $traversableMetadata->addConstraint(new Traverse(false)); + + $this->metadataFactory->addMetadata($traversableMetadata); + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + + $violations = $this->validate($traversable, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testExpectTraversableIfTraversalEnabledOnClass() + { + $this->expectException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); + $entity = new Entity(); + + $this->metadata->addConstraint(new Traverse(true)); + + $this->validator->validate($entity); + } + + public function testReferenceTraversalDisabledOnClass() + { + $entity = new Entity(); + $entity->reference = new \ArrayIterator(['key' => new Reference()]); + + $callback = function ($value, ExecutionContextInterface $context) { + $this->fail('Should not be called'); + }; + + $traversableMetadata = new ClassMetadata('ArrayIterator'); + $traversableMetadata->addConstraint(new Traverse(false)); + + $this->metadataFactory->addMetadata($traversableMetadata); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + $this->metadata->addPropertyConstraint('reference', new Valid()); + + $violations = $this->validate($entity, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testReferenceTraversalEnabledOnReferenceDisabledOnClass() + { + $entity = new Entity(); + $entity->reference = new \ArrayIterator(['key' => new Reference()]); + + $callback = function ($value, ExecutionContextInterface $context) { + $this->fail('Should not be called'); + }; + + $traversableMetadata = new ClassMetadata('ArrayIterator'); + $traversableMetadata->addConstraint(new Traverse(false)); + + $this->metadataFactory->addMetadata($traversableMetadata); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + $this->metadata->addPropertyConstraint('reference', new Valid([ + 'traverse' => true, + ])); + + $violations = $this->validate($entity, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testReferenceTraversalDisabledOnReferenceEnabledOnClass() + { + $entity = new Entity(); + $entity->reference = new \ArrayIterator(['key' => new Reference()]); + + $callback = function ($value, ExecutionContextInterface $context) { + $this->fail('Should not be called'); + }; + + $traversableMetadata = new ClassMetadata('ArrayIterator'); + $traversableMetadata->addConstraint(new Traverse(true)); + + $this->metadataFactory->addMetadata($traversableMetadata); + $this->referenceMetadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => 'Group', + ])); + $this->metadata->addPropertyConstraint('reference', new Valid([ + 'traverse' => false, + ])); + + $violations = $this->validate($entity, new Valid(), 'Group'); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(0, $violations); + } + + public function testAddCustomizedViolation() + { + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->buildViolation('Message %param%') + ->setParameter('%param%', 'value') + ->setInvalidValue('Invalid value') + ->setPlural(2) + ->setCode('42') + ->addViolation(); + }; + + $this->metadata->addConstraint(new Callback($callback)); + + $violations = $this->validator->validate($entity); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + $this->assertSame('Message value', $violations[0]->getMessage()); + $this->assertSame('Message %param%', $violations[0]->getMessageTemplate()); + $this->assertSame(['%param%' => 'value'], $violations[0]->getParameters()); + $this->assertSame('', $violations[0]->getPropertyPath()); + $this->assertSame($entity, $violations[0]->getRoot()); + $this->assertSame('Invalid value', $violations[0]->getInvalidValue()); + $this->assertSame(2, $violations[0]->getPlural()); + $this->assertSame('42', $violations[0]->getCode()); + } + + public function testNoDuplicateValidationIfClassConstraintInMultipleGroups() + { + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $this->metadata->addConstraint(new Callback([ + 'callback' => $callback, + 'groups' => ['Group 1', 'Group 2'], + ])); + + $violations = $this->validator->validate($entity, new Valid(), ['Group 1', 'Group 2']); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + public function testNoDuplicateValidationIfPropertyConstraintInMultipleGroups() + { + $entity = new Entity(); + + $callback = function ($value, ExecutionContextInterface $context) { + $context->addViolation('Message'); + }; + + $this->metadata->addPropertyConstraint('firstName', new Callback([ + 'callback' => $callback, + 'groups' => ['Group 1', 'Group 2'], + ])); + + $violations = $this->validator->validate($entity, new Valid(), ['Group 1', 'Group 2']); + + /* @var ConstraintViolationInterface[] $violations */ + $this->assertCount(1, $violations); + } + + public function testValidateFailsIfNoConstraintsAndNoObjectOrArray() + { + $this->expectException('Symfony\Component\Validator\Exception\RuntimeException'); + $this->validate('Foobar'); + } + + public function testAccessCurrentObject() + { + $called = false; + $entity = new Entity(); + $entity->firstName = 'Bernhard'; + $entity->data = ['firstName' => 'Bernhard']; + + $callback = function ($value, ExecutionContextInterface $context) use ($entity, &$called) { + $called = true; + $this->assertSame($entity, $context->getObject()); + }; + + $this->metadata->addConstraint(new Callback($callback)); + $this->metadata->addPropertyConstraint('firstName', new Callback($callback)); + $this->metadata->addPropertyConstraint('data', new Collection(['firstName' => new Expression('value == this.firstName')])); + + $this->validator->validate($entity); + + $this->assertTrue($called); + } + + public function testInitializeObjectsOnFirstValidation() + { + $entity = new Entity(); + $entity->initialized = false; + + // prepare initializers that set "initialized" to true + $initializer1 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); + $initializer2 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); + + $initializer1->expects($this->once()) + ->method('initialize') + ->with($entity) + ->willReturnCallback(function ($object) { + $object->initialized = true; + }); + + $initializer2->expects($this->once()) + ->method('initialize') + ->with($entity); + + $this->validator = $this->createValidator($this->metadataFactory, [ + $initializer1, + $initializer2, + ]); + + // prepare constraint which + // * checks that "initialized" is set to true + // * validates the object again + $callback = function ($object, ExecutionContextInterface $context) { + $this->assertTrue($object->initialized); + + // validate again in same group + $validator = $context->getValidator()->inContext($context); + + $validator->validate($object); + + // validate again in other group + $validator->validate($object, null, 'SomeGroup'); + }; + + $this->metadata->addConstraint(new Callback($callback)); + + $this->validate($entity); + + $this->assertTrue($entity->initialized); + } + + public function testPassConstraintToViolation() + { + $constraint = new FailingConstraint(); + $violations = $this->validate('Foobar', $constraint); + + $this->assertCount(1, $violations); + $this->assertSame($constraint, $violations[0]->getConstraint()); + } + + public function testCollectionConstraitViolationHasCorrectContext() + { + $data = [ + 'foo' => 'fooValue', + ]; + + // Missing field must not be the first in the collection validation + $constraint = new Collection([ + 'foo' => new NotNull(), + 'bar' => new NotNull(), + ]); + + $violations = $this->validate($data, $constraint); + + $this->assertCount(1, $violations); + $this->assertSame($constraint, $violations[0]->getConstraint()); + } + + public function testNestedObjectIsNotValidatedIfGroupInValidConstraintIsNotValidated() + { + $entity = new Entity(); + $entity->firstName = ''; + $reference = new Reference(); + $reference->value = ''; + $entity->childA = $reference; + + $this->metadata->addPropertyConstraint('firstName', new NotBlank(['groups' => 'group1'])); + $this->metadata->addPropertyConstraint('childA', new Valid(['groups' => 'group1'])); + $this->referenceMetadata->addPropertyConstraint('value', new NotBlank()); + + $violations = $this->validator->validate($entity, null, []); + + $this->assertCount(0, $violations); + } + + public function testNestedObjectIsValidatedIfGroupInValidConstraintIsValidated() + { + $entity = new Entity(); + $entity->firstName = ''; + $reference = new Reference(); + $reference->value = ''; + $entity->childA = $reference; + + $this->metadata->addPropertyConstraint('firstName', new NotBlank(['groups' => 'group1'])); + $this->metadata->addPropertyConstraint('childA', new Valid(['groups' => 'group1'])); + $this->referenceMetadata->addPropertyConstraint('value', new NotBlank(['groups' => 'group1'])); + + $violations = $this->validator->validate($entity, null, ['Default', 'group1']); + + $this->assertCount(2, $violations); + } + + public function testNestedObjectIsValidatedInMultipleGroupsIfGroupInValidConstraintIsValidated() + { + $entity = new Entity(); + $entity->firstName = null; + + $reference = new Reference(); + $reference->value = null; + + $entity->childA = $reference; + + $this->metadata->addPropertyConstraint('firstName', new NotBlank()); + $this->metadata->addPropertyConstraint('childA', new Valid(['groups' => ['group1', 'group2']])); + + $this->referenceMetadata->addPropertyConstraint('value', new NotBlank(['groups' => 'group1'])); + $this->referenceMetadata->addPropertyConstraint('value', new NotNull(['groups' => 'group2'])); + + $violations = $this->validator->validate($entity, null, ['Default', 'group1', 'group2']); + + $this->assertCount(3, $violations); + } + protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = []): ValidatorInterface { $translator = new IdentityTranslator();