diff --git a/src/Symfony/Component/Validator/Constraints/DateRange.php b/src/Symfony/Component/Validator/Constraints/DateRange.php new file mode 100644 index 0000000000000..f90380e8fc2af --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/DateRange.php @@ -0,0 +1,67 @@ + + * + * 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 earlier than or equal to {{ limit }}'; + + public $endMessage = 'End date should be later than or equal to {{ limit }}'; + + 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. + */ + 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..7f5259986e551 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/DateRangeValidator.php @@ -0,0 +1,201 @@ + + * + * 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\Context\ExecutionContextInterface; +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; + } + + 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) { + $this->checkIntervals($value, clone $start, clone $end, $constraint, $diff); + 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); + + $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 { + if ($path === null) { + $context->addViolation($message, $parameters, $value); + } else { + $context->addViolationAt($path, $message, $parameters, $value); + } + } + } + + /** + * @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 new file mode 100644 index 0000000000000..93c4fed8e229d --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/DateRangeValidatorTest.php @@ -0,0 +1,345 @@ + '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'), + ), + 'property.path', + $value + ); + } + + 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 earlier than or equal to {{ limit }}', + array( + '{{ limit }}' => $value->getEndDate()->format('Y-m-d'), + ), + 'property.path.startDate', + $value + ); + } + + 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 later than or equal to {{ limit }}', + array( + '{{ limit }}' => $value->getStartDate()->format('Y-m-d'), + ), + 'property.path.endDate', + $value + ); + } + + /** + * @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 +{ + 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; + } +} 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