From 09e8753994b7f449b4d1ed402f9895ed7c82d608 Mon Sep 17 00:00:00 2001 From: Manuel Reinhard Date: Tue, 14 May 2013 12:02:36 +0200 Subject: [PATCH] Extended Date Validator with before/after options --- .../Component/Validator/Constraints/Date.php | 49 +++- .../Validator/Constraints/DateValidator.php | 35 ++- .../Tests/Constraints/DateValidatorTest.php | 225 ++++++++++++++++++ 3 files changed, 301 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/Date.php b/src/Symfony/Component/Validator/Constraints/Date.php index f9923052e0cf1..6f1d728e50cf9 100644 --- a/src/Symfony/Component/Validator/Constraints/Date.php +++ b/src/Symfony/Component/Validator/Constraints/Date.php @@ -22,5 +22,50 @@ */ class Date extends Constraint { - public $message = 'This value is not a valid date.'; -} + public $message = 'This value is not a valid date.'; + public $messageBeforeDate = 'The date should be before {{ before }}.'; + public $messageAfterDate = 'The date should be after {{ after }}.'; + public $dateFormatMessages = 'yyyy-MM-dd'; // see http://userguide.icu-project.org/formatparse/datetime + public $dateFormatter; + + public $before; + public $after; + + public function __construct($options = null) + { + if (isset($options['after']) && !$options['after'] instanceof \DateTime) { + $options['after'] = new \DateTime($options['after']); + } + + if (isset($options['before']) && !$options['before'] instanceof \DateTime) { + $options['before'] = new \DateTime($options['before']); + } + + if (isset($options['before']) && isset($options['after'])) { + if ($options['before'] == $options['after']) { + throw new InvalidOptionsException('The options "after" and "before" may not have the same value. ' . __CLASS__, array('after', 'before')); + } + if ($options['before'] < $options['after']) { + throw new InvalidOptionsException('The value of "before" must be a date later than the value of "after". ' . __CLASS__, array('after', 'before')); + } + } + + if (isset($options['dateFormatter'])) { + if (!$options['dateFormatter'] instanceof \IntlDateFormatter) { + throw new InvalidOptionsException('The option "dateFormatter" must be an instance of \IntlDateFormatter.' . __CLASS__, array('dateFormatter')); + } + } else { + $options['dateFormatter'] = new \IntlDateFormatter('en_US', \IntlDateFormatter::SHORT, \IntlDateFormatter::NONE); + } + + if (isset($options['dateFormatMessages'])) { + if (!$options['dateFormatter']->setPattern($options['dateFormatMessages'])) { + throw new InvalidOptionsException('The value of the option "dateFormatMessages" is invalid. ' . __CLASS__, array('dateFormatMessages')); + } + } else { + $options['dateFormatter']->setPattern($this->dateFormatMessages); + } + + parent::__construct($options); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Validator/Constraints/DateValidator.php b/src/Symfony/Component/Validator/Constraints/DateValidator.php index f891f9d621a9b..b43420d0c880c 100644 --- a/src/Symfony/Component/Validator/Constraints/DateValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateValidator.php @@ -29,18 +29,41 @@ class DateValidator extends ConstraintValidator */ public function validate($value, Constraint $constraint) { - if (null === $value || '' === $value || $value instanceof \DateTime) { + if (null === $value || '' === $value) { return; } - if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) { - throw new UnexpectedTypeException($value, 'string'); + if($value instanceof \DateTime) { + $dateTime = $value; + } else { + + if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + + if (!preg_match(static::PATTERN, $value, $matches) || !checkdate($matches[2], $matches[3], $matches[1])) { + $this->context->addViolation($constraint->message, array('{{ value }}' => $value)); + + return; + } + + $dateTime = new \DateTime($matches[1].'-'.$matches[2].'-'.$matches[3]); } - $value = (string) $value; + if (null !== $constraint->before && $dateTime >= $constraint->before) { + $formattedBeforeDate = $constraint->dateFormatter->format($constraint->before); + $this->context->addViolation($constraint->messageBeforeDate, array('{{ value }}' => $value, '{{ before }}' => $formattedBeforeDate)); - if (!preg_match(static::PATTERN, $value, $matches) || !checkdate($matches[2], $matches[3], $matches[1])) { - $this->context->addViolation($constraint->message, array('{{ value }}' => $value)); + return; + } + + if (null !== $constraint->after && $dateTime <= $constraint->after) { + $formattedAfterDate = $constraint->dateFormatter->format($constraint->after); + $this->context->addViolation($constraint->messageAfterDate, array('{{ value }}' => $value, '{{ after }}' => $formattedAfterDate)); + + return; } } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php index b9cf8816b76a5..c57c4ff1692b8 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateValidatorTest.php @@ -78,6 +78,7 @@ public function testValidDates($date) public function getValidDates() { return array( + array(''), array('2010-01-01'), array('1955-12-12'), array('2030-05-31'), @@ -113,4 +114,228 @@ public function getInvalidDates() array('2010-02-29'), ); } + + /** + * @dataProvider getValidBeforeDates + */ + public function testValidBeforeDates($date) + { + $constraint = new Date(array( + 'before' => '2015-01-01' + )); + + $this->context->expects($this->never()) + ->method('addViolation'); + + $this->validator->validate($date, $constraint); + } + + public function getValidBeforeDates() + { + return array( + array('2014-12-31'), + array('2000-01-01'), + array('1980-01-01'), + ); + } + + /** + * @dataProvider getInvalidBeforeDates + */ + public function testInvalidBeforeDates($date) + { + $constraint = new Date(array( + 'messageBeforeDate' => 'myMessage', + 'before' => '2015-01-01' + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ value }}' => $date, + '{{ before }}' => '2015-01-01', + )); + + $this->validator->validate($date, $constraint); + } + + public function getInvalidBeforeDates() + { + return array( + array('2015-01-01'), + array('2018-12-20'), + array('2016-02-29'), + ); + } + + /** + * @dataProvider getValidAfterDates + */ + public function testValidAfterDates($date) + { + $constraint = new Date(array( + 'after' => '2015-01-01' + )); + + $this->context->expects($this->never()) + ->method('addViolation'); + + $this->validator->validate($date, $constraint); + } + + public function getValidAfterDates() + { + return array( + array('2015-01-02'), + array('2016-02-29'), + array('2100-12-31'), + ); + } + + /** + * @dataProvider getInvalidAfterDates + */ + public function testInvalidAfterDates($date) + { + $constraint = new Date(array( + 'messageAfterDate' => 'myMessage', + 'after' => '2015-01-01' + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ value }}' => $date, + '{{ after }}' => '2015-01-01', + )); + + $this->validator->validate($date, $constraint); + } + + public function getInvalidAfterDates() + { + return array( + array('2015-01-01'), + array('2014-12-31'), + array('1980-01-01'), + ); + } + + + /** + * @dataProvider getValidInBetweenDates + */ + public function testValidInBetweenDates($date) + { + $constraint = new Date(array( + 'after' => '2014-12-31', + 'before' => '2017-01-01' + )); + + $this->context->expects($this->never()) + ->method('addViolation'); + + $this->validator->validate($date, $constraint); + } + + public function getValidInBetweenDates() + { + return array( + array('2015-01-01'), + array('2015-12-31'), + array('2016-12-31'), + ); + } + + /** + * @dataProvider getTooLateInBetweenDates + */ + public function testTooLateInBetweenDates($date) + { + $constraint = new Date(array( + 'messageAfterDate' => 'myMessage', + 'messageBeforeDate' => 'myMessage', + 'after' => '2014-12-31', + 'before' => '2017-01-01' + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ value }}' => $date, + '{{ before }}' => '2017-01-01', + )); + + $this->validator->validate($date, $constraint); + } + + public function getTooLateInBetweenDates() + { + return array( + array('2017-01-01'), + array('2020-06-06'), + ); + } + + /** + * @dataProvider getTooEarlyInBetweenDates + */ + public function testTooEarlyInBetweenDates($date) + { + $constraint = new Date(array( + 'messageAfterDate' => 'myMessage', + 'messageBeforeDate' => 'myMessage', + 'after' => '2014-12-31', + 'before' => '2017-01-01' + )); + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ value }}' => $date, + '{{ after }}' => '2014-12-31', + )); + + $this->validator->validate($date, $constraint); + } + + public function getTooEarlyInBetweenDates() + { + return array( + array('2014-12-31'), + array('2000-06-06'), + ); + } + + /** + * @dataProvider getDateFormatMessages + */ + public function testDateFormatMessagesFormatter($format, $expected) + { + $constraint = new Date(array( + 'messageAfterDate' => 'myMessage', + 'after' => '2014-12-31', + 'dateFormatMessages' => $format, + )); + + $date = '2000-01-01'; + + $this->context->expects($this->once()) + ->method('addViolation') + ->with('myMessage', array( + '{{ value }}' => $date, + '{{ after }}' => $expected, + )); + + $this->validator->validate($date, $constraint); + } + + public function getDateFormatMessages() + { + return array( + array('dd/MM/yy', '31/12/14'), + array('yyyy-MM-dd', '2014-12-31'), + array('yyyyMMdd HH:mm:ss', '20141231 00:00:00'), + ); + } }