360d84f98516704d781d2c66d7102a10a3a09a88
[yaffs-website] / vendor / symfony / validator / Tests / Validator / AbstractValidatorTest.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 PHPUnit\Framework\TestCase;
15 use Symfony\Component\Validator\Constraints\Callback;
16 use Symfony\Component\Validator\Constraints\GroupSequence;
17 use Symfony\Component\Validator\Constraints\Valid;
18 use Symfony\Component\Validator\ConstraintViolationInterface;
19 use Symfony\Component\Validator\Context\ExecutionContextInterface;
20 use Symfony\Component\Validator\Mapping\ClassMetadata;
21 use Symfony\Component\Validator\Tests\Fixtures\Entity;
22 use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
23 use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity;
24 use Symfony\Component\Validator\Tests\Fixtures\Reference;
25
26 /**
27  * @author Bernhard Schussek <bschussek@gmail.com>
28  */
29 abstract class AbstractValidatorTest extends TestCase
30 {
31     const ENTITY_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
32
33     const REFERENCE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Reference';
34
35     /**
36      * @var FakeMetadataFactory
37      */
38     public $metadataFactory;
39
40     /**
41      * @var ClassMetadata
42      */
43     public $metadata;
44
45     /**
46      * @var ClassMetadata
47      */
48     public $referenceMetadata;
49
50     protected function setUp()
51     {
52         $this->metadataFactory = new FakeMetadataFactory();
53         $this->metadata = new ClassMetadata(self::ENTITY_CLASS);
54         $this->referenceMetadata = new ClassMetadata(self::REFERENCE_CLASS);
55         $this->metadataFactory->addMetadata($this->metadata);
56         $this->metadataFactory->addMetadata($this->referenceMetadata);
57     }
58
59     protected function tearDown()
60     {
61         $this->metadataFactory = null;
62         $this->metadata = null;
63         $this->referenceMetadata = null;
64     }
65
66     abstract protected function validate($value, $constraints = null, $groups = null);
67
68     abstract protected function validateProperty($object, $propertyName, $groups = null);
69
70     abstract protected function validatePropertyValue($object, $propertyName, $value, $groups = null);
71
72     public function testValidate()
73     {
74         $callback = function ($value, ExecutionContextInterface $context) {
75             $this->assertNull($context->getClassName());
76             $this->assertNull($context->getPropertyName());
77             $this->assertSame('', $context->getPropertyPath());
78             $this->assertSame('Group', $context->getGroup());
79             $this->assertSame('Bernhard', $context->getRoot());
80             $this->assertSame('Bernhard', $context->getValue());
81             $this->assertSame('Bernhard', $value);
82
83             $context->addViolation('Message %param%', array('%param%' => 'value'));
84         };
85
86         $constraint = new Callback(array(
87             'callback' => $callback,
88             'groups' => 'Group',
89         ));
90
91         $violations = $this->validate('Bernhard', $constraint, 'Group');
92
93         /* @var ConstraintViolationInterface[] $violations */
94         $this->assertCount(1, $violations);
95         $this->assertSame('Message value', $violations[0]->getMessage());
96         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
97         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
98         $this->assertSame('', $violations[0]->getPropertyPath());
99         $this->assertSame('Bernhard', $violations[0]->getRoot());
100         $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
101         $this->assertNull($violations[0]->getPlural());
102         $this->assertNull($violations[0]->getCode());
103     }
104
105     public function testClassConstraint()
106     {
107         $entity = new Entity();
108
109         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
110             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
111             $this->assertNull($context->getPropertyName());
112             $this->assertSame('', $context->getPropertyPath());
113             $this->assertSame('Group', $context->getGroup());
114             $this->assertSame($this->metadata, $context->getMetadata());
115             $this->assertSame($entity, $context->getRoot());
116             $this->assertSame($entity, $context->getValue());
117             $this->assertSame($entity, $value);
118
119             $context->addViolation('Message %param%', array('%param%' => 'value'));
120         };
121
122         $this->metadata->addConstraint(new Callback(array(
123             'callback' => $callback,
124             'groups' => 'Group',
125         )));
126
127         $violations = $this->validate($entity, null, 'Group');
128
129         /* @var ConstraintViolationInterface[] $violations */
130         $this->assertCount(1, $violations);
131         $this->assertSame('Message value', $violations[0]->getMessage());
132         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
133         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
134         $this->assertSame('', $violations[0]->getPropertyPath());
135         $this->assertSame($entity, $violations[0]->getRoot());
136         $this->assertSame($entity, $violations[0]->getInvalidValue());
137         $this->assertNull($violations[0]->getPlural());
138         $this->assertNull($violations[0]->getCode());
139     }
140
141     public function testPropertyConstraint()
142     {
143         $entity = new Entity();
144         $entity->firstName = 'Bernhard';
145
146         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
147             $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName');
148
149             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
150             $this->assertSame('firstName', $context->getPropertyName());
151             $this->assertSame('firstName', $context->getPropertyPath());
152             $this->assertSame('Group', $context->getGroup());
153             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
154             $this->assertSame($entity, $context->getRoot());
155             $this->assertSame('Bernhard', $context->getValue());
156             $this->assertSame('Bernhard', $value);
157
158             $context->addViolation('Message %param%', array('%param%' => 'value'));
159         };
160
161         $this->metadata->addPropertyConstraint('firstName', new Callback(array(
162             'callback' => $callback,
163             'groups' => 'Group',
164         )));
165
166         $violations = $this->validate($entity, null, 'Group');
167
168         /* @var ConstraintViolationInterface[] $violations */
169         $this->assertCount(1, $violations);
170         $this->assertSame('Message value', $violations[0]->getMessage());
171         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
172         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
173         $this->assertSame('firstName', $violations[0]->getPropertyPath());
174         $this->assertSame($entity, $violations[0]->getRoot());
175         $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
176         $this->assertNull($violations[0]->getPlural());
177         $this->assertNull($violations[0]->getCode());
178     }
179
180     public function testGetterConstraint()
181     {
182         $entity = new Entity();
183         $entity->setLastName('Schussek');
184
185         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
186             $propertyMetadatas = $this->metadata->getPropertyMetadata('lastName');
187
188             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
189             $this->assertSame('lastName', $context->getPropertyName());
190             $this->assertSame('lastName', $context->getPropertyPath());
191             $this->assertSame('Group', $context->getGroup());
192             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
193             $this->assertSame($entity, $context->getRoot());
194             $this->assertSame('Schussek', $context->getValue());
195             $this->assertSame('Schussek', $value);
196
197             $context->addViolation('Message %param%', array('%param%' => 'value'));
198         };
199
200         $this->metadata->addGetterConstraint('lastName', new Callback(array(
201             'callback' => $callback,
202             'groups' => 'Group',
203         )));
204
205         $violations = $this->validate($entity, null, 'Group');
206
207         /* @var ConstraintViolationInterface[] $violations */
208         $this->assertCount(1, $violations);
209         $this->assertSame('Message value', $violations[0]->getMessage());
210         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
211         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
212         $this->assertSame('lastName', $violations[0]->getPropertyPath());
213         $this->assertSame($entity, $violations[0]->getRoot());
214         $this->assertSame('Schussek', $violations[0]->getInvalidValue());
215         $this->assertNull($violations[0]->getPlural());
216         $this->assertNull($violations[0]->getCode());
217     }
218
219     public function testArray()
220     {
221         $entity = new Entity();
222         $array = array('key' => $entity);
223
224         $callback = function ($value, ExecutionContextInterface $context) use ($entity, $array) {
225             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
226             $this->assertNull($context->getPropertyName());
227             $this->assertSame('[key]', $context->getPropertyPath());
228             $this->assertSame('Group', $context->getGroup());
229             $this->assertSame($this->metadata, $context->getMetadata());
230             $this->assertSame($array, $context->getRoot());
231             $this->assertSame($entity, $context->getValue());
232             $this->assertSame($entity, $value);
233
234             $context->addViolation('Message %param%', array('%param%' => 'value'));
235         };
236
237         $this->metadata->addConstraint(new Callback(array(
238             'callback' => $callback,
239             'groups' => 'Group',
240         )));
241
242         $violations = $this->validate($array, null, 'Group');
243
244         /* @var ConstraintViolationInterface[] $violations */
245         $this->assertCount(1, $violations);
246         $this->assertSame('Message value', $violations[0]->getMessage());
247         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
248         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
249         $this->assertSame('[key]', $violations[0]->getPropertyPath());
250         $this->assertSame($array, $violations[0]->getRoot());
251         $this->assertSame($entity, $violations[0]->getInvalidValue());
252         $this->assertNull($violations[0]->getPlural());
253         $this->assertNull($violations[0]->getCode());
254     }
255
256     public function testRecursiveArray()
257     {
258         $entity = new Entity();
259         $array = array(2 => array('key' => $entity));
260
261         $callback = function ($value, ExecutionContextInterface $context) use ($entity, $array) {
262             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
263             $this->assertNull($context->getPropertyName());
264             $this->assertSame('[2][key]', $context->getPropertyPath());
265             $this->assertSame('Group', $context->getGroup());
266             $this->assertSame($this->metadata, $context->getMetadata());
267             $this->assertSame($array, $context->getRoot());
268             $this->assertSame($entity, $context->getValue());
269             $this->assertSame($entity, $value);
270
271             $context->addViolation('Message %param%', array('%param%' => 'value'));
272         };
273
274         $this->metadata->addConstraint(new Callback(array(
275             'callback' => $callback,
276             'groups' => 'Group',
277         )));
278
279         $violations = $this->validate($array, null, 'Group');
280
281         /* @var ConstraintViolationInterface[] $violations */
282         $this->assertCount(1, $violations);
283         $this->assertSame('Message value', $violations[0]->getMessage());
284         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
285         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
286         $this->assertSame('[2][key]', $violations[0]->getPropertyPath());
287         $this->assertSame($array, $violations[0]->getRoot());
288         $this->assertSame($entity, $violations[0]->getInvalidValue());
289         $this->assertNull($violations[0]->getPlural());
290         $this->assertNull($violations[0]->getCode());
291     }
292
293     public function testTraversable()
294     {
295         $entity = new Entity();
296         $traversable = new \ArrayIterator(array('key' => $entity));
297
298         $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) {
299             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
300             $this->assertNull($context->getPropertyName());
301             $this->assertSame('[key]', $context->getPropertyPath());
302             $this->assertSame('Group', $context->getGroup());
303             $this->assertSame($this->metadata, $context->getMetadata());
304             $this->assertSame($traversable, $context->getRoot());
305             $this->assertSame($entity, $context->getValue());
306             $this->assertSame($entity, $value);
307
308             $context->addViolation('Message %param%', array('%param%' => 'value'));
309         };
310
311         $this->metadata->addConstraint(new Callback(array(
312             'callback' => $callback,
313             'groups' => 'Group',
314         )));
315
316         $violations = $this->validate($traversable, null, 'Group');
317
318         /* @var ConstraintViolationInterface[] $violations */
319         $this->assertCount(1, $violations);
320         $this->assertSame('Message value', $violations[0]->getMessage());
321         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
322         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
323         $this->assertSame('[key]', $violations[0]->getPropertyPath());
324         $this->assertSame($traversable, $violations[0]->getRoot());
325         $this->assertSame($entity, $violations[0]->getInvalidValue());
326         $this->assertNull($violations[0]->getPlural());
327         $this->assertNull($violations[0]->getCode());
328     }
329
330     public function testRecursiveTraversable()
331     {
332         $entity = new Entity();
333         $traversable = new \ArrayIterator(array(
334             2 => new \ArrayIterator(array('key' => $entity)),
335         ));
336
337         $callback = function ($value, ExecutionContextInterface $context) use ($entity, $traversable) {
338             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
339             $this->assertNull($context->getPropertyName());
340             $this->assertSame('[2][key]', $context->getPropertyPath());
341             $this->assertSame('Group', $context->getGroup());
342             $this->assertSame($this->metadata, $context->getMetadata());
343             $this->assertSame($traversable, $context->getRoot());
344             $this->assertSame($entity, $context->getValue());
345             $this->assertSame($entity, $value);
346
347             $context->addViolation('Message %param%', array('%param%' => 'value'));
348         };
349
350         $this->metadata->addConstraint(new Callback(array(
351             'callback' => $callback,
352             'groups' => 'Group',
353         )));
354
355         $violations = $this->validate($traversable, null, 'Group');
356
357         /* @var ConstraintViolationInterface[] $violations */
358         $this->assertCount(1, $violations);
359         $this->assertSame('Message value', $violations[0]->getMessage());
360         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
361         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
362         $this->assertSame('[2][key]', $violations[0]->getPropertyPath());
363         $this->assertSame($traversable, $violations[0]->getRoot());
364         $this->assertSame($entity, $violations[0]->getInvalidValue());
365         $this->assertNull($violations[0]->getPlural());
366         $this->assertNull($violations[0]->getCode());
367     }
368
369     public function testReferenceClassConstraint()
370     {
371         $entity = new Entity();
372         $entity->reference = new Reference();
373
374         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
375             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
376             $this->assertNull($context->getPropertyName());
377             $this->assertSame('reference', $context->getPropertyPath());
378             $this->assertSame('Group', $context->getGroup());
379             $this->assertSame($this->referenceMetadata, $context->getMetadata());
380             $this->assertSame($entity, $context->getRoot());
381             $this->assertSame($entity->reference, $context->getValue());
382             $this->assertSame($entity->reference, $value);
383
384             $context->addViolation('Message %param%', array('%param%' => 'value'));
385         };
386
387         $this->metadata->addPropertyConstraint('reference', new Valid());
388         $this->referenceMetadata->addConstraint(new Callback(array(
389             'callback' => $callback,
390             'groups' => 'Group',
391         )));
392
393         $violations = $this->validate($entity, null, 'Group');
394
395         /* @var ConstraintViolationInterface[] $violations */
396         $this->assertCount(1, $violations);
397         $this->assertSame('Message value', $violations[0]->getMessage());
398         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
399         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
400         $this->assertSame('reference', $violations[0]->getPropertyPath());
401         $this->assertSame($entity, $violations[0]->getRoot());
402         $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
403         $this->assertNull($violations[0]->getPlural());
404         $this->assertNull($violations[0]->getCode());
405     }
406
407     public function testReferencePropertyConstraint()
408     {
409         $entity = new Entity();
410         $entity->reference = new Reference();
411         $entity->reference->value = 'Foobar';
412
413         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
414             $propertyMetadatas = $this->referenceMetadata->getPropertyMetadata('value');
415
416             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
417             $this->assertSame('value', $context->getPropertyName());
418             $this->assertSame('reference.value', $context->getPropertyPath());
419             $this->assertSame('Group', $context->getGroup());
420             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
421             $this->assertSame($entity, $context->getRoot());
422             $this->assertSame('Foobar', $context->getValue());
423             $this->assertSame('Foobar', $value);
424
425             $context->addViolation('Message %param%', array('%param%' => 'value'));
426         };
427
428         $this->metadata->addPropertyConstraint('reference', new Valid());
429         $this->referenceMetadata->addPropertyConstraint('value', new Callback(array(
430             'callback' => $callback,
431             'groups' => 'Group',
432         )));
433
434         $violations = $this->validate($entity, null, 'Group');
435
436         /* @var ConstraintViolationInterface[] $violations */
437         $this->assertCount(1, $violations);
438         $this->assertSame('Message value', $violations[0]->getMessage());
439         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
440         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
441         $this->assertSame('reference.value', $violations[0]->getPropertyPath());
442         $this->assertSame($entity, $violations[0]->getRoot());
443         $this->assertSame('Foobar', $violations[0]->getInvalidValue());
444         $this->assertNull($violations[0]->getPlural());
445         $this->assertNull($violations[0]->getCode());
446     }
447
448     public function testReferenceGetterConstraint()
449     {
450         $entity = new Entity();
451         $entity->reference = new Reference();
452         $entity->reference->setPrivateValue('Bamboo');
453
454         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
455             $propertyMetadatas = $this->referenceMetadata->getPropertyMetadata('privateValue');
456
457             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
458             $this->assertSame('privateValue', $context->getPropertyName());
459             $this->assertSame('reference.privateValue', $context->getPropertyPath());
460             $this->assertSame('Group', $context->getGroup());
461             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
462             $this->assertSame($entity, $context->getRoot());
463             $this->assertSame('Bamboo', $context->getValue());
464             $this->assertSame('Bamboo', $value);
465
466             $context->addViolation('Message %param%', array('%param%' => 'value'));
467         };
468
469         $this->metadata->addPropertyConstraint('reference', new Valid());
470         $this->referenceMetadata->addPropertyConstraint('privateValue', new Callback(array(
471             'callback' => $callback,
472             'groups' => 'Group',
473         )));
474
475         $violations = $this->validate($entity, null, 'Group');
476
477         /* @var ConstraintViolationInterface[] $violations */
478         $this->assertCount(1, $violations);
479         $this->assertSame('Message value', $violations[0]->getMessage());
480         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
481         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
482         $this->assertSame('reference.privateValue', $violations[0]->getPropertyPath());
483         $this->assertSame($entity, $violations[0]->getRoot());
484         $this->assertSame('Bamboo', $violations[0]->getInvalidValue());
485         $this->assertNull($violations[0]->getPlural());
486         $this->assertNull($violations[0]->getCode());
487     }
488
489     public function testsIgnoreNullReference()
490     {
491         $entity = new Entity();
492         $entity->reference = null;
493
494         $this->metadata->addPropertyConstraint('reference', new Valid());
495
496         $violations = $this->validate($entity);
497
498         /* @var ConstraintViolationInterface[] $violations */
499         $this->assertCount(0, $violations);
500     }
501
502     /**
503      * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
504      */
505     public function testFailOnScalarReferences()
506     {
507         $entity = new Entity();
508         $entity->reference = 'string';
509
510         $this->metadata->addPropertyConstraint('reference', new Valid());
511
512         $this->validate($entity);
513     }
514
515     public function testArrayReference()
516     {
517         $entity = new Entity();
518         $entity->reference = array('key' => new Reference());
519
520         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
521             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
522             $this->assertNull($context->getPropertyName());
523             $this->assertSame('reference[key]', $context->getPropertyPath());
524             $this->assertSame('Group', $context->getGroup());
525             $this->assertSame($this->referenceMetadata, $context->getMetadata());
526             $this->assertSame($entity, $context->getRoot());
527             $this->assertSame($entity->reference['key'], $context->getValue());
528             $this->assertSame($entity->reference['key'], $value);
529
530             $context->addViolation('Message %param%', array('%param%' => 'value'));
531         };
532
533         $this->metadata->addPropertyConstraint('reference', new Valid());
534         $this->referenceMetadata->addConstraint(new Callback(array(
535             'callback' => $callback,
536             'groups' => 'Group',
537         )));
538
539         $violations = $this->validate($entity, null, 'Group');
540
541         /* @var ConstraintViolationInterface[] $violations */
542         $this->assertCount(1, $violations);
543         $this->assertSame('Message value', $violations[0]->getMessage());
544         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
545         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
546         $this->assertSame('reference[key]', $violations[0]->getPropertyPath());
547         $this->assertSame($entity, $violations[0]->getRoot());
548         $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue());
549         $this->assertNull($violations[0]->getPlural());
550         $this->assertNull($violations[0]->getCode());
551     }
552
553     // https://github.com/symfony/symfony/issues/6246
554     public function testRecursiveArrayReference()
555     {
556         $entity = new Entity();
557         $entity->reference = array(2 => array('key' => new Reference()));
558
559         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
560             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
561             $this->assertNull($context->getPropertyName());
562             $this->assertSame('reference[2][key]', $context->getPropertyPath());
563             $this->assertSame('Group', $context->getGroup());
564             $this->assertSame($this->referenceMetadata, $context->getMetadata());
565             $this->assertSame($entity, $context->getRoot());
566             $this->assertSame($entity->reference[2]['key'], $context->getValue());
567             $this->assertSame($entity->reference[2]['key'], $value);
568
569             $context->addViolation('Message %param%', array('%param%' => 'value'));
570         };
571
572         $this->metadata->addPropertyConstraint('reference', new Valid());
573         $this->referenceMetadata->addConstraint(new Callback(array(
574             'callback' => $callback,
575             'groups' => 'Group',
576         )));
577
578         $violations = $this->validate($entity, null, 'Group');
579
580         /* @var ConstraintViolationInterface[] $violations */
581         $this->assertCount(1, $violations);
582         $this->assertSame('Message value', $violations[0]->getMessage());
583         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
584         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
585         $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath());
586         $this->assertSame($entity, $violations[0]->getRoot());
587         $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue());
588         $this->assertNull($violations[0]->getPlural());
589         $this->assertNull($violations[0]->getCode());
590     }
591
592     public function testArrayTraversalCannotBeDisabled()
593     {
594         $entity = new Entity();
595         $entity->reference = array('key' => new Reference());
596
597         $callback = function ($value, ExecutionContextInterface $context) {
598             $context->addViolation('Message %param%', array('%param%' => 'value'));
599         };
600
601         $this->metadata->addPropertyConstraint('reference', new Valid(array(
602             'traverse' => false,
603         )));
604         $this->referenceMetadata->addConstraint(new Callback($callback));
605
606         $violations = $this->validate($entity);
607
608         /* @var ConstraintViolationInterface[] $violations */
609         $this->assertCount(1, $violations);
610     }
611
612     public function testRecursiveArrayTraversalCannotBeDisabled()
613     {
614         $entity = new Entity();
615         $entity->reference = array(2 => array('key' => new Reference()));
616
617         $callback = function ($value, ExecutionContextInterface $context) {
618             $context->addViolation('Message %param%', array('%param%' => 'value'));
619         };
620
621         $this->metadata->addPropertyConstraint('reference', new Valid(array(
622             'traverse' => false,
623         )));
624         $this->referenceMetadata->addConstraint(new Callback($callback));
625
626         $violations = $this->validate($entity);
627
628         /* @var ConstraintViolationInterface[] $violations */
629         $this->assertCount(1, $violations);
630     }
631
632     public function testIgnoreScalarsDuringArrayTraversal()
633     {
634         $entity = new Entity();
635         $entity->reference = array('string', 1234);
636
637         $this->metadata->addPropertyConstraint('reference', new Valid());
638
639         $violations = $this->validate($entity);
640
641         /* @var ConstraintViolationInterface[] $violations */
642         $this->assertCount(0, $violations);
643     }
644
645     public function testIgnoreNullDuringArrayTraversal()
646     {
647         $entity = new Entity();
648         $entity->reference = array(null);
649
650         $this->metadata->addPropertyConstraint('reference', new Valid());
651
652         $violations = $this->validate($entity);
653
654         /* @var ConstraintViolationInterface[] $violations */
655         $this->assertCount(0, $violations);
656     }
657
658     public function testTraversableReference()
659     {
660         $entity = new Entity();
661         $entity->reference = new \ArrayIterator(array('key' => new Reference()));
662
663         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
664             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
665             $this->assertNull($context->getPropertyName());
666             $this->assertSame('reference[key]', $context->getPropertyPath());
667             $this->assertSame('Group', $context->getGroup());
668             $this->assertSame($this->referenceMetadata, $context->getMetadata());
669             $this->assertSame($entity, $context->getRoot());
670             $this->assertSame($entity->reference['key'], $context->getValue());
671             $this->assertSame($entity->reference['key'], $value);
672
673             $context->addViolation('Message %param%', array('%param%' => 'value'));
674         };
675
676         $this->metadata->addPropertyConstraint('reference', new Valid());
677         $this->referenceMetadata->addConstraint(new Callback(array(
678             'callback' => $callback,
679             'groups' => 'Group',
680         )));
681
682         $violations = $this->validate($entity, null, 'Group');
683
684         /* @var ConstraintViolationInterface[] $violations */
685         $this->assertCount(1, $violations);
686         $this->assertSame('Message value', $violations[0]->getMessage());
687         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
688         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
689         $this->assertSame('reference[key]', $violations[0]->getPropertyPath());
690         $this->assertSame($entity, $violations[0]->getRoot());
691         $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue());
692         $this->assertNull($violations[0]->getPlural());
693         $this->assertNull($violations[0]->getCode());
694     }
695
696     public function testDisableTraversableTraversal()
697     {
698         $entity = new Entity();
699         $entity->reference = new \ArrayIterator(array('key' => new Reference()));
700
701         $callback = function ($value, ExecutionContextInterface $context) {
702             $context->addViolation('Message %param%', array('%param%' => 'value'));
703         };
704
705         $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
706         $this->metadata->addPropertyConstraint('reference', new Valid(array(
707             'traverse' => false,
708         )));
709         $this->referenceMetadata->addConstraint(new Callback($callback));
710
711         $violations = $this->validate($entity);
712
713         /* @var ConstraintViolationInterface[] $violations */
714         $this->assertCount(0, $violations);
715     }
716
717     /**
718      * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
719      */
720     public function testMetadataMustExistIfTraversalIsDisabled()
721     {
722         $entity = new Entity();
723         $entity->reference = new \ArrayIterator();
724
725         $this->metadata->addPropertyConstraint('reference', new Valid(array(
726             'traverse' => false,
727         )));
728
729         $this->validate($entity);
730     }
731
732     public function testEnableRecursiveTraversableTraversal()
733     {
734         $entity = new Entity();
735         $entity->reference = new \ArrayIterator(array(
736             2 => new \ArrayIterator(array('key' => new Reference())),
737         ));
738
739         $callback = function ($value, ExecutionContextInterface $context) use ($entity) {
740             $this->assertSame($this::REFERENCE_CLASS, $context->getClassName());
741             $this->assertNull($context->getPropertyName());
742             $this->assertSame('reference[2][key]', $context->getPropertyPath());
743             $this->assertSame('Group', $context->getGroup());
744             $this->assertSame($this->referenceMetadata, $context->getMetadata());
745             $this->assertSame($entity, $context->getRoot());
746             $this->assertSame($entity->reference[2]['key'], $context->getValue());
747             $this->assertSame($entity->reference[2]['key'], $value);
748
749             $context->addViolation('Message %param%', array('%param%' => 'value'));
750         };
751
752         $this->metadata->addPropertyConstraint('reference', new Valid(array(
753             'traverse' => true,
754         )));
755         $this->referenceMetadata->addConstraint(new Callback(array(
756             'callback' => $callback,
757             'groups' => 'Group',
758         )));
759
760         $violations = $this->validate($entity, null, 'Group');
761
762         /* @var ConstraintViolationInterface[] $violations */
763         $this->assertCount(1, $violations);
764         $this->assertSame('Message value', $violations[0]->getMessage());
765         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
766         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
767         $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath());
768         $this->assertSame($entity, $violations[0]->getRoot());
769         $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue());
770         $this->assertNull($violations[0]->getPlural());
771         $this->assertNull($violations[0]->getCode());
772     }
773
774     public function testValidateProperty()
775     {
776         $entity = new Entity();
777         $entity->firstName = 'Bernhard';
778         $entity->setLastName('Schussek');
779
780         $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) {
781             $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName');
782
783             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
784             $this->assertSame('firstName', $context->getPropertyName());
785             $this->assertSame('firstName', $context->getPropertyPath());
786             $this->assertSame('Group', $context->getGroup());
787             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
788             $this->assertSame($entity, $context->getRoot());
789             $this->assertSame('Bernhard', $context->getValue());
790             $this->assertSame('Bernhard', $value);
791
792             $context->addViolation('Message %param%', array('%param%' => 'value'));
793         };
794
795         $callback2 = function ($value, ExecutionContextInterface $context) {
796             $context->addViolation('Other violation');
797         };
798
799         $this->metadata->addPropertyConstraint('firstName', new Callback(array(
800             'callback' => $callback1,
801             'groups' => 'Group',
802         )));
803         $this->metadata->addPropertyConstraint('lastName', new Callback(array(
804             'callback' => $callback2,
805             'groups' => 'Group',
806         )));
807
808         $violations = $this->validateProperty($entity, 'firstName', 'Group');
809
810         /* @var ConstraintViolationInterface[] $violations */
811         $this->assertCount(1, $violations);
812         $this->assertSame('Message value', $violations[0]->getMessage());
813         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
814         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
815         $this->assertSame('firstName', $violations[0]->getPropertyPath());
816         $this->assertSame($entity, $violations[0]->getRoot());
817         $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
818         $this->assertNull($violations[0]->getPlural());
819         $this->assertNull($violations[0]->getCode());
820     }
821
822     /**
823      * https://github.com/symfony/symfony/issues/11604.
824      */
825     public function testValidatePropertyWithoutConstraints()
826     {
827         $entity = new Entity();
828         $violations = $this->validateProperty($entity, 'lastName');
829
830         $this->assertCount(0, $violations, '->validateProperty() returns no violations if no constraints have been configured for the property being validated');
831     }
832
833     public function testValidatePropertyValue()
834     {
835         $entity = new Entity();
836         $entity->setLastName('Schussek');
837
838         $callback1 = function ($value, ExecutionContextInterface $context) use ($entity) {
839             $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName');
840
841             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
842             $this->assertSame('firstName', $context->getPropertyName());
843             $this->assertSame('firstName', $context->getPropertyPath());
844             $this->assertSame('Group', $context->getGroup());
845             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
846             $this->assertSame($entity, $context->getRoot());
847             $this->assertSame('Bernhard', $context->getValue());
848             $this->assertSame('Bernhard', $value);
849
850             $context->addViolation('Message %param%', array('%param%' => 'value'));
851         };
852
853         $callback2 = function ($value, ExecutionContextInterface $context) {
854             $context->addViolation('Other violation');
855         };
856
857         $this->metadata->addPropertyConstraint('firstName', new Callback(array(
858             'callback' => $callback1,
859             'groups' => 'Group',
860         )));
861         $this->metadata->addPropertyConstraint('lastName', new Callback(array(
862             'callback' => $callback2,
863             'groups' => 'Group',
864         )));
865
866         $violations = $this->validatePropertyValue(
867             $entity,
868             'firstName',
869             'Bernhard',
870             'Group'
871         );
872
873         /* @var ConstraintViolationInterface[] $violations */
874         $this->assertCount(1, $violations);
875         $this->assertSame('Message value', $violations[0]->getMessage());
876         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
877         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
878         $this->assertSame('firstName', $violations[0]->getPropertyPath());
879         $this->assertSame($entity, $violations[0]->getRoot());
880         $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
881         $this->assertNull($violations[0]->getPlural());
882         $this->assertNull($violations[0]->getCode());
883     }
884
885     public function testValidatePropertyValueWithClassName()
886     {
887         $callback1 = function ($value, ExecutionContextInterface $context) {
888             $propertyMetadatas = $this->metadata->getPropertyMetadata('firstName');
889
890             $this->assertSame($this::ENTITY_CLASS, $context->getClassName());
891             $this->assertSame('firstName', $context->getPropertyName());
892             $this->assertSame('', $context->getPropertyPath());
893             $this->assertSame('Group', $context->getGroup());
894             $this->assertSame($propertyMetadatas[0], $context->getMetadata());
895             $this->assertSame('Bernhard', $context->getRoot());
896             $this->assertSame('Bernhard', $context->getValue());
897             $this->assertSame('Bernhard', $value);
898
899             $context->addViolation('Message %param%', array('%param%' => 'value'));
900         };
901
902         $callback2 = function ($value, ExecutionContextInterface $context) {
903             $context->addViolation('Other violation');
904         };
905
906         $this->metadata->addPropertyConstraint('firstName', new Callback(array(
907             'callback' => $callback1,
908             'groups' => 'Group',
909         )));
910         $this->metadata->addPropertyConstraint('lastName', new Callback(array(
911             'callback' => $callback2,
912             'groups' => 'Group',
913         )));
914
915         $violations = $this->validatePropertyValue(
916             self::ENTITY_CLASS,
917             'firstName',
918             'Bernhard',
919             'Group'
920         );
921
922         /* @var ConstraintViolationInterface[] $violations */
923         $this->assertCount(1, $violations);
924         $this->assertSame('Message value', $violations[0]->getMessage());
925         $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
926         $this->assertSame(array('%param%' => 'value'), $violations[0]->getParameters());
927         $this->assertSame('', $violations[0]->getPropertyPath());
928         $this->assertSame('Bernhard', $violations[0]->getRoot());
929         $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
930         $this->assertNull($violations[0]->getPlural());
931         $this->assertNull($violations[0]->getCode());
932     }
933
934     /**
935      * https://github.com/symfony/symfony/issues/11604.
936      */
937     public function testValidatePropertyValueWithoutConstraints()
938     {
939         $entity = new Entity();
940         $violations = $this->validatePropertyValue($entity, 'lastName', 'foo');
941
942         $this->assertCount(0, $violations, '->validatePropertyValue() returns no violations if no constraints have been configured for the property being validated');
943     }
944
945     public function testValidateObjectOnlyOncePerGroup()
946     {
947         $entity = new Entity();
948         $entity->reference = new Reference();
949         $entity->reference2 = $entity->reference;
950
951         $callback = function ($value, ExecutionContextInterface $context) {
952             $context->addViolation('Message');
953         };
954
955         $this->metadata->addPropertyConstraint('reference', new Valid());
956         $this->metadata->addPropertyConstraint('reference2', new Valid());
957         $this->referenceMetadata->addConstraint(new Callback($callback));
958
959         $violations = $this->validate($entity);
960
961         /* @var ConstraintViolationInterface[] $violations */
962         $this->assertCount(1, $violations);
963     }
964
965     public function testValidateDifferentObjectsSeparately()
966     {
967         $entity = new Entity();
968         $entity->reference = new Reference();
969         $entity->reference2 = new Reference();
970
971         $callback = function ($value, ExecutionContextInterface $context) {
972             $context->addViolation('Message');
973         };
974
975         $this->metadata->addPropertyConstraint('reference', new Valid());
976         $this->metadata->addPropertyConstraint('reference2', new Valid());
977         $this->referenceMetadata->addConstraint(new Callback($callback));
978
979         $violations = $this->validate($entity);
980
981         /* @var ConstraintViolationInterface[] $violations */
982         $this->assertCount(2, $violations);
983     }
984
985     public function testValidateSingleGroup()
986     {
987         $entity = new Entity();
988
989         $callback = function ($value, ExecutionContextInterface $context) {
990             $context->addViolation('Message');
991         };
992
993         $this->metadata->addConstraint(new Callback(array(
994             'callback' => $callback,
995             'groups' => 'Group 1',
996         )));
997         $this->metadata->addConstraint(new Callback(array(
998             'callback' => $callback,
999             'groups' => 'Group 2',
1000         )));
1001
1002         $violations = $this->validate($entity, null, 'Group 2');
1003
1004         /* @var ConstraintViolationInterface[] $violations */
1005         $this->assertCount(1, $violations);
1006     }
1007
1008     public function testValidateMultipleGroups()
1009     {
1010         $entity = new Entity();
1011
1012         $callback = function ($value, ExecutionContextInterface $context) {
1013             $context->addViolation('Message');
1014         };
1015
1016         $this->metadata->addConstraint(new Callback(array(
1017             'callback' => $callback,
1018             'groups' => 'Group 1',
1019         )));
1020         $this->metadata->addConstraint(new Callback(array(
1021             'callback' => $callback,
1022             'groups' => 'Group 2',
1023         )));
1024
1025         $violations = $this->validate($entity, null, array('Group 1', 'Group 2'));
1026
1027         /* @var ConstraintViolationInterface[] $violations */
1028         $this->assertCount(2, $violations);
1029     }
1030
1031     public function testReplaceDefaultGroupByGroupSequenceObject()
1032     {
1033         $entity = new Entity();
1034
1035         $callback1 = function ($value, ExecutionContextInterface $context) {
1036             $context->addViolation('Violation in Group 2');
1037         };
1038         $callback2 = function ($value, ExecutionContextInterface $context) {
1039             $context->addViolation('Violation in Group 3');
1040         };
1041
1042         $this->metadata->addConstraint(new Callback(array(
1043             'callback' => function () {},
1044             'groups' => 'Group 1',
1045         )));
1046         $this->metadata->addConstraint(new Callback(array(
1047             'callback' => $callback1,
1048             'groups' => 'Group 2',
1049         )));
1050         $this->metadata->addConstraint(new Callback(array(
1051             'callback' => $callback2,
1052             'groups' => 'Group 3',
1053         )));
1054
1055         $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3', 'Entity'));
1056         $this->metadata->setGroupSequence($sequence);
1057
1058         $violations = $this->validate($entity, null, 'Default');
1059
1060         /* @var ConstraintViolationInterface[] $violations */
1061         $this->assertCount(1, $violations);
1062         $this->assertSame('Violation in Group 2', $violations[0]->getMessage());
1063     }
1064
1065     public function testReplaceDefaultGroupByGroupSequenceArray()
1066     {
1067         $entity = new Entity();
1068
1069         $callback1 = function ($value, ExecutionContextInterface $context) {
1070             $context->addViolation('Violation in Group 2');
1071         };
1072         $callback2 = function ($value, ExecutionContextInterface $context) {
1073             $context->addViolation('Violation in Group 3');
1074         };
1075
1076         $this->metadata->addConstraint(new Callback(array(
1077             'callback' => function () {},
1078             'groups' => 'Group 1',
1079         )));
1080         $this->metadata->addConstraint(new Callback(array(
1081             'callback' => $callback1,
1082             'groups' => 'Group 2',
1083         )));
1084         $this->metadata->addConstraint(new Callback(array(
1085             'callback' => $callback2,
1086             'groups' => 'Group 3',
1087         )));
1088
1089         $sequence = array('Group 1', 'Group 2', 'Group 3', 'Entity');
1090         $this->metadata->setGroupSequence($sequence);
1091
1092         $violations = $this->validate($entity, null, 'Default');
1093
1094         /* @var ConstraintViolationInterface[] $violations */
1095         $this->assertCount(1, $violations);
1096         $this->assertSame('Violation in Group 2', $violations[0]->getMessage());
1097     }
1098
1099     public function testPropagateDefaultGroupToReferenceWhenReplacingDefaultGroup()
1100     {
1101         $entity = new Entity();
1102         $entity->reference = new Reference();
1103
1104         $callback1 = function ($value, ExecutionContextInterface $context) {
1105             $context->addViolation('Violation in Default group');
1106         };
1107         $callback2 = function ($value, ExecutionContextInterface $context) {
1108             $context->addViolation('Violation in group sequence');
1109         };
1110
1111         $this->metadata->addPropertyConstraint('reference', new Valid());
1112         $this->referenceMetadata->addConstraint(new Callback(array(
1113             'callback' => $callback1,
1114             'groups' => 'Default',
1115         )));
1116         $this->referenceMetadata->addConstraint(new Callback(array(
1117             'callback' => $callback2,
1118             'groups' => 'Group 1',
1119         )));
1120
1121         $sequence = new GroupSequence(array('Group 1', 'Entity'));
1122         $this->metadata->setGroupSequence($sequence);
1123
1124         $violations = $this->validate($entity, null, 'Default');
1125
1126         /* @var ConstraintViolationInterface[] $violations */
1127         $this->assertCount(1, $violations);
1128         $this->assertSame('Violation in Default group', $violations[0]->getMessage());
1129     }
1130
1131     public function testValidateCustomGroupWhenDefaultGroupWasReplaced()
1132     {
1133         $entity = new Entity();
1134
1135         $callback1 = function ($value, ExecutionContextInterface $context) {
1136             $context->addViolation('Violation in other group');
1137         };
1138         $callback2 = function ($value, ExecutionContextInterface $context) {
1139             $context->addViolation('Violation in group sequence');
1140         };
1141
1142         $this->metadata->addConstraint(new Callback(array(
1143             'callback' => $callback1,
1144             'groups' => 'Other Group',
1145         )));
1146         $this->metadata->addConstraint(new Callback(array(
1147             'callback' => $callback2,
1148             'groups' => 'Group 1',
1149         )));
1150
1151         $sequence = new GroupSequence(array('Group 1', 'Entity'));
1152         $this->metadata->setGroupSequence($sequence);
1153
1154         $violations = $this->validate($entity, null, 'Other Group');
1155
1156         /* @var ConstraintViolationInterface[] $violations */
1157         $this->assertCount(1, $violations);
1158         $this->assertSame('Violation in other group', $violations[0]->getMessage());
1159     }
1160
1161     /**
1162      * @dataProvider getTestReplaceDefaultGroup
1163      */
1164     public function testReplaceDefaultGroup($sequence, array $assertViolations)
1165     {
1166         $entity = new GroupSequenceProviderEntity($sequence);
1167
1168         $callback1 = function ($value, ExecutionContextInterface $context) {
1169             $context->addViolation('Violation in Group 2');
1170         };
1171         $callback2 = function ($value, ExecutionContextInterface $context) {
1172             $context->addViolation('Violation in Group 3');
1173         };
1174
1175         $metadata = new ClassMetadata(get_class($entity));
1176         $metadata->addConstraint(new Callback(array(
1177             'callback' => function () {},
1178             'groups' => 'Group 1',
1179         )));
1180         $metadata->addConstraint(new Callback(array(
1181             'callback' => $callback1,
1182             'groups' => 'Group 2',
1183         )));
1184         $metadata->addConstraint(new Callback(array(
1185             'callback' => $callback2,
1186             'groups' => 'Group 3',
1187         )));
1188         $metadata->setGroupSequenceProvider(true);
1189
1190         $this->metadataFactory->addMetadata($metadata);
1191
1192         $violations = $this->validate($entity, null, 'Default');
1193
1194         /* @var ConstraintViolationInterface[] $violations */
1195         $this->assertCount(count($assertViolations), $violations);
1196         foreach ($assertViolations as $key => $message) {
1197             $this->assertSame($message, $violations[$key]->getMessage());
1198         }
1199     }
1200
1201     public function getTestReplaceDefaultGroup()
1202     {
1203         return array(
1204             array(
1205                 'sequence' => new GroupSequence(array('Group 1', 'Group 2', 'Group 3', 'Entity')),
1206                 'assertViolations' => array(
1207                     'Violation in Group 2',
1208                 ),
1209             ),
1210             array(
1211                 'sequence' => array('Group 1', 'Group 2', 'Group 3', 'Entity'),
1212                 'assertViolations' => array(
1213                     'Violation in Group 2',
1214                 ),
1215             ),
1216             array(
1217                 'sequence' => new GroupSequence(array('Group 1', array('Group 2', 'Group 3'), 'Entity')),
1218                 'assertViolations' => array(
1219                     'Violation in Group 2',
1220                     'Violation in Group 3',
1221                 ),
1222             ),
1223             array(
1224                 'sequence' => array('Group 1', array('Group 2', 'Group 3'), 'Entity'),
1225                 'assertViolations' => array(
1226                     'Violation in Group 2',
1227                     'Violation in Group 3',
1228                 ),
1229             ),
1230         );
1231     }
1232 }