* * 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 */ 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 Symfony 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 Symfony 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 ); } }