diff --git a/src/Symfony/Component/Validator/Constraints/Range.php b/src/Symfony/Component/Validator/Constraints/Range.php index 3ccbf89b62..7bbe69fcc3 100644 --- a/src/Symfony/Component/Validator/Constraints/Range.php +++ b/src/Symfony/Component/Validator/Constraints/Range.php @@ -41,6 +41,7 @@ class Range extends Constraint public $minMessage = 'This value should be {{ limit }} or more.'; public $maxMessage = 'This value should be {{ limit }} or less.'; public $invalidMessage = 'This value should be a valid number.'; + public $invalidDateTimeMessage = 'This value should be a valid datetime.'; public $min; public $minPropertyPath; public $max; diff --git a/src/Symfony/Component/Validator/Constraints/RangeValidator.php b/src/Symfony/Component/Validator/Constraints/RangeValidator.php index 2aa81666f7..7a9ad79e04 100644 --- a/src/Symfony/Component/Validator/Constraints/RangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/RangeValidator.php @@ -44,18 +44,25 @@ class RangeValidator extends ConstraintValidator return; } + $min = $this->getLimit($constraint->minPropertyPath, $constraint->min, $constraint); + $max = $this->getLimit($constraint->maxPropertyPath, $constraint->max, $constraint); + if (!is_numeric($value) && !$value instanceof \DateTimeInterface) { - $this->context->buildViolation($constraint->invalidMessage) - ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) - ->setCode(Range::INVALID_CHARACTERS_ERROR) - ->addViolation(); + if ($this->isParsableDatetimeString($min) && $this->isParsableDatetimeString($max)) { + $this->context->buildViolation($constraint->invalidDateTimeMessage) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->addViolation(); + } else { + $this->context->buildViolation($constraint->invalidMessage) + ->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE)) + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->addViolation(); + } return; } - $min = $this->getLimit($constraint->minPropertyPath, $constraint->min, $constraint); - $max = $this->getLimit($constraint->maxPropertyPath, $constraint->max, $constraint); - // Convert strings to DateTimes if comparing another DateTime // This allows to compare with any date/time value supported by // the DateTime constructor: @@ -182,4 +189,23 @@ class RangeValidator extends ConstraintValidator return $this->propertyAccessor; } + + private function isParsableDatetimeString($boundary): bool + { + if (null === $boundary) { + return true; + } + + if (!\is_string($boundary)) { + return false; + } + + try { + new \DateTime($boundary); + } catch (\Exception $e) { + return false; + } + + return true; + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php index ff1497c923..3d7a773a21 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php @@ -379,13 +379,102 @@ class RangeValidatorTest extends ConstraintValidatorTestCase public function testNonNumeric() { - $this->validator->validate('abcd', new Range([ + $constraint = new Range([ 'min' => 10, 'max' => 20, - 'invalidMessage' => 'myMessage', - ])); + ]); - $this->buildViolation('myMessage') + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidMessage) + ->setParameter('{{ value }}', '"abcd"') + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } + + public function testNonNumericWithParsableDatetimeMinAndMaxNull() + { + $constraint = new Range([ + 'min' => 'March 10, 2014', + ]); + + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidDateTimeMessage) + ->setParameter('{{ value }}', '"abcd"') + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } + + public function testNonNumericWithParsableDatetimeMaxAndMinNull() + { + $constraint = new Range([ + 'max' => 'March 20, 2014', + ]); + + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidDateTimeMessage) + ->setParameter('{{ value }}', '"abcd"') + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } + + public function testNonNumericWithParsableDatetimeMinAndMax() + { + $constraint = new Range([ + 'min' => 'March 10, 2014', + 'max' => 'March 20, 2014', + ]); + + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidDateTimeMessage) + ->setParameter('{{ value }}', '"abcd"') + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } + + public function testNonNumericWithNonParsableDatetimeMin() + { + $constraint = new Range([ + 'min' => 'March 40, 2014', + 'max' => 'March 20, 2014', + ]); + + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidMessage) + ->setParameter('{{ value }}', '"abcd"') + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } + + public function testNonNumericWithNonParsableDatetimeMax() + { + $constraint = new Range([ + 'min' => 'March 10, 2014', + 'max' => 'March 50, 2014', + ]); + + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidMessage) + ->setParameter('{{ value }}', '"abcd"') + ->setCode(Range::INVALID_CHARACTERS_ERROR) + ->assertRaised(); + } + + public function testNonNumericWithNonParsableDatetimeMinAndMax() + { + $constraint = new Range([ + 'min' => 'March 40, 2014', + 'max' => 'March 50, 2014', + ]); + + $this->validator->validate('abcd', $constraint); + + $this->buildViolation($constraint->invalidMessage) ->setParameter('{{ value }}', '"abcd"') ->setCode(Range::INVALID_CHARACTERS_ERROR) ->assertRaised();