From 65f714c5091ecd38a6bcf705ec4658bd0e25a3e6 Mon Sep 17 00:00:00 2001 From: Bez Hermoso Date: Fri, 15 Aug 2014 10:46:55 -0700 Subject: [PATCH 1/3] Date range class validator. --- .../Validator/Constraints/DateRange.php | 65 +++++++ .../Constraints/DateRangeValidator.php | 123 ++++++++++++ .../Constraints/DateRangeValidatorTest.php | 181 ++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 src/Symfony/Component/Validator/Constraints/DateRange.php create mode 100644 src/Symfony/Component/Validator/Constraints/DateRangeValidator.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php diff --git a/src/Symfony/Component/Validator/Constraints/DateRange.php b/src/Symfony/Component/Validator/Constraints/DateRange.php new file mode 100644 index 0000000000000..fb352ce97957e --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/DateRange.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * Class DateRange + * + * @Annotation + * @Target({"CLASS"}) + * + * @author Bez Hermoso + */ +class DateRange extends Constraint +{ + /** + * @var string The property which contains the start date. + */ + public $start; + + /** + * @var string The property which contains the end date. + */ + public $end; + + public $startMessage = 'Start date should be less than or equal to {{ limit }}'; + + public $endMessage = 'End date should be greater than or equal to {{ limit }}'; + + public $emptyStartMessage = 'Start date cannot be empty'; + + public $emptyEndMessage = 'End date cannot be empty'; + + public $invalidMessage = 'Invalid date range'; + + public $limitFormat = 'Y-m-d'; + + /** + * @var string The property to attach the error message on. + */ + public $errorPath = false; + + public function getRequiredOptions() + { + return array( + 'start', + 'end', + ); + } + + public function getTargets() + { + return static::CLASS_CONSTRAINT; + } +} diff --git a/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php new file mode 100644 index 0000000000000..b459f261bfbe5 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * Class DateRange + * + * @Annotation + * @Target({"CLASS"}) + * + * @author Bez Hermoso + */ +class DateRangeValidator extends ConstraintValidator +{ + + /** + * Checks if the passed value is valid. + * + * @param mixed $value The value that should be validated + * @param Constraint $constraint The constraint for the validation + * + * @throws \Symfony\Component\Validator\Exception\UnexpectedTypeException + * @throws \Symfony\Component\Validator\Exception\ConstraintDefinitionException + * @api + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof DateRange) { + throw new UnexpectedTypeException($value, __NAMESPACE__ . '\\DateRange'); + } + + $accessor = PropertyAccess::createPropertyAccessor(); + + try { + $start = $accessor->getValue($value, $constraint->start); + } catch (NoSuchIndexException $e) { + throw new ConstraintDefinitionException( + sprintf( + 'The object "%s" does not have a "%s" property.', + get_class($value), + $constraint->start + ) + ); + } + + try { + $end = $accessor->getValue($value, $constraint->end); + } catch (NoSuchIndexException $e) { + throw new ConstraintDefinitionException( + sprintf( + 'The object "%s" does not have a "%s" property.', + get_class($value), + $constraint->end + ) + ); + } + + $validPropertyPaths = array($constraint->start, $constraint->end); + + if ($constraint->errorPath && !in_array($constraint->errorPath, $validPropertyPaths)) { + throw new ConstraintDefinitionException( + sprintf( + 'Cannot set error message on "%s". Choose from: %s', + $constraint->errorPath, + json_encode($validPropertyPaths) + ) + ); + } + + if (empty($start) || empty($end)) { + return; + } + + $diff = $start->diff($end); + + if ($diff->invert === 0) { + return; + } + + if ($constraint->errorPath) { + $message = + $constraint->errorPath === $constraint->end ? $constraint->endMessage : $constraint->startMessage; + $limit = + $constraint->errorPath === $constraint->end ? + $start->format($constraint->limitFormat) : $end->format($constraint->limitFormat); + $this + ->context + ->addViolationAt( + $constraint->errorPath, + $message, + array( + '{{ limit }}' => $limit, + ), + null + ); + } else { + $this->context + ->addViolation( + $constraint->invalidMessage, + array( + '{{ start }}' => $start->format($constraint->limitFormat), + '{{ end }}' => $end->format($constraint->limitFormat), + ) + ); + } + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php new file mode 100644 index 0000000000000..934ae585db3f5 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php @@ -0,0 +1,181 @@ + 'startDate', + 'end' => 'endDate', + )); + $this->validator->validate(new TestEvent(null, null), $constraint); + $this->assertNoViolation(); + } + + public function testStartBlank() + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + )); + $this->validator->validate(new TestEvent(null, new \DateTime()), $constraint); + $this->assertNoViolation(); + } + + public function testEndBlank() + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + )); + $this->validator->validate(new TestEvent(new \DateTime(), null), $constraint); + $this->assertNoViolation(); + } + + public function testEqual() + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + )); + $this->validator->validate(new TestEvent(new \DateTime(), new \DateTime()), $constraint); + $this->assertNoViolation(); + } + + /** + * @dataProvider dataTestValid + */ + public function testValid($value) + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + )); + $this->validator->validate($value, $constraint); + $this->assertNoViolation(); + } + + public function dataTestValid() + { + return array( + array( + new TestEvent(new \DateTime('2014-01-01'), new \DateTime('2014-01-02')), + ), + array( + new TestEvent(new \DateTime('2014-01-01 00:00:00'), new \DateTime('2014-01-01 00:30:00')), + ) + ); + } + + /** + * @param TestEvent $value + * @dataProvider dataTestInvalid + */ + public function testInvalid($value) + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + )); + $this->validator->validate($value, $constraint); + $this->assertViolation( + 'Invalid date range', + array( + '{{ start }}' => $value->getStartDate()->format('Y-m-d'), + '{{ end }}' => $value->getEndDate()->format('Y-m-d'), + ) + ); + } + + public function dataTestInvalid() + { + return array( + array( + new TestEvent(new \DateTime('2014-01-01'), new \DateTime('2013-12-31')), + ), + array( + new TestEvent(new \DateTime('2014-01-01 00:30:00'), new \DateTime('2014-01-01 00:00:00')), + ) + ); + } + + public function testErrorPathOnStart() + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + 'errorPath' => 'startDate', + )); + $value = new TestEvent(new \DateTime(), new \DateTime('-1 day')); + $this->validator->validate($value, $constraint); + $this->assertViolation( + 'Start date should be less than or equal to {{ limit }}', + array( + '{{ limit }}' => $value->getEndDate()->format('Y-m-d'), + ), + 'property.path.startDate', + null + ); + } + + public function testErrorPathOnEnd() + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + 'errorPath' => 'endDate', + )); + $value = new TestEvent(new \DateTime(), new \DateTime('-1 day')); + $this->validator->validate($value, $constraint); + $this->assertViolation( + 'End date should be greater than or equal to {{ limit }}', + array( + '{{ limit }}' => $value->getStartDate()->format('Y-m-d'), + ), + 'property.path.endDate', + null + ); + } + +} + +class TestEvent +{ + protected $startDate; + + protected $endDate; + + public function __construct($startDate, $endDate) + { + $this->startDate = $startDate; + $this->endDate = $endDate; + } + + public function getStartDate() + { + return $this->startDate; + } + + public function getEndDate() + { + return $this->endDate; + } +} From f0031ca3e33375457b8526f6ff8cf7cf69ba62f8 Mon Sep 17 00:00:00 2001 From: Bez Hermoso Date: Fri, 15 Aug 2014 11:23:55 -0700 Subject: [PATCH 2/3] 2.5 API. --- .../Constraints/DateRangeValidator.php | 69 ++++++++++++++----- .../Constraints/DateRangeValidatorTest.php | 10 +-- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php index b459f261bfbe5..da9f9a6f92998 100644 --- a/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php @@ -15,6 +15,7 @@ use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\UnexpectedTypeException; @@ -87,6 +88,18 @@ public function validate($value, Constraint $constraint) return; } + $context = $this->context; + + if (!$start instanceof \DateTime) { + $this->addViolation($value, $constraint->invalidMessage, array()); + return; + } + + if (!$end instanceof \DateTime) { + $this->addViolation($value, $constraint->invalidMessage, array()); + return; + } + $diff = $start->diff($end); if ($diff->invert === 0) { @@ -99,25 +112,45 @@ public function validate($value, Constraint $constraint) $limit = $constraint->errorPath === $constraint->end ? $start->format($constraint->limitFormat) : $end->format($constraint->limitFormat); - $this - ->context - ->addViolationAt( - $constraint->errorPath, - $message, - array( - '{{ limit }}' => $limit, - ), - null - ); + + $parameters = array( + '{{ limit }}' => $limit, + ); + + $this->addViolation($value, $message, $parameters, $constraint->errorPath); + + } else { + $parameters = array( + '{{ start }}' => $start->format($constraint->limitFormat), + '{{ end }}' => $end->format($constraint->limitFormat), + ); + $this->addViolation($value, $constraint->invalidMessage, $parameters); + } + } + + /** + * Abstract violation handling between Validator APIs + * + * @param $message + * @param array $parameters + * @param null $path + */ + private function addViolation($value, $message, array $parameters, $path = null) + { + $context = $this->context; + if ($context instanceof ExecutionContextInterface) { + $violation = $context->buildViolation($message, $parameters); + if ($path !== null) { + $violation->atPath($path); + } + $violation->setInvalidValue($value); + $violation->addViolation(); } else { - $this->context - ->addViolation( - $constraint->invalidMessage, - array( - '{{ start }}' => $start->format($constraint->limitFormat), - '{{ end }}' => $end->format($constraint->limitFormat), - ) - ); + if ($path === null) { + $context->addViolation($message, $parameters, $value); + } else { + $context->addViolationAt($path, $message, $parameters, $value); + } } } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php index 934ae585db3f5..9d967a479759c 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php @@ -12,7 +12,7 @@ class DateRangeValidatorTest extends AbstractConstraintValidatorTest protected function getApiVersion() { - return Validation::API_VERSION_2_4; + return Validation::API_VERSION_2_5; } protected function createValidator() @@ -101,7 +101,9 @@ public function testInvalid($value) array( '{{ start }}' => $value->getStartDate()->format('Y-m-d'), '{{ end }}' => $value->getEndDate()->format('Y-m-d'), - ) + ), + 'property.path', + $value ); } @@ -132,7 +134,7 @@ public function testErrorPathOnStart() '{{ limit }}' => $value->getEndDate()->format('Y-m-d'), ), 'property.path.startDate', - null + $value ); } @@ -151,7 +153,7 @@ public function testErrorPathOnEnd() '{{ limit }}' => $value->getStartDate()->format('Y-m-d'), ), 'property.path.endDate', - null + $value ); } From 513f9e572f3fbd370b68e238b7b69cc1f37b4529 Mon Sep 17 00:00:00 2001 From: Bez Hermoso Date: Fri, 15 Aug 2014 14:26:16 -0700 Subject: [PATCH 3/3] min and max intervals. --- .../Validator/Constraints/DateRange.php | 12 +- .../Constraints/DateRangeValidator.php | 49 +++++- .../Constraints/CollectionValidatorTest.php | 2 +- .../Constraints/DateRangeValidatorTest.php | 166 +++++++++++++++++- 4 files changed, 219 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/DateRange.php b/src/Symfony/Component/Validator/Constraints/DateRange.php index fb352ce97957e..f90380e8fc2af 100644 --- a/src/Symfony/Component/Validator/Constraints/DateRange.php +++ b/src/Symfony/Component/Validator/Constraints/DateRange.php @@ -33,18 +33,20 @@ class DateRange extends Constraint */ public $end; - public $startMessage = 'Start date should be less than or equal to {{ limit }}'; + public $startMessage = 'Start date should be earlier than or equal to {{ limit }}'; - public $endMessage = 'End date should be greater than or equal to {{ limit }}'; + public $endMessage = 'End date should be later than or equal to {{ limit }}'; - public $emptyStartMessage = 'Start date cannot be empty'; - - public $emptyEndMessage = 'End date cannot be empty'; + public $invalidIntervalMessage = 'Dates must be {{ interval }} apart'; public $invalidMessage = 'Invalid date range'; public $limitFormat = 'Y-m-d'; + public $min = null; + + public $max = null; + /** * @var string The property to attach the error message on. */ diff --git a/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php index da9f9a6f92998..7f5259986e551 100644 --- a/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php @@ -88,8 +88,6 @@ public function validate($value, Constraint $constraint) return; } - $context = $this->context; - if (!$start instanceof \DateTime) { $this->addViolation($value, $constraint->invalidMessage, array()); return; @@ -103,6 +101,7 @@ public function validate($value, Constraint $constraint) $diff = $start->diff($end); if ($diff->invert === 0) { + $this->checkIntervals($value, clone $start, clone $end, $constraint, $diff); return; } @@ -153,4 +152,50 @@ private function addViolation($value, $message, array $parameters, $path = null) } } } + + /** + * @param \DateInterval $interval + * + * @return int + */ + private function convertIntervalToSeconds(\DateInterval $interval) + { + return $interval->d * 60 * 60 * 24 + + $interval->h * 60 * 60 + + $interval->i * 60 + + $interval->s; + } + + private function checkIntervals( + $value, + \DateTime $start, + \DateTime $end, + DateRange $constraint, + \DateInterval $diff + ) { + + $diffSeconds = $this->convertIntervalToSeconds($diff); + + if ($constraint->min) { + $minInterval = \DateInterval::createFromDateString('+' . $constraint->min); + $start->add($minInterval); + $diff = $start->diff($end); + if ($diff->invert === 1) { + $this->addViolation($value, $constraint->invalidIntervalMessage, array( + '{{ interval }}' => $constraint->min, + ), $constraint->errorPath); + } + } + + if ($constraint->max) { + $maxInterval = \DateInterval::createFromDateString('+' . $constraint->max); + $start->add($maxInterval); + $diff = $end->diff($start); + if ($diff->invert === 1) { + $this->addViolation($value, $constraint->invalidIntervalMessage, array( + '{{ interval }}' => $constraint->max, + ), $constraint->errorPath); + } + } + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php index cafcafe472afb..da480ea14441a 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php @@ -19,7 +19,7 @@ use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\Validation; -abstract class CollectionValidatorTest extends AbstractConstraintValidatorTest +abstract class CollectionValidatorTest extends AbstractConstraintValidatorTest { protected function getApiVersion() { diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php index 9d967a479759c..93c4fed8e229d 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php @@ -129,7 +129,7 @@ public function testErrorPathOnStart() $value = new TestEvent(new \DateTime(), new \DateTime('-1 day')); $this->validator->validate($value, $constraint); $this->assertViolation( - 'Start date should be less than or equal to {{ limit }}', + 'Start date should be earlier than or equal to {{ limit }}', array( '{{ limit }}' => $value->getEndDate()->format('Y-m-d'), ), @@ -148,7 +148,7 @@ public function testErrorPathOnEnd() $value = new TestEvent(new \DateTime(), new \DateTime('-1 day')); $this->validator->validate($value, $constraint); $this->assertViolation( - 'End date should be greater than or equal to {{ limit }}', + 'End date should be later than or equal to {{ limit }}', array( '{{ limit }}' => $value->getStartDate()->format('Y-m-d'), ), @@ -157,6 +157,168 @@ public function testErrorPathOnEnd() ); } + /** + * @param $value + * @param $interval + * + * @dataProvider dateMinIntervalViolations + */ + public function testMinIntervalViolations($value, $interval) + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + 'min' => $interval + )); + $this->validator->validate($value, $constraint); + $this->assertViolation( + 'Dates must be {{ interval }} apart', + array( + '{{ interval }}' => $interval, + ), + 'property.path', + $value + ); + } + + public function dateMinIntervalViolations() + { + return array( + array( + new TestEvent(new \DateTime(), new \DateTime('+1 hour')), + '1 day', + ), + array( + new TestEvent(new \DateTime(), new \DateTime('+1 week')), + '1 month', + ), + array( + new TestEvent(new \DateTime('2014-02-01'), new \DateTime('2014-02-27')), + '1 month', + ), + array( + new TestEvent(new \DateTime('-30 seconds'), new \DateTime()), + '1 minute', + ), + array( + new TestEvent(new \DateTime(), new \DateTime('+1 month 2 days')), + '1 month + 10 days', + ), + ); + } + + /** + * @param $value + * @param $interval + * + * @dataProvider dateMinIntervalValid + */ + public function testMinIntervalsValid($value, $interval) + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + 'min' => $interval + )); + $this->validator->validate($value, $constraint); + $this->assertNoViolation(); + } + + public function dateMinIntervalValid() + { + return array( + array( + new TestEvent(new \DateTime(), new \DateTime('+1 day')), + '1 day', + ), + array( + new TestEvent(new \DateTime(), new \DateTime('+1 week')), + '5 days', + ), + array( + new TestEvent(new \DateTime('2014-02-01'), new \DateTime('2014-03-01')), + '1 month', + ), + ); + } + + /** + * @param $value + * @param $interval + * + * @dataProvider dateMaxIntervalViolations + */ + public function testMaxIntervalViolations($value, $interval) + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + 'max' => $interval + )); + $this->validator->validate($value, $constraint); + $this->assertViolation( + 'Dates must be {{ interval }} apart', + array( + '{{ interval }}' => $interval, + ), + 'property.path', + $value + ); + } + + public function dateMaxIntervalViolations() + { + return array( + array( + new TestEvent(new \DateTime(), new \DateTime('+1 day 1 seconds')), + '1 day', + ), + array( + new TestEvent(new \DateTime(), new \DateTime('+1 year')), + '1 month', + ), + array( + new TestEvent(new \DateTime('2014-12-01'), new \DateTime('2015-01-02')), + '1 month', + ), + ); + } + + /** + * @param $value + * @param $interval + * + * @dataProvider dateMaxIntervalValid + */ + public function testMaxIntervalsValid($value, $interval) + { + $constraint = new DateRange(array( + 'start' => 'startDate', + 'end' => 'endDate', + 'max' => $interval + )); + $this->validator->validate($value, $constraint); + $this->assertNoViolation(); + } + + public function dateMaxIntervalValid() + { + return array( + array( + new TestEvent(new \DateTime(), new \DateTime('+1 day')), + '1 day', + ), + array( + new TestEvent(new \DateTime(), new \DateTime('+3 days')), + '5 days', + ), + array( + new TestEvent(new \DateTime('2014-02-01'), new \DateTime('2014-03-01')), + '1 month', + ), + ); + } + } class TestEvent pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy