77a7cc6c2bdc6815d8208ac32aabc9b00278301d
[yaffs-website] / vendor / symfony / validator / Tests / Validator / AbstractTest.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Validator\Tests\Validator;
13
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;
28
29 /**
30  * @author Bernhard Schussek <bschussek@gmail.com>
31  */
32 abstract class AbstractTest extends AbstractValidatorTest
33 {
34     /**
35      * @var ValidatorInterface
36      */
37     protected $validator;
38
39     /**
40      * @param MetadataFactoryInterface $metadataFactory
41      * @param array                    $objectInitializers
42      *
43      * @return ValidatorInterface
44      */
45     abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());
46
47     protected function setUp()
48     {
49         parent::setUp();
50
51         $this->validator = $this->createValidator($this->metadataFactory);
52     }
53
54     protected function validate($value, $constraints = null, $groups = null)
55     {
56         return $this->validator->validate($value, $constraints, $groups);
57     }
58
59     protected function validateProperty($object, $propertyName, $groups = null)
60     {
61         return $this->validator->validateProperty($object, $propertyName, $groups);
62     }
63
64     protected function validatePropertyValue($object, $propertyName, $value, $groups = null)
65     {
66         return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups);
67     }
68
69     public function testValidateConstraintWithoutGroup()
70     {
71         $violations = $this->validator->validate(null, new NotNull());
72
73         $this->assertCount(1, $violations);
74     }
75
76     public function testValidateWithEmptyArrayAsConstraint()
77     {
78         $violations = $this->validator->validate('value', array());
79         $this->assertCount(0, $violations);
80     }
81
82     public function testGroupSequenceAbortsAfterFailedGroup()
83     {
84         $entity = new Entity();
85
86         $callback1 = function ($value, ExecutionContextInterface $context) {
87             $context->addViolation('Message 1');
88         };
89         $callback2 = function ($value, ExecutionContextInterface $context) {
90             $context->addViolation('Message 2');
91         };
92
93         $this->metadata->addConstraint(new Callback(array(
94             'callback' => function () {},
95             'groups' => 'Group 1',
96         )));
97         $this->metadata->addConstraint(new Callback(array(
98             'callback' => $callback1,
99             'groups' => 'Group 2',
100         )));
101         $this->metadata->addConstraint(new Callback(array(
102             'callback' => $callback2,
103             'groups' => 'Group 3',
104         )));
105
106         $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3'));
107         $violations = $this->validator->validate($entity, new Valid(), $sequence);
108
109         /* @var ConstraintViolationInterface[] $violations */
110         $this->assertCount(1, $violations);
111         $this->assertSame('Message 1', $violations[0]->getMessage());
112     }
113
114     public function testGroupSequenceIncludesReferences()
115     {
116         $entity = new Entity();
117         $entity->reference = new Reference();
118
119         $callback1 = function ($value, ExecutionContextInterface $context) {
120             $context->addViolation('Reference violation 1');
121         };
122         $callback2 = function ($value, ExecutionContextInterface $context) {
123             $context->addViolation('Reference violation 2');
124         };
125
126         $this->metadata->addPropertyConstraint('reference', new Valid());
127         $this->referenceMetadata->addConstraint(new Callback(array(
128             'callback' => $callback1,
129             'groups' => 'Group 1',
130         )));
131         $this->referenceMetadata->addConstraint(new Callback(array(
132             'callback' => $callback2,
133             'groups' => 'Group 2',
134         )));
135
136         $sequence = new GroupSequence(array('Group 1', 'Entity'));
137         $violations = $this->validator->validate($entity, new Valid(), $sequence);
138
139         /* @var ConstraintViolationInterface[] $violations */
140         $this->assertCount(1, $violations);
141         $this->assertSame('Reference violation 1', $violations[0]->getMessage());
142     }
143
144     public function testValidateInSeparateContext()
145     {
146         $entity = new Entity();
147         $entity->reference = new Reference();
148
149         $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) {
150             $violations = $context
151                 ->getValidator()
152                 // Since the validator is not context aware, the group must
153                 // be passed explicitly
154                 ->validate($value->reference, new Valid(), 'Group')
155             ;
156
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());
163
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());
169
170             // Verify that this method is called
171             $context->addViolation('Separate violation');
172         };
173
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);
183
184             $context->addViolation('Message %param%', array('%param%' => 'value'));
185         };
186
187         $this->metadata->addConstraint(new Callback(array(
188             'callback' => $callback1,
189             'groups' => 'Group',
190         )));
191         $this->referenceMetadata->addConstraint(new Callback(array(
192             'callback' => $callback2,
193             'groups' => 'Group',
194         )));
195
196         $violations = $this->validator->validate($entity, new Valid(), 'Group');
197
198         /* @var ConstraintViolationInterface[] $violations */
199         $this->assertCount(1, $violations);
200         $this->assertSame('Separate violation', $violations[0]->getMessage());
201     }
202
203     public function testValidateInContext()
204     {
205         $entity = new Entity();
206         $entity->reference = new Reference();
207
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();
214
215             $context
216                 ->getValidator()
217                 ->inContext($context)
218                 ->atPath('subpath')
219                 ->validate($value->reference)
220             ;
221
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());
228         };
229
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);
239
240             $context->addViolation('Message %param%', array('%param%' => 'value'));
241         };
242
243         $this->metadata->addConstraint(new Callback(array(
244             'callback' => $callback1,
245             'groups' => 'Group',
246         )));
247         $this->referenceMetadata->addConstraint(new Callback(array(
248             'callback' => $callback2,
249             'groups' => 'Group',
250         )));
251
252         $violations = $this->validator->validate($entity, new Valid(), 'Group');
253
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());
264     }
265
266     public function testValidateArrayInContext()
267     {
268         $entity = new Entity();
269         $entity->reference = new Reference();
270
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();
277
278             $context
279                 ->getValidator()
280                 ->inContext($context)
281                 ->atPath('subpath')
282                 ->validate(array('key' => $value->reference))
283             ;
284
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());
291         };
292
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);
302
303             $context->addViolation('Message %param%', array('%param%' => 'value'));
304         };
305
306         $this->metadata->addConstraint(new Callback(array(
307             'callback' => $callback1,
308             'groups' => 'Group',
309         )));
310         $this->referenceMetadata->addConstraint(new Callback(array(
311             'callback' => $callback2,
312             'groups' => 'Group',
313         )));
314
315         $violations = $this->validator->validate($entity, new Valid(), 'Group');
316
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());
327     }
328
329     public function testTraverseTraversableByDefault()
330     {
331         $entity = new Entity();
332         $traversable = new \ArrayIterator(array('key' => $entity));
333
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);
343
344             $context->addViolation('Message %param%', array('%param%' => 'value'));
345         };
346
347         $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
348         $this->metadata->addConstraint(new Callback(array(
349             'callback' => $callback,
350             'groups' => 'Group',
351         )));
352
353         $violations = $this->validate($traversable, new Valid(), 'Group');
354
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());
365     }
366
367     public function testTraversalEnabledOnClass()
368     {
369         $entity = new Entity();
370         $traversable = new \ArrayIterator(array('key' => $entity));
371
372         $callback = function ($value, ExecutionContextInterface $context) {
373             $context->addViolation('Message');
374         };
375
376         $traversableMetadata = new ClassMetadata('ArrayIterator');
377         $traversableMetadata->addConstraint(new Traverse(true));
378
379         $this->metadataFactory->addMetadata($traversableMetadata);
380         $this->metadata->addConstraint(new Callback(array(
381             'callback' => $callback,
382             'groups' => 'Group',
383         )));
384
385         $violations = $this->validate($traversable, new Valid(), 'Group');
386
387         /* @var ConstraintViolationInterface[] $violations */
388         $this->assertCount(1, $violations);
389     }
390
391     public function testTraversalDisabledOnClass()
392     {
393         $entity = new Entity();
394         $traversable = new \ArrayIterator(array('key' => $entity));
395
396         $callback = function ($value, ExecutionContextInterface $context) {
397             $this->fail('Should not be called');
398         };
399
400         $traversableMetadata = new ClassMetadata('ArrayIterator');
401         $traversableMetadata->addConstraint(new Traverse(false));
402
403         $this->metadataFactory->addMetadata($traversableMetadata);
404         $this->metadata->addConstraint(new Callback(array(
405             'callback' => $callback,
406             'groups' => 'Group',
407         )));
408
409         $violations = $this->validate($traversable, new Valid(), 'Group');
410
411         /* @var ConstraintViolationInterface[] $violations */
412         $this->assertCount(0, $violations);
413     }
414
415     /**
416      * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
417      */
418     public function testExpectTraversableIfTraversalEnabledOnClass()
419     {
420         $entity = new Entity();
421
422         $this->metadata->addConstraint(new Traverse(true));
423
424         $this->validator->validate($entity);
425     }
426
427     public function testReferenceTraversalDisabledOnClass()
428     {
429         $entity = new Entity();
430         $entity->reference = new \ArrayIterator(array('key' => new Reference()));
431
432         $callback = function ($value, ExecutionContextInterface $context) {
433             $this->fail('Should not be called');
434         };
435
436         $traversableMetadata = new ClassMetadata('ArrayIterator');
437         $traversableMetadata->addConstraint(new Traverse(false));
438
439         $this->metadataFactory->addMetadata($traversableMetadata);
440         $this->referenceMetadata->addConstraint(new Callback(array(
441             'callback' => $callback,
442             'groups' => 'Group',
443         )));
444         $this->metadata->addPropertyConstraint('reference', new Valid());
445
446         $violations = $this->validate($entity, new Valid(), 'Group');
447
448         /* @var ConstraintViolationInterface[] $violations */
449         $this->assertCount(0, $violations);
450     }
451
452     public function testReferenceTraversalEnabledOnReferenceDisabledOnClass()
453     {
454         $entity = new Entity();
455         $entity->reference = new \ArrayIterator(array('key' => new Reference()));
456
457         $callback = function ($value, ExecutionContextInterface $context) {
458             $this->fail('Should not be called');
459         };
460
461         $traversableMetadata = new ClassMetadata('ArrayIterator');
462         $traversableMetadata->addConstraint(new Traverse(false));
463
464         $this->metadataFactory->addMetadata($traversableMetadata);
465         $this->referenceMetadata->addConstraint(new Callback(array(
466             'callback' => $callback,
467             'groups' => 'Group',
468         )));
469         $this->metadata->addPropertyConstraint('reference', new Valid(array(
470             'traverse' => true,
471         )));
472
473         $violations = $this->validate($entity, new Valid(), 'Group');
474
475         /* @var ConstraintViolationInterface[] $violations */
476         $this->assertCount(0, $violations);
477     }
478
479     public function testReferenceTraversalDisabledOnReferenceEnabledOnClass()
480     {
481         $entity = new Entity();
482         $entity->reference = new \ArrayIterator(array('key' => new Reference()));
483
484         $callback = function ($value, ExecutionContextInterface $context) {
485             $this->fail('Should not be called');
486         };
487
488         $traversableMetadata = new ClassMetadata('ArrayIterator');
489         $traversableMetadata->addConstraint(new Traverse(true));
490
491         $this->metadataFactory->addMetadata($traversableMetadata);
492         $this->referenceMetadata->addConstraint(new Callback(array(
493             'callback' => $callback,
494             'groups' => 'Group',
495         )));
496         $this->metadata->addPropertyConstraint('reference', new Valid(array(
497             'traverse' => false,
498         )));
499
500         $violations = $this->validate($entity, new Valid(), 'Group');
501
502         /* @var ConstraintViolationInterface[] $violations */
503         $this->assertCount(0, $violations);
504     }
505
506     public function testAddCustomizedViolation()
507     {
508         $entity = new Entity();
509
510         $callback = function ($value, ExecutionContextInterface $context) {
511             $context->buildViolation('Message %param%')
512                 ->setParameter('%param%', 'value')
513                 ->setInvalidValue('Invalid value')
514                 ->setPlural(2)
515                 ->setCode(42)
516                 ->addViolation();
517         };
518
519         $this->metadata->addConstraint(new Callback($callback));
520
521         $violations = $this->validator->validate($entity);
522
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());
533     }
534
535     public function testNoDuplicateValidationIfClassConstraintInMultipleGroups()
536     {
537         $entity = new Entity();
538
539         $callback = function ($value, ExecutionContextInterface $context) {
540             $context->addViolation('Message');
541         };
542
543         $this->metadata->addConstraint(new Callback(array(
544             'callback' => $callback,
545             'groups' => array('Group 1', 'Group 2'),
546         )));
547
548         $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
549
550         /* @var ConstraintViolationInterface[] $violations */
551         $this->assertCount(1, $violations);
552     }
553
554     public function testNoDuplicateValidationIfPropertyConstraintInMultipleGroups()
555     {
556         $entity = new Entity();
557
558         $callback = function ($value, ExecutionContextInterface $context) {
559             $context->addViolation('Message');
560         };
561
562         $this->metadata->addPropertyConstraint('firstName', new Callback(array(
563             'callback' => $callback,
564             'groups' => array('Group 1', 'Group 2'),
565         )));
566
567         $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
568
569         /* @var ConstraintViolationInterface[] $violations */
570         $this->assertCount(1, $violations);
571     }
572
573     /**
574      * @expectedException \Symfony\Component\Validator\Exception\RuntimeException
575      */
576     public function testValidateFailsIfNoConstraintsAndNoObjectOrArray()
577     {
578         $this->validate('Foobar');
579     }
580
581     public function testAccessCurrentObject()
582     {
583         $called = false;
584         $entity = new Entity();
585         $entity->firstName = 'Bernhard';
586
587         $callback = function ($value, ExecutionContextInterface $context) use ($entity, &$called) {
588             $called = true;
589             $this->assertSame($entity, $context->getObject());
590         };
591
592         $this->metadata->addConstraint(new Callback($callback));
593         $this->metadata->addPropertyConstraint('firstName', new Callback($callback));
594
595         $this->validator->validate($entity);
596
597         $this->assertTrue($called);
598     }
599
600     public function testInitializeObjectsOnFirstValidation()
601     {
602         $entity = new Entity();
603         $entity->initialized = false;
604
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();
608
609         $initializer1->expects($this->once())
610             ->method('initialize')
611             ->with($entity)
612             ->will($this->returnCallback(function ($object) {
613                 $object->initialized = true;
614             }));
615
616         $initializer2->expects($this->once())
617             ->method('initialize')
618             ->with($entity);
619
620         $this->validator = $this->createValidator($this->metadataFactory, array(
621             $initializer1,
622             $initializer2,
623         ));
624
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);
630
631             // validate again in same group
632             $validator = $context->getValidator()->inContext($context);
633
634             $validator->validate($object);
635
636             // validate again in other group
637             $validator->validate($object, null, 'SomeGroup');
638         };
639
640         $this->metadata->addConstraint(new Callback($callback));
641
642         $this->validate($entity);
643
644         $this->assertTrue($entity->initialized);
645     }
646
647     public function testPassConstraintToViolation()
648     {
649         $constraint = new FailingConstraint();
650         $violations = $this->validate('Foobar', $constraint);
651
652         $this->assertCount(1, $violations);
653         $this->assertSame($constraint, $violations[0]->getConstraint());
654     }
655
656     public function testCollectionConstraitViolationHasCorrectContext()
657     {
658         $data = array(
659             'foo' => 'fooValue',
660         );
661
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(),
666         ));
667
668         $violations = $this->validate($data, $constraint);
669
670         $this->assertCount(1, $violations);
671         $this->assertSame($constraint, $violations[0]->getConstraint());
672     }
673 }