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\Mapping\Factory\MetadataFactoryInterface;
24 use Symfony\Component\Validator\Tests\Fixtures\Entity;
25 use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
26 use Symfony\Component\Validator\Tests\Fixtures\Reference;
27 use Symfony\Component\Validator\Validator\ValidatorInterface;
30 * @author Bernhard Schussek <bschussek@gmail.com>
32 abstract class AbstractTest extends AbstractValidatorTest
35 * @var ValidatorInterface
40 * @param MetadataFactoryInterface $metadataFactory
41 * @param array $objectInitializers
43 * @return ValidatorInterface
45 abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());
47 protected function setUp()
51 $this->validator = $this->createValidator($this->metadataFactory);
54 protected function validate($value, $constraints = null, $groups = null)
56 return $this->validator->validate($value, $constraints, $groups);
59 protected function validateProperty($object, $propertyName, $groups = null)
61 return $this->validator->validateProperty($object, $propertyName, $groups);
64 protected function validatePropertyValue($object, $propertyName, $value, $groups = null)
66 return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups);
69 public function testValidateConstraintWithoutGroup()
71 $violations = $this->validator->validate(null, new NotNull());
73 $this->assertCount(1, $violations);
76 public function testValidateWithEmptyArrayAsConstraint()
78 $violations = $this->validator->validate('value', array());
79 $this->assertCount(0, $violations);
82 public function testGroupSequenceAbortsAfterFailedGroup()
84 $entity = new Entity();
86 $callback1 = function ($value, ExecutionContextInterface $context) {
87 $context->addViolation('Message 1');
89 $callback2 = function ($value, ExecutionContextInterface $context) {
90 $context->addViolation('Message 2');
93 $this->metadata->addConstraint(new Callback(array(
94 'callback' => function () {},
95 'groups' => 'Group 1',
97 $this->metadata->addConstraint(new Callback(array(
98 'callback' => $callback1,
99 'groups' => 'Group 2',
101 $this->metadata->addConstraint(new Callback(array(
102 'callback' => $callback2,
103 'groups' => 'Group 3',
106 $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3'));
107 $violations = $this->validator->validate($entity, new Valid(), $sequence);
109 /* @var ConstraintViolationInterface[] $violations */
110 $this->assertCount(1, $violations);
111 $this->assertSame('Message 1', $violations[0]->getMessage());
114 public function testGroupSequenceIncludesReferences()
116 $entity = new Entity();
117 $entity->reference = new Reference();
119 $callback1 = function ($value, ExecutionContextInterface $context) {
120 $context->addViolation('Reference violation 1');
122 $callback2 = function ($value, ExecutionContextInterface $context) {
123 $context->addViolation('Reference violation 2');
126 $this->metadata->addPropertyConstraint('reference', new Valid());
127 $this->referenceMetadata->addConstraint(new Callback(array(
128 'callback' => $callback1,
129 'groups' => 'Group 1',
131 $this->referenceMetadata->addConstraint(new Callback(array(
132 'callback' => $callback2,
133 'groups' => 'Group 2',
136 $sequence = new GroupSequence(array('Group 1', 'Entity'));
137 $violations = $this->validator->validate($entity, new Valid(), $sequence);
139 /* @var ConstraintViolationInterface[] $violations */
140 $this->assertCount(1, $violations);
141 $this->assertSame('Reference violation 1', $violations[0]->getMessage());
144 public function testValidateInSeparateContext()
146 $entity = new Entity();
147 $entity->reference = new Reference();
149 $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) {
150 $violations = $context
152 // Since the validator is not context aware, the group must
153 // be passed explicitly
154 ->validate($value->reference, new Valid(), 'Group')
157 /* @var ConstraintViolationInterface[] $violations */
158 $this->assertCount(1, $violations);
159 $this->assertSame('Message value', $violations[0]->getMessage());
160 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
161 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
162 $this->assertSame('', $violations[0]->getPropertyPath());
164 // The root is different as we're in a new context
165 $this->assertSame($entity->reference, $violations[0]->getRoot());
166 $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
167 $this->assertNull($violations[0]->getPlural());
168 $this->assertNull($violations[0]->getCode());
170 // Verify that this method is called
171 $context->addViolation('Separate violation');
174 $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) {
175 $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
176 $this->assertNull($context->getPropertyName());
177 $this->assertSame('', $context->getPropertyPath());
178 $this->assertSame('Group', $context->getGroup());
179 $this->assertSame($this->referenceMetadata, $context->getMetadata());
180 $this->assertSame($entity->reference, $context->getRoot());
181 $this->assertSame($entity->reference, $context->getValue());
182 $this->assertSame($entity->reference, $value);
184 $context->addViolation('Message %param%', array('%param%' => 'value'));
187 $this->metadata->addConstraint(new Callback(array(
188 'callback' => $callback1,
191 $this->referenceMetadata->addConstraint(new Callback(array(
192 'callback' => $callback2,
196 $violations = $this->validator->validate($entity, new Valid(), 'Group');
198 /* @var ConstraintViolationInterface[] $violations */
199 $this->assertCount(1, $violations);
200 $this->assertSame('Separate violation', $violations[0]->getMessage());
203 public function testValidateInContext()
205 $entity = new Entity();
206 $entity->reference = new Reference();
208 $callback1 = function ($value, ExecutionContextInterface $context) {
209 $previousValue = $context->getValue();
210 $previousObject = $context->getObject();
211 $previousMetadata = $context->getMetadata();
212 $previousPath = $context->getPropertyPath();
213 $previousGroup = $context->getGroup();
217 ->inContext($context)
219 ->validate($value->reference)
222 // context changes shouldn't leak out of the validate() call
223 $this->assertSame($previousValue, $context->getValue());
224 $this->assertSame($previousObject, $context->getObject());
225 $this->assertSame($previousMetadata, $context->getMetadata());
226 $this->assertSame($previousPath, $context->getPropertyPath());
227 $this->assertSame($previousGroup, $context->getGroup());
230 $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) {
231 $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
232 $this->assertNull($context->getPropertyName());
233 $this->assertSame('subpath', $context->getPropertyPath());
234 $this->assertSame('Group', $context->getGroup());
235 $this->assertSame($this->referenceMetadata, $context->getMetadata());
236 $this->assertSame($entity, $context->getRoot());
237 $this->assertSame($entity->reference, $context->getValue());
238 $this->assertSame($entity->reference, $value);
240 $context->addViolation('Message %param%', array('%param%' => 'value'));
243 $this->metadata->addConstraint(new Callback(array(
244 'callback' => $callback1,
247 $this->referenceMetadata->addConstraint(new Callback(array(
248 'callback' => $callback2,
252 $violations = $this->validator->validate($entity, new Valid(), 'Group');
254 /* @var ConstraintViolationInterface[] $violations */
255 $this->assertCount(1, $violations);
256 $this->assertSame('Message value', $violations[0]->getMessage());
257 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
258 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
259 $this->assertSame('subpath', $violations[0]->getPropertyPath());
260 $this->assertSame($entity, $violations[0]->getRoot());
261 $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
262 $this->assertNull($violations[0]->getPlural());
263 $this->assertNull($violations[0]->getCode());
266 public function testValidateArrayInContext()
268 $entity = new Entity();
269 $entity->reference = new Reference();
271 $callback1 = function ($value, ExecutionContextInterface $context) {
272 $previousValue = $context->getValue();
273 $previousObject = $context->getObject();
274 $previousMetadata = $context->getMetadata();
275 $previousPath = $context->getPropertyPath();
276 $previousGroup = $context->getGroup();
280 ->inContext($context)
282 ->validate(array('key' => $value->reference))
285 // context changes shouldn't leak out of the validate() call
286 $this->assertSame($previousValue, $context->getValue());
287 $this->assertSame($previousObject, $context->getObject());
288 $this->assertSame($previousMetadata, $context->getMetadata());
289 $this->assertSame($previousPath, $context->getPropertyPath());
290 $this->assertSame($previousGroup, $context->getGroup());
293 $callback2 = function ($value, ExecutionContextInterface $context) use ($entity) {
294 $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
295 $this->assertNull($context->getPropertyName());
296 $this->assertSame('subpath[key]', $context->getPropertyPath());
297 $this->assertSame('Group', $context->getGroup());
298 $this->assertSame($this->referenceMetadata, $context->getMetadata());
299 $this->assertSame($entity, $context->getRoot());
300 $this->assertSame($entity->reference, $context->getValue());
301 $this->assertSame($entity->reference, $value);
303 $context->addViolation('Message %param%', array('%param%' => 'value'));
306 $this->metadata->addConstraint(new Callback(array(
307 'callback' => $callback1,
310 $this->referenceMetadata->addConstraint(new Callback(array(
311 'callback' => $callback2,
315 $violations = $this->validator->validate($entity, new Valid(), 'Group');
317 /* @var ConstraintViolationInterface[] $violations */
318 $this->assertCount(1, $violations);
319 $this->assertSame('Message value', $violations[0]->getMessage());
320 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
321 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
322 $this->assertSame('subpath[key]', $violations[0]->getPropertyPath());
323 $this->assertSame($entity, $violations[0]->getRoot());
324 $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
325 $this->assertNull($violations[0]->getPlural());
326 $this->assertNull($violations[0]->getCode());
329 public function testTraverseTraversableByDefault()
331 $entity = new Entity();
332 $traversable = new \ArrayIterator(array('key' => $entity));
334 $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) {
335 $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
336 $this->assertNull($context->getPropertyName());
337 $this->assertSame('[key]', $context->getPropertyPath());
338 $this->assertSame('Group', $context->getGroup());
339 $this->assertSame($this->metadata, $context->getMetadata());
340 $this->assertSame($traversable, $context->getRoot());
341 $this->assertSame($entity, $context->getValue());
342 $this->assertSame($entity, $value);
344 $context->addViolation('Message %param%', array('%param%' => 'value'));
347 $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
348 $this->metadata->addConstraint(new Callback(array(
349 'callback' => $callback,
353 $violations = $this->validate($traversable, new Valid(), 'Group');
355 /* @var ConstraintViolationInterface[] $violations */
356 $this->assertCount(1, $violations);
357 $this->assertSame('Message value', $violations[0]->getMessage());
358 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
359 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
360 $this->assertSame('[key]', $violations[0]->getPropertyPath());
361 $this->assertSame($traversable, $violations[0]->getRoot());
362 $this->assertSame($entity, $violations[0]->getInvalidValue());
363 $this->assertNull($violations[0]->getPlural());
364 $this->assertNull($violations[0]->getCode());
367 public function testTraversalEnabledOnClass()
369 $entity = new Entity();
370 $traversable = new \ArrayIterator(array('key' => $entity));
372 $callback = function ($value, ExecutionContextInterface $context) {
373 $context->addViolation('Message');
376 $traversableMetadata = new ClassMetadata('ArrayIterator');
377 $traversableMetadata->addConstraint(new Traverse(true));
379 $this->metadataFactory->addMetadata($traversableMetadata);
380 $this->metadata->addConstraint(new Callback(array(
381 'callback' => $callback,
385 $violations = $this->validate($traversable, new Valid(), 'Group');
387 /* @var ConstraintViolationInterface[] $violations */
388 $this->assertCount(1, $violations);
391 public function testTraversalDisabledOnClass()
393 $entity = new Entity();
394 $traversable = new \ArrayIterator(array('key' => $entity));
396 $callback = function ($value, ExecutionContextInterface $context) {
397 $this->fail('Should not be called');
400 $traversableMetadata = new ClassMetadata('ArrayIterator');
401 $traversableMetadata->addConstraint(new Traverse(false));
403 $this->metadataFactory->addMetadata($traversableMetadata);
404 $this->metadata->addConstraint(new Callback(array(
405 'callback' => $callback,
409 $violations = $this->validate($traversable, new Valid(), 'Group');
411 /* @var ConstraintViolationInterface[] $violations */
412 $this->assertCount(0, $violations);
416 * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
418 public function testExpectTraversableIfTraversalEnabledOnClass()
420 $entity = new Entity();
422 $this->metadata->addConstraint(new Traverse(true));
424 $this->validator->validate($entity);
427 public function testReferenceTraversalDisabledOnClass()
429 $entity = new Entity();
430 $entity->reference = new \ArrayIterator(array('key' => new Reference()));
432 $callback = function ($value, ExecutionContextInterface $context) {
433 $this->fail('Should not be called');
436 $traversableMetadata = new ClassMetadata('ArrayIterator');
437 $traversableMetadata->addConstraint(new Traverse(false));
439 $this->metadataFactory->addMetadata($traversableMetadata);
440 $this->referenceMetadata->addConstraint(new Callback(array(
441 'callback' => $callback,
444 $this->metadata->addPropertyConstraint('reference', new Valid());
446 $violations = $this->validate($entity, new Valid(), 'Group');
448 /* @var ConstraintViolationInterface[] $violations */
449 $this->assertCount(0, $violations);
452 public function testReferenceTraversalEnabledOnReferenceDisabledOnClass()
454 $entity = new Entity();
455 $entity->reference = new \ArrayIterator(array('key' => new Reference()));
457 $callback = function ($value, ExecutionContextInterface $context) {
458 $this->fail('Should not be called');
461 $traversableMetadata = new ClassMetadata('ArrayIterator');
462 $traversableMetadata->addConstraint(new Traverse(false));
464 $this->metadataFactory->addMetadata($traversableMetadata);
465 $this->referenceMetadata->addConstraint(new Callback(array(
466 'callback' => $callback,
469 $this->metadata->addPropertyConstraint('reference', new Valid(array(
473 $violations = $this->validate($entity, new Valid(), 'Group');
475 /* @var ConstraintViolationInterface[] $violations */
476 $this->assertCount(0, $violations);
479 public function testReferenceTraversalDisabledOnReferenceEnabledOnClass()
481 $entity = new Entity();
482 $entity->reference = new \ArrayIterator(array('key' => new Reference()));
484 $callback = function ($value, ExecutionContextInterface $context) {
485 $this->fail('Should not be called');
488 $traversableMetadata = new ClassMetadata('ArrayIterator');
489 $traversableMetadata->addConstraint(new Traverse(true));
491 $this->metadataFactory->addMetadata($traversableMetadata);
492 $this->referenceMetadata->addConstraint(new Callback(array(
493 'callback' => $callback,
496 $this->metadata->addPropertyConstraint('reference', new Valid(array(
500 $violations = $this->validate($entity, new Valid(), 'Group');
502 /* @var ConstraintViolationInterface[] $violations */
503 $this->assertCount(0, $violations);
506 public function testAddCustomizedViolation()
508 $entity = new Entity();
510 $callback = function ($value, ExecutionContextInterface $context) {
511 $context->buildViolation('Message %param%')
512 ->setParameter('%param%', 'value')
513 ->setInvalidValue('Invalid value')
519 $this->metadata->addConstraint(new Callback($callback));
521 $violations = $this->validator->validate($entity);
523 /* @var ConstraintViolationInterface[] $violations */
524 $this->assertCount(1, $violations);
525 $this->assertSame('Message value', $violations[0]->getMessage());
526 $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
527 $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
528 $this->assertSame('', $violations[0]->getPropertyPath());
529 $this->assertSame($entity, $violations[0]->getRoot());
530 $this->assertSame('Invalid value', $violations[0]->getInvalidValue());
531 $this->assertSame(2, $violations[0]->getPlural());
532 $this->assertSame(42, $violations[0]->getCode());
535 public function testNoDuplicateValidationIfClassConstraintInMultipleGroups()
537 $entity = new Entity();
539 $callback = function ($value, ExecutionContextInterface $context) {
540 $context->addViolation('Message');
543 $this->metadata->addConstraint(new Callback(array(
544 'callback' => $callback,
545 'groups' => array('Group 1', 'Group 2'),
548 $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
550 /* @var ConstraintViolationInterface[] $violations */
551 $this->assertCount(1, $violations);
554 public function testNoDuplicateValidationIfPropertyConstraintInMultipleGroups()
556 $entity = new Entity();
558 $callback = function ($value, ExecutionContextInterface $context) {
559 $context->addViolation('Message');
562 $this->metadata->addPropertyConstraint('firstName', new Callback(array(
563 'callback' => $callback,
564 'groups' => array('Group 1', 'Group 2'),
567 $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
569 /* @var ConstraintViolationInterface[] $violations */
570 $this->assertCount(1, $violations);
574 * @expectedException \Symfony\Component\Validator\Exception\RuntimeException
576 public function testValidateFailsIfNoConstraintsAndNoObjectOrArray()
578 $this->validate('Foobar');
581 public function testAccessCurrentObject()
584 $entity = new Entity();
585 $entity->firstName = 'Bernhard';
587 $callback = function ($value, ExecutionContextInterface $context) use ($entity, &$called) {
589 $this->assertSame($entity, $context->getObject());
592 $this->metadata->addConstraint(new Callback($callback));
593 $this->metadata->addPropertyConstraint('firstName', new Callback($callback));
595 $this->validator->validate($entity);
597 $this->assertTrue($called);
600 public function testInitializeObjectsOnFirstValidation()
602 $entity = new Entity();
603 $entity->initialized = false;
605 // prepare initializers that set "initialized" to true
606 $initializer1 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock();
607 $initializer2 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock();
609 $initializer1->expects($this->once())
610 ->method('initialize')
612 ->will($this->returnCallback(function ($object) {
613 $object->initialized = true;
616 $initializer2->expects($this->once())
617 ->method('initialize')
620 $this->validator = $this->createValidator($this->metadataFactory, array(
625 // prepare constraint which
626 // * checks that "initialized" is set to true
627 // * validates the object again
628 $callback = function ($object, ExecutionContextInterface $context) {
629 $this->assertTrue($object->initialized);
631 // validate again in same group
632 $validator = $context->getValidator()->inContext($context);
634 $validator->validate($object);
636 // validate again in other group
637 $validator->validate($object, null, 'SomeGroup');
640 $this->metadata->addConstraint(new Callback($callback));
642 $this->validate($entity);
644 $this->assertTrue($entity->initialized);
647 public function testPassConstraintToViolation()
649 $constraint = new FailingConstraint();
650 $violations = $this->validate('Foobar', $constraint);
652 $this->assertCount(1, $violations);
653 $this->assertSame($constraint, $violations[0]->getConstraint());
656 public function testCollectionConstraitViolationHasCorrectContext()
662 // Missing field must not be the first in the collection validation
663 $constraint = new Collection(array(
664 'foo' => new NotNull(),
665 'bar' => new NotNull(),
668 $violations = $this->validate($data, $constraint);
670 $this->assertCount(1, $violations);
671 $this->assertSame($constraint, $violations[0]->getConstraint());