--- /dev/null
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Constraints;
+
+use PHPUnit\Framework\Assert;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\NotNull;
+use Symfony\Component\Validator\ConstraintValidatorInterface;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\Context\ExecutionContext;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Context\LegacyExecutionContext;
+use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\PropertyMetadata;
+use Symfony\Component\Validator\Validation;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+abstract class AbstractConstraintValidatorTest extends TestCase
+{
+ /**
+ * @var ExecutionContextInterface
+ */
+ protected $context;
+
+ /**
+ * @var ConstraintValidatorInterface
+ */
+ protected $validator;
+
+ protected $group;
+ protected $metadata;
+ protected $object;
+ protected $value;
+ protected $root;
+ protected $propertyPath;
+ protected $constraint;
+ protected $defaultTimezone;
+
+ protected function setUp()
+ {
+ $this->group = 'MyGroup';
+ $this->metadata = null;
+ $this->object = null;
+ $this->value = 'InvalidValue';
+ $this->root = 'root';
+ $this->propertyPath = 'property.path';
+
+ // Initialize the context with some constraint so that we can
+ // successfully build a violation.
+ $this->constraint = new NotNull();
+
+ $this->context = $this->createContext();
+ $this->validator = $this->createValidator();
+ $this->validator->initialize($this->context);
+
+ \Locale::setDefault('en');
+
+ $this->setDefaultTimezone('UTC');
+ }
+
+ protected function tearDown()
+ {
+ $this->restoreDefaultTimezone();
+ }
+
+ protected function setDefaultTimezone($defaultTimezone)
+ {
+ // Make sure this method can not be called twice before calling
+ // also restoreDefaultTimezone()
+ if (null === $this->defaultTimezone) {
+ $this->defaultTimezone = date_default_timezone_get();
+ date_default_timezone_set($defaultTimezone);
+ }
+ }
+
+ protected function restoreDefaultTimezone()
+ {
+ if (null !== $this->defaultTimezone) {
+ date_default_timezone_set($this->defaultTimezone);
+ $this->defaultTimezone = null;
+ }
+ }
+
+ protected function createContext()
+ {
+ $translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock();
+ $validator = $this->getMockBuilder('Symfony\Component\Validator\Validator\ValidatorInterface')->getMock();
+ $contextualValidator = $this->getMockBuilder('Symfony\Component\Validator\Validator\ContextualValidatorInterface')->getMock();
+
+ switch ($this->getApiVersion()) {
+ case Validation::API_VERSION_2_5:
+ $context = new ExecutionContext(
+ $validator,
+ $this->root,
+ $translator
+ );
+ break;
+ case Validation::API_VERSION_2_4:
+ case Validation::API_VERSION_2_5_BC:
+ $context = new LegacyExecutionContext(
+ $validator,
+ $this->root,
+ $this->getMockBuilder('Symfony\Component\Validator\MetadataFactoryInterface')->getMock(),
+ $translator
+ );
+ break;
+ default:
+ throw new \RuntimeException('Invalid API version');
+ }
+
+ $context->setGroup($this->group);
+ $context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath);
+ $context->setConstraint($this->constraint);
+
+ $validator->expects($this->any())
+ ->method('inContext')
+ ->with($context)
+ ->will($this->returnValue($contextualValidator));
+
+ return $context;
+ }
+
+ /**
+ * @param mixed $message
+ * @param array $parameters
+ * @param string $propertyPath
+ * @param string $invalidValue
+ * @param null $plural
+ * @param null $code
+ *
+ * @return ConstraintViolation
+ *
+ * @deprecated to be removed in Symfony 3.0. Use {@link buildViolation()} instead.
+ */
+ protected function createViolation($message, array $parameters = array(), $propertyPath = 'property.path', $invalidValue = 'InvalidValue', $plural = null, $code = null)
+ {
+ return new ConstraintViolation(
+ null,
+ $message,
+ $parameters,
+ $this->root,
+ $propertyPath,
+ $invalidValue,
+ $plural,
+ $code,
+ $this->constraint
+ );
+ }
+
+ protected function setGroup($group)
+ {
+ $this->group = $group;
+ $this->context->setGroup($group);
+ }
+
+ protected function setObject($object)
+ {
+ $this->object = $object;
+ $this->metadata = is_object($object)
+ ? new ClassMetadata(get_class($object))
+ : null;
+
+ $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath);
+ }
+
+ protected function setProperty($object, $property)
+ {
+ $this->object = $object;
+ $this->metadata = is_object($object)
+ ? new PropertyMetadata(get_class($object), $property)
+ : null;
+
+ $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath);
+ }
+
+ protected function setValue($value)
+ {
+ $this->value = $value;
+ $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath);
+ }
+
+ protected function setRoot($root)
+ {
+ $this->root = $root;
+ $this->context = $this->createContext();
+ $this->validator->initialize($this->context);
+ }
+
+ protected function setPropertyPath($propertyPath)
+ {
+ $this->propertyPath = $propertyPath;
+ $this->context->setNode($this->value, $this->object, $this->metadata, $this->propertyPath);
+ }
+
+ protected function expectNoValidate()
+ {
+ $validator = $this->context->getValidator()->inContext($this->context);
+ $validator->expects($this->never())
+ ->method('atPath');
+ $validator->expects($this->never())
+ ->method('validate');
+ }
+
+ protected function expectValidateAt($i, $propertyPath, $value, $group)
+ {
+ $validator = $this->context->getValidator()->inContext($this->context);
+ $validator->expects($this->at(2 * $i))
+ ->method('atPath')
+ ->with($propertyPath)
+ ->will($this->returnValue($validator));
+ $validator->expects($this->at(2 * $i + 1))
+ ->method('validate')
+ ->with($value, $this->logicalOr(null, array(), $this->isInstanceOf('\Symfony\Component\Validator\Constraints\Valid')), $group);
+ }
+
+ protected function expectValidateValueAt($i, $propertyPath, $value, $constraints, $group = null)
+ {
+ $contextualValidator = $this->context->getValidator()->inContext($this->context);
+ $contextualValidator->expects($this->at(2 * $i))
+ ->method('atPath')
+ ->with($propertyPath)
+ ->will($this->returnValue($contextualValidator));
+ $contextualValidator->expects($this->at(2 * $i + 1))
+ ->method('validate')
+ ->with($value, $constraints, $group);
+ }
+
+ protected function assertNoViolation()
+ {
+ $this->assertSame(0, $violationsCount = count($this->context->getViolations()), sprintf('0 violation expected. Got %u.', $violationsCount));
+ }
+
+ /**
+ * @param mixed $message
+ * @param array $parameters
+ * @param string $propertyPath
+ * @param string $invalidValue
+ * @param null $plural
+ * @param null $code
+ *
+ * @deprecated To be removed in Symfony 3.0. Use
+ * {@link buildViolation()} instead.
+ */
+ protected function assertViolation($message, array $parameters = array(), $propertyPath = 'property.path', $invalidValue = 'InvalidValue', $plural = null, $code = null)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0. Use the buildViolation() method instead.', E_USER_DEPRECATED);
+
+ $this->buildViolation($message)
+ ->setParameters($parameters)
+ ->atPath($propertyPath)
+ ->setInvalidValue($invalidValue)
+ ->setCode($code)
+ ->setPlural($plural)
+ ->assertRaised();
+ }
+
+ /**
+ * @param array $expected
+ *
+ * @deprecated To be removed in Symfony 3.0. Use
+ * {@link buildViolation()} instead.
+ */
+ protected function assertViolations(array $expected)
+ {
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 2.3 and will be removed in 3.0. Use the buildViolation() method instead.', E_USER_DEPRECATED);
+
+ $violations = $this->context->getViolations();
+
+ $this->assertCount(count($expected), $violations);
+
+ $i = 0;
+
+ foreach ($expected as $violation) {
+ $this->assertEquals($violation, $violations[$i++]);
+ }
+ }
+
+ /**
+ * @param $message
+ *
+ * @return ConstraintViolationAssertion
+ */
+ protected function buildViolation($message)
+ {
+ return new ConstraintViolationAssertion($this->context, $message, $this->constraint);
+ }
+
+ protected function getApiVersion()
+ {
+ return Validation::API_VERSION_2_5;
+ }
+
+ abstract protected function createValidator();
+}
+
+/**
+ * @internal
+ */
+class ConstraintViolationAssertion
+{
+ /**
+ * @var LegacyExecutionContextInterface
+ */
+ private $context;
+
+ /**
+ * @var ConstraintViolationAssertion[]
+ */
+ private $assertions;
+
+ private $message;
+ private $parameters = array();
+ private $invalidValue = 'InvalidValue';
+ private $propertyPath = 'property.path';
+ private $translationDomain;
+ private $plural;
+ private $code;
+ private $constraint;
+ private $cause;
+
+ public function __construct(LegacyExecutionContextInterface $context, $message, Constraint $constraint = null, array $assertions = array())
+ {
+ $this->context = $context;
+ $this->message = $message;
+ $this->constraint = $constraint;
+ $this->assertions = $assertions;
+ }
+
+ public function atPath($path)
+ {
+ $this->propertyPath = $path;
+
+ return $this;
+ }
+
+ public function setParameter($key, $value)
+ {
+ $this->parameters[$key] = $value;
+
+ return $this;
+ }
+
+ public function setParameters(array $parameters)
+ {
+ $this->parameters = $parameters;
+
+ return $this;
+ }
+
+ public function setTranslationDomain($translationDomain)
+ {
+ $this->translationDomain = $translationDomain;
+
+ return $this;
+ }
+
+ public function setInvalidValue($invalidValue)
+ {
+ $this->invalidValue = $invalidValue;
+
+ return $this;
+ }
+
+ public function setPlural($number)
+ {
+ $this->plural = $number;
+
+ return $this;
+ }
+
+ public function setCode($code)
+ {
+ $this->code = $code;
+
+ return $this;
+ }
+
+ public function setCause($cause)
+ {
+ $this->cause = $cause;
+
+ return $this;
+ }
+
+ public function buildNextViolation($message)
+ {
+ $assertions = $this->assertions;
+ $assertions[] = $this;
+
+ return new self($this->context, $message, $this->constraint, $assertions);
+ }
+
+ public function assertRaised()
+ {
+ $expected = array();
+ foreach ($this->assertions as $assertion) {
+ $expected[] = $assertion->getViolation();
+ }
+ $expected[] = $this->getViolation();
+
+ $violations = iterator_to_array($this->context->getViolations());
+
+ Assert::assertSame($expectedCount = count($expected), $violationsCount = count($violations), sprintf('%u violation(s) expected. Got %u.', $expectedCount, $violationsCount));
+
+ reset($violations);
+
+ foreach ($expected as $violation) {
+ Assert::assertEquals($violation, current($violations));
+ next($violations);
+ }
+ }
+
+ private function getViolation()
+ {
+ return new ConstraintViolation(
+ null,
+ $this->message,
+ $this->parameters,
+ $this->context->getRoot(),
+ $this->propertyPath,
+ $this->invalidValue,
+ $this->plural,
+ $this->code,
+ $this->constraint,
+ $this->cause
+ );
+ }
+}