4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Validator\Tests\Validator;
14 use Symfony\Component\Validator\Constraints\Callback;
15 use Symfony\Component\Validator\Constraints\Collection;
16 use Symfony\Component\Validator\Constraints\GroupSequence;
17 use Symfony\Component\Validator\Constraints\NotNull;
18 use Symfony\Component\Validator\Constraints\Traverse;
19 use Symfony\Component\Validator\Constraints\Valid;
20 use Symfony\Component\Validator\ConstraintViolationInterface;
21 use Symfony\Component\Validator\Context\ExecutionContextInterface;
22 use Symfony\Component\Validator\Mapping\ClassMetadata;
23 use Symfony\Component\Validator\MetadataFactoryInterface;
24 use Symfony\Component\Validator\Tests\Fixtures\Entity;
25 use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
26 use Symfony\Component\Validator\Tests\Fixtures\FakeClassMetadata;
27 use Symfony\Component\Validator\Tests\Fixtures\Reference;
28 use Symfony\Component\Validator\Validator\ValidatorInterface;
31 * Verifies that a validator satisfies the API of Symfony 2.5+.
33 * @author Bernhard Schussek <bschussek@gmail.com>
35 abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
38 * @var ValidatorInterface
43 * @param MetadataFactoryInterface $metadataFactory
44 * @param array $objectInitializers
46 * @return ValidatorInterface
48 abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());
50 protected function setUp()
54 $this->validator = $this->createValidator($this->metadataFactory);
57 protected function validate($value, $constraints = null, $groups = null)
59 return $this->validator->validate($value, $constraints, $groups);
62 protected function validateProperty($object, $propertyName, $groups = null)
64 return $this->validator->validateProperty($object, $propertyName, $groups);
67 protected function validatePropertyValue($object, $propertyName, $value, $groups = null)
69 return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups);
72 public function testValidateConstraintWithoutGroup()
74 $violations = $this->validator->validate(null, new NotNull());
76 $this->assertCount(1, $violations);
79 public function testValidateWithEmptyArrayAsConstraint()
81 $violations = $this->validator->validate('value', array());
82 $this->assertCount(0, $violations);
85 public function testGroupSequenceAbortsAfterFailedGroup()
87 $entity = new Entity();
89 $callback1 = function ($value, ExecutionContextInterface $context) {
90 $context->addViolation('Message 1');
92 $callback2 = function ($value, ExecutionContextInterface $context) {
93 $context->addViolation('Message 2');
96 $this->metadata->addConstraint(new Callback(array(
97 'callback' => function () {},
98 'groups' => 'Group 1',
100 $this->metadata->addConstraint(new Callback(array(
101 'callback' => $callback1,
102 'groups' => 'Group 2',
104 $this->metadata->addConstraint(new Callback(array(
105 'callback' => $callback2,
106 'groups' => 'Group 3',
109 $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3'));
110 $violations = $this->validator->validate($entity, new Valid(), $sequence);
112 /* @var ConstraintViolationInterface[] $violations */
113 $this->assertCount(1, $violations);
114 $this->assertSame('Message 1', $violations[0]->getMessage());
117 public function testGroupSequenceIncludesReferences()
119 $entity = new Entity();
120 $entity->reference = new Reference();
122 $callback1 = function ($value, ExecutionContextInterface $context) {
123 $context->addViolation('Reference violation 1');
125 $callback2 = function ($value, ExecutionContextInterface $context) {
126 $context->addViolation('Reference violation 2');
129 $this->metadata->addPropertyConstraint('reference', new Valid());
130 $this->referenceMetadata->addConstraint(new Callback(array(
131 'callback' => $callback1,
132 'groups' => 'Group 1',
134 $this->referenceMetadata->addConstraint(new Callback(array(
135 'callback' => $callback2,
136 'groups' => 'Group 2',
139 $sequence = new GroupSequence(array('Group 1', 'Entity'));
140 $violations = $this->validator->validate($entity, new Valid(), $sequence);
142 /* @var ConstraintViolationInterface[] $violations */
143 $this->assertCount(1, $violations);
144 $this->assertSame('Reference violation 1', $violations[0]->getMessage());
147 public function testValidateInSeparateContext()
150 $entity = new Entity();
151 $entity->reference = new Reference();
153 $callback1 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
154 $violations = $context
156 // Since the validator is not context aware, the group must
157 // be passed explicitly
158 ->validate($value->reference, new Valid(), 'Group')
161 /* @var ConstraintViolationInterface[] $violations */
162 $test->assertCount(1, $violations);
163 $test->assertSame('Message value', $violations[0]->getMessage());
164 $test->assertSame('Message %param%', $violations[0]->getMessageTemplate());
165 $test->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
166 $test->assertSame('', $violations[0]->getPropertyPath());
167 // The root is different as we're in a new context
168 $test->assertSame($entity->reference, $violations[0]->getRoot());
169 $test->assertSame($entity->reference, $violations[0]->getInvalidValue());
170 $test->assertNull($violations[0]->getPlural());
171 $test->assertNull($violations[0]->getCode());
173 // Verify that this method is called
174 $context->addViolation('Separate violation');
177 $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
178 $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
179 $test->assertNull($context->getPropertyName());
180 $test->assertSame('', $context->getPropertyPath());
181 $test->assertSame('Group', $context->getGroup());
182 $test->assertSame($test->referenceMetadata, $context->getMetadata());
183 $test->assertSame($entity->reference, $context->getRoot());
184 $test->assertSame($entity->reference, $context->getValue());
185 $test->assertSame($entity->reference, $value);
187 $context->addViolation('Message %param%', array('%param%' => 'value'));
190 $this->metadata->addConstraint(new Callback(array(
191 'callback' => $callback1,
194 $this->referenceMetadata->addConstraint(new Callback(array(
195 'callback' => $callback2,
199 $violations = $this->validator->validate($entity, new Valid(), 'Group');
201 /* @var ConstraintViolationInterface[] $violations */
202 $this->assertCount(1, $violations);
203 $test->assertSame('Separate violation', $violations[0]->getMessage());
206 public function testValidateInContext()
209 $entity = new Entity();
210 $entity->reference = new Reference();
212 $callback1 = function ($value, ExecutionContextInterface $context) use ($test) {
213 $previousValue = $context->getValue();
214 $previousObject = $context->getObject();
215 $previousMetadata = $context->getMetadata();
216 $previousPath = $context->getPropertyPath();
217 $previousGroup = $context->getGroup();
221 ->inContext($context)
223 ->validate($value->reference)
226 // context changes shouldn't leak out of the validate() call
227 $test->assertSame($previousValue, $context->getValue());
228 $test->assertSame($previousObject, $context->getObject());
229 $test->assertSame($previousMetadata, $context->getMetadata());
230 $test->assertSame($previousPath, $context->getPropertyPath());
231 $test->assertSame($previousGroup, $context->getGroup());
234 $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
235 $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
236 $test->assertNull($context->getPropertyName());
237 $test->assertSame('subpath', $context->getPropertyPath());
238 $test->assertSame('Group', $context->getGroup());
239 $test->assertSame($test->referenceMetadata, $context->getMetadata());
240 $test->assertSame($entity, $context->getRoot());
241 $test->assertSame($entity->reference, $context->getValue());
242 $test->assertSame($entity->reference, $value);
244 $context->addViolation('Message %param%', array('%param%' => 'value'));
247 $this->metadata->addConstraint(new Callback(array(
248 'callback' => $callback1,
251 $this->referenceMetadata->addConstraint(new Callback(array(
252 'callback' => $callback2,
256 $violations = $this->validator->validate($entity, new Valid(), 'Group');
258 /* @var ConstraintViolationInterface[] $violations */
259 $this->assertCount(1, $violations);
260 $this->assertSame('Message value', $violations[0]->getMessage());
261 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
262 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
263 $this->assertSame('subpath', $violations[0]->getPropertyPath());
264 $this->assertSame($entity, $violations[0]->getRoot());
265 $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
266 $this->assertNull($violations[0]->getPlural());
267 $this->assertNull($violations[0]->getCode());
270 public function testValidateArrayInContext()
273 $entity = new Entity();
274 $entity->reference = new Reference();
276 $callback1 = function ($value, ExecutionContextInterface $context) use ($test) {
277 $previousValue = $context->getValue();
278 $previousObject = $context->getObject();
279 $previousMetadata = $context->getMetadata();
280 $previousPath = $context->getPropertyPath();
281 $previousGroup = $context->getGroup();
285 ->inContext($context)
287 ->validate(array('key' => $value->reference))
290 // context changes shouldn't leak out of the validate() call
291 $test->assertSame($previousValue, $context->getValue());
292 $test->assertSame($previousObject, $context->getObject());
293 $test->assertSame($previousMetadata, $context->getMetadata());
294 $test->assertSame($previousPath, $context->getPropertyPath());
295 $test->assertSame($previousGroup, $context->getGroup());
298 $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
299 $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
300 $test->assertNull($context->getPropertyName());
301 $test->assertSame('subpath[key]', $context->getPropertyPath());
302 $test->assertSame('Group', $context->getGroup());
303 $test->assertSame($test->referenceMetadata, $context->getMetadata());
304 $test->assertSame($entity, $context->getRoot());
305 $test->assertSame($entity->reference, $context->getValue());
306 $test->assertSame($entity->reference, $value);
308 $context->addViolation('Message %param%', array('%param%' => 'value'));
311 $this->metadata->addConstraint(new Callback(array(
312 'callback' => $callback1,
315 $this->referenceMetadata->addConstraint(new Callback(array(
316 'callback' => $callback2,
320 $violations = $this->validator->validate($entity, new Valid(), 'Group');
322 /* @var ConstraintViolationInterface[] $violations */
323 $this->assertCount(1, $violations);
324 $this->assertSame('Message value', $violations[0]->getMessage());
325 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
326 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
327 $this->assertSame('subpath[key]', $violations[0]->getPropertyPath());
328 $this->assertSame($entity, $violations[0]->getRoot());
329 $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
330 $this->assertNull($violations[0]->getPlural());
331 $this->assertNull($violations[0]->getCode());
334 public function testTraverseTraversableByDefault()
337 $entity = new Entity();
338 $traversable = new \ArrayIterator(array('key' => $entity));
340 $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $traversable) {
341 $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
342 $test->assertNull($context->getPropertyName());
343 $test->assertSame('[key]', $context->getPropertyPath());
344 $test->assertSame('Group', $context->getGroup());
345 $test->assertSame($test->metadata, $context->getMetadata());
346 $test->assertSame($traversable, $context->getRoot());
347 $test->assertSame($entity, $context->getValue());
348 $test->assertSame($entity, $value);
350 $context->addViolation('Message %param%', array('%param%' => 'value'));
353 $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
354 $this->metadata->addConstraint(new Callback(array(
355 'callback' => $callback,
359 $violations = $this->validate($traversable, new Valid(), 'Group');
361 /* @var ConstraintViolationInterface[] $violations */
362 $this->assertCount(1, $violations);
363 $this->assertSame('Message value', $violations[0]->getMessage());
364 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
365 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
366 $this->assertSame('[key]', $violations[0]->getPropertyPath());
367 $this->assertSame($traversable, $violations[0]->getRoot());
368 $this->assertSame($entity, $violations[0]->getInvalidValue());
369 $this->assertNull($violations[0]->getPlural());
370 $this->assertNull($violations[0]->getCode());
373 public function testTraversalEnabledOnClass()
375 $entity = new Entity();
376 $traversable = new \ArrayIterator(array('key' => $entity));
378 $callback = function ($value, ExecutionContextInterface $context) {
379 $context->addViolation('Message');
382 $traversableMetadata = new ClassMetadata('ArrayIterator');
383 $traversableMetadata->addConstraint(new Traverse(true));
385 $this->metadataFactory->addMetadata($traversableMetadata);
386 $this->metadata->addConstraint(new Callback(array(
387 'callback' => $callback,
391 $violations = $this->validate($traversable, new Valid(), 'Group');
393 /* @var ConstraintViolationInterface[] $violations */
394 $this->assertCount(1, $violations);
397 public function testTraversalDisabledOnClass()
400 $entity = new Entity();
401 $traversable = new \ArrayIterator(array('key' => $entity));
403 $callback = function ($value, ExecutionContextInterface $context) use ($test) {
404 $test->fail('Should not be called');
407 $traversableMetadata = new ClassMetadata('ArrayIterator');
408 $traversableMetadata->addConstraint(new Traverse(false));
410 $this->metadataFactory->addMetadata($traversableMetadata);
411 $this->metadata->addConstraint(new Callback(array(
412 'callback' => $callback,
416 $violations = $this->validate($traversable, new Valid(), 'Group');
418 /* @var ConstraintViolationInterface[] $violations */
419 $this->assertCount(0, $violations);
423 * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
425 public function testExpectTraversableIfTraversalEnabledOnClass()
427 $entity = new Entity();
429 $this->metadata->addConstraint(new Traverse(true));
431 $this->validator->validate($entity);
434 public function testReferenceTraversalDisabledOnClass()
437 $entity = new Entity();
438 $entity->reference = new \ArrayIterator(array('key' => new Reference()));
440 $callback = function ($value, ExecutionContextInterface $context) use ($test) {
441 $test->fail('Should not be called');
444 $traversableMetadata = new ClassMetadata('ArrayIterator');
445 $traversableMetadata->addConstraint(new Traverse(false));
447 $this->metadataFactory->addMetadata($traversableMetadata);
448 $this->referenceMetadata->addConstraint(new Callback(array(
449 'callback' => $callback,
452 $this->metadata->addPropertyConstraint('reference', new Valid());
454 $violations = $this->validate($entity, new Valid(), 'Group');
456 /* @var ConstraintViolationInterface[] $violations */
457 $this->assertCount(0, $violations);
460 public function testReferenceTraversalEnabledOnReferenceDisabledOnClass()
463 $entity = new Entity();
464 $entity->reference = new \ArrayIterator(array('key' => new Reference()));
466 $callback = function ($value, ExecutionContextInterface $context) use ($test) {
467 $test->fail('Should not be called');
470 $traversableMetadata = new ClassMetadata('ArrayIterator');
471 $traversableMetadata->addConstraint(new Traverse(false));
473 $this->metadataFactory->addMetadata($traversableMetadata);
474 $this->referenceMetadata->addConstraint(new Callback(array(
475 'callback' => $callback,
478 $this->metadata->addPropertyConstraint('reference', new Valid(array(
482 $violations = $this->validate($entity, new Valid(), 'Group');
484 /* @var ConstraintViolationInterface[] $violations */
485 $this->assertCount(0, $violations);
488 public function testReferenceTraversalDisabledOnReferenceEnabledOnClass()
491 $entity = new Entity();
492 $entity->reference = new \ArrayIterator(array('key' => new Reference()));
494 $callback = function ($value, ExecutionContextInterface $context) use ($test) {
495 $test->fail('Should not be called');
498 $traversableMetadata = new ClassMetadata('ArrayIterator');
499 $traversableMetadata->addConstraint(new Traverse(true));
501 $this->metadataFactory->addMetadata($traversableMetadata);
502 $this->referenceMetadata->addConstraint(new Callback(array(
503 'callback' => $callback,
506 $this->metadata->addPropertyConstraint('reference', new Valid(array(
510 $violations = $this->validate($entity, new Valid(), 'Group');
512 /* @var ConstraintViolationInterface[] $violations */
513 $this->assertCount(0, $violations);
516 public function testAddCustomizedViolation()
518 $entity = new Entity();
520 $callback = function ($value, ExecutionContextInterface $context) {
521 $context->buildViolation('Message %param%')
522 ->setParameter('%param%', 'value')
523 ->setInvalidValue('Invalid value')
529 $this->metadata->addConstraint(new Callback($callback));
531 $violations = $this->validator->validate($entity);
533 /* @var ConstraintViolationInterface[] $violations */
534 $this->assertCount(1, $violations);
535 $this->assertSame('Message value', $violations[0]->getMessage());
536 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
537 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
538 $this->assertSame('', $violations[0]->getPropertyPath());
539 $this->assertSame($entity, $violations[0]->getRoot());
540 $this->assertSame('Invalid value', $violations[0]->getInvalidValue());
541 $this->assertSame(2, $violations[0]->getPlural());
542 $this->assertSame(42, $violations[0]->getCode());
546 * @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
549 public function testMetadataMustImplementClassMetadataInterface()
551 $entity = new Entity();
553 $metadata = $this->getMockBuilder('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata')->getMock();
554 $metadata->expects($this->any())
555 ->method('getClassName')
556 ->will($this->returnValue(get_class($entity)));
558 $this->metadataFactory->addMetadata($metadata);
560 $this->validator->validate($entity);
564 * @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
567 public function testReferenceMetadataMustImplementClassMetadataInterface()
569 $entity = new Entity();
570 $entity->reference = new Reference();
572 $metadata = $this->getMockBuilder('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata')->getMock();
573 $metadata->expects($this->any())
574 ->method('getClassName')
575 ->will($this->returnValue(get_class($entity->reference)));
577 $this->metadataFactory->addMetadata($metadata);
579 $this->metadata->addPropertyConstraint('reference', new Valid());
581 $this->validator->validate($entity);
585 * @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
588 public function testLegacyPropertyMetadataMustImplementPropertyMetadataInterface()
590 $entity = new Entity();
593 $propertyMetadata = $this->getMockBuilder('Symfony\Component\Validator\MetadataInterface')->getMock();
594 $metadata = new FakeClassMetadata(get_class($entity));
595 $metadata->addCustomPropertyMetadata('firstName', $propertyMetadata);
597 $this->metadataFactory->addMetadata($metadata);
599 $this->validator->validate($entity);
602 public function testNoDuplicateValidationIfClassConstraintInMultipleGroups()
604 $entity = new Entity();
606 $callback = function ($value, ExecutionContextInterface $context) {
607 $context->addViolation('Message');
610 $this->metadata->addConstraint(new Callback(array(
611 'callback' => $callback,
612 'groups' => array('Group 1', 'Group 2'),
615 $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
617 /* @var ConstraintViolationInterface[] $violations */
618 $this->assertCount(1, $violations);
621 public function testNoDuplicateValidationIfPropertyConstraintInMultipleGroups()
623 $entity = new Entity();
625 $callback = function ($value, ExecutionContextInterface $context) {
626 $context->addViolation('Message');
629 $this->metadata->addPropertyConstraint('firstName', new Callback(array(
630 'callback' => $callback,
631 'groups' => array('Group 1', 'Group 2'),
634 $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
636 /* @var ConstraintViolationInterface[] $violations */
637 $this->assertCount(1, $violations);
641 * @expectedException \Symfony\Component\Validator\Exception\RuntimeException
643 public function testValidateFailsIfNoConstraintsAndNoObjectOrArray()
645 $this->validate('Foobar');
648 public function testAccessCurrentObject()
652 $entity = new Entity();
653 $entity->firstName = 'Bernhard';
655 $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, &$called) {
657 $test->assertSame($entity, $context->getObject());
660 $this->metadata->addConstraint(new Callback($callback));
661 $this->metadata->addPropertyConstraint('firstName', new Callback($callback));
663 $this->validator->validate($entity);
665 $this->assertTrue($called);
668 public function testInitializeObjectsOnFirstValidation()
671 $entity = new Entity();
672 $entity->initialized = false;
674 // prepare initializers that set "initialized" to true
675 $initializer1 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock();
676 $initializer2 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock();
678 $initializer1->expects($this->once())
679 ->method('initialize')
681 ->will($this->returnCallback(function ($object) {
682 $object->initialized = true;
685 $initializer2->expects($this->once())
686 ->method('initialize')
689 $this->validator = $this->createValidator($this->metadataFactory, array(
694 // prepare constraint which
695 // * checks that "initialized" is set to true
696 // * validates the object again
697 $callback = function ($object, ExecutionContextInterface $context) use ($test) {
698 $test->assertTrue($object->initialized);
700 // validate again in same group
701 $validator = $context->getValidator()->inContext($context);
703 $validator->validate($object);
705 // validate again in other group
706 $validator->validate($object, null, 'SomeGroup');
709 $this->metadata->addConstraint(new Callback($callback));
711 $this->validate($entity);
713 $this->assertTrue($entity->initialized);
716 public function testPassConstraintToViolation()
718 $constraint = new FailingConstraint();
719 $violations = $this->validate('Foobar', $constraint);
721 $this->assertCount(1, $violations);
722 $this->assertSame($constraint, $violations[0]->getConstraint());
725 public function testCollectionConstraitViolationHasCorrectContext()
731 // Missing field must not be the first in the collection validation
732 $constraint = new Collection(array(
733 'foo' => new NotNull(),
734 'bar' => new NotNull(),
737 $violations = $this->validate($data, $constraint);
739 $this->assertCount(1, $violations);
740 $this->assertSame($constraint, $violations[0]->getConstraint());