Skip to content

Commit 1d7684a

Browse files
feature #11673 [Validator] Added date support to comparison constraints and Range (webmozart)
This PR was merged into the 2.6-dev branch. Discussion ---------- [Validator] Added date support to comparison constraints and Range | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #3640, #7766, #9164, #9390, #8300 | License | MIT | Doc PR | symfony/symfony-docs#4143 This commit adds frequently requested functionality to compare dates. Since the `DateTime` constructor is very flexible, you can do many fancy things now such as: ```php /** * Only accept requests that start in at least an hour. * @Assert\GreaterThanOrEqual("+1 hours") */ private $date; /** * Same as before. * @Assert\Range(min = "+1 hours") */ private $date; /** * Only accept dates in the current year. * @Assert\Range(min = "first day of January", max = "first day of January next year") */ private $date; /** * Timezones are supported. * @Assert\Range(min = "first day of January UTC", max = "first day of January next year UTC") */ private $date; ``` Commits ------- 60a5863 [Validator] Added date support to comparison constraints and Range
2 parents 2293556 + 60a5863 commit 1d7684a

14 files changed

+419
-14
lines changed

src/Symfony/Component/Validator/ConstraintValidator.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,22 @@ protected function formatTypeOf($value)
8989
*/
9090
protected function formatValue($value, $format = 0)
9191
{
92-
if (($format & self::PRETTY_DATE) && $value instanceof \DateTime) {
92+
$isDateTime = $value instanceof \DateTime || $value instanceof \DateTimeInterface;
93+
94+
if (($format & self::PRETTY_DATE) && $isDateTime) {
9395
if (class_exists('IntlDateFormatter')) {
9496
$locale = \Locale::getDefault();
9597
$formatter = new \IntlDateFormatter($locale, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT);
9698

99+
// neither the native nor the stub IntlDateFormatter support
100+
// DateTimeImmutable as of yet
101+
if (!$value instanceof \DateTime) {
102+
$value = new \DateTime(
103+
$value->format('Y-m-d H:i:s.u e'),
104+
$value->getTimezone()
105+
);
106+
}
107+
97108
return $formatter->format($value);
98109
}
99110

src/Symfony/Component/Validator/Constraints/AbstractComparisonValidator.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,28 @@ public function validate($value, Constraint $constraint)
3535
return;
3636
}
3737

38-
if (!$this->compareValues($value, $constraint->value)) {
38+
$comparedValue = $constraint->value;
39+
40+
// Convert strings to DateTimes if comparing another DateTime
41+
// This allows to compare with any date/time value supported by
42+
// the DateTime constructor:
43+
// http://php.net/manual/en/datetime.formats.php
44+
if (is_string($comparedValue)) {
45+
if ($value instanceof \DatetimeImmutable) {
46+
// If $value is immutable, convert the compared value to a
47+
// DateTimeImmutable too
48+
$comparedValue = new \DatetimeImmutable($comparedValue);
49+
} elseif ($value instanceof \DateTime || $value instanceof \DateTimeInterface) {
50+
// Otherwise use DateTime
51+
$comparedValue = new \DateTime($comparedValue);
52+
}
53+
}
54+
55+
if (!$this->compareValues($value, $comparedValue)) {
3956
$this->context->addViolation($constraint->message, array(
4057
'{{ value }}' => $this->formatValue($value, self::OBJECT_TO_STRING | self::PRETTY_DATE),
41-
'{{ compared_value }}' => $this->formatValue($constraint->value, self::OBJECT_TO_STRING | self::PRETTY_DATE),
42-
'{{ compared_value_type }}' => $this->formatTypeOf($constraint->value)
58+
'{{ compared_value }}' => $this->formatValue($comparedValue, self::OBJECT_TO_STRING | self::PRETTY_DATE),
59+
'{{ compared_value_type }}' => $this->formatTypeOf($comparedValue)
4360
));
4461
}
4562
}

src/Symfony/Component/Validator/Constraints/RangeValidator.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,44 @@ public function validate($value, Constraint $constraint)
3333
return;
3434
}
3535

36-
if (!is_numeric($value)) {
36+
if (!is_numeric($value) && !$value instanceof \DateTime && !$value instanceof \DateTimeInterface) {
3737
$this->context->addViolation($constraint->invalidMessage, array(
3838
'{{ value }}' => $this->formatValue($value),
3939
));
4040

4141
return;
4242
}
4343

44-
if (null !== $constraint->max && $value > $constraint->max) {
44+
$min = $constraint->min;
45+
$max = $constraint->max;
46+
47+
// Convert strings to DateTimes if comparing another DateTime
48+
// This allows to compare with any date/time value supported by
49+
// the DateTime constructor:
50+
// http://php.net/manual/en/datetime.formats.php
51+
if ($value instanceof \DateTime || $value instanceof \DateTimeInterface) {
52+
if (is_string($min)) {
53+
$min = new \DateTime($min);
54+
}
55+
56+
if (is_string($max)) {
57+
$max = new \DateTime($max);
58+
}
59+
}
60+
61+
if (null !== $constraint->max && $value > $max) {
4562
$this->context->addViolation($constraint->maxMessage, array(
4663
'{{ value }}' => $value,
47-
'{{ limit }}' => $constraint->max,
64+
'{{ limit }}' => $this->formatValue($max, self::PRETTY_DATE),
4865
));
4966

5067
return;
5168
}
5269

53-
if (null !== $constraint->min && $value < $constraint->min) {
70+
if (null !== $constraint->min && $value < $min) {
5471
$this->context->addViolation($constraint->minMessage, array(
5572
'{{ value }}' => $value,
56-
'{{ limit }}' => $constraint->min,
73+
'{{ limit }}' => $this->formatValue($min, self::PRETTY_DATE),
5774
));
5875
}
5976
}

src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ public function __toString()
3434
*/
3535
abstract class AbstractComparisonValidatorTestCase extends AbstractConstraintValidatorTest
3636
{
37+
protected static function addPhp5Dot5Comparisons(array $comparisons)
38+
{
39+
if (version_compare(PHP_VERSION, '5.5.0-dev', '<')) {
40+
return $comparisons;
41+
}
42+
43+
$result = $comparisons;
44+
45+
// Duplicate all tests involving DateTime objects to be tested with
46+
// DateTimeImmutable objects as well
47+
foreach ($comparisons as $comparison) {
48+
$add = false;
49+
50+
foreach ($comparison as $i => $value) {
51+
if ($value instanceof \DateTime) {
52+
$comparison[$i] = new \DateTimeImmutable(
53+
$value->format('Y-m-d H:i:s.u e'),
54+
$value->getTimezone()
55+
);
56+
$add = true;
57+
} elseif ('DateTime' === $value) {
58+
$comparison[$i] = 'DateTimeImmutable';
59+
$add = true;
60+
}
61+
}
62+
63+
if ($add) {
64+
$result[] = $comparison;
65+
}
66+
}
67+
68+
return $result;
69+
}
70+
3771
/**
3872
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
3973
*/
@@ -45,7 +79,7 @@ public function testThrowsConstraintExceptionIfNoValueOrProperty()
4579
}
4680

4781
/**
48-
* @dataProvider provideValidComparisons
82+
* @dataProvider provideAllValidComparisons
4983
* @param mixed $dirtyValue
5084
* @param mixed $comparisonValue
5185
*/
@@ -58,13 +92,29 @@ public function testValidComparisonToValue($dirtyValue, $comparisonValue)
5892
$this->assertNoViolation();
5993
}
6094

95+
/**
96+
* @return array
97+
*/
98+
public function provideAllValidComparisons()
99+
{
100+
// The provider runs before setUp(), so we need to manually fix
101+
// the default timezone
102+
$this->setDefaultTimezone('UTC');
103+
104+
$comparisons = self::addPhp5Dot5Comparisons($this->provideValidComparisons());
105+
106+
$this->restoreDefaultTimezone();
107+
108+
return $comparisons;
109+
}
110+
61111
/**
62112
* @return array
63113
*/
64114
abstract public function provideValidComparisons();
65115

66116
/**
67-
* @dataProvider provideInvalidComparisons
117+
* @dataProvider provideAllInvalidComparisons
68118
* @param mixed $dirtyValue
69119
* @param mixed $dirtyValueAsString
70120
* @param mixed $comparedValue
@@ -75,7 +125,7 @@ public function testInvalidComparisonToValue($dirtyValue, $dirtyValueAsString, $
75125
{
76126
// Conversion of dates to string differs between ICU versions
77127
// Make sure we have the correct version loaded
78-
if ($dirtyValue instanceof \DateTime) {
128+
if ($dirtyValue instanceof \DateTime || $dirtyValue instanceof \DateTimeInterface) {
79129
IntlTestHelper::requireIntl($this);
80130
}
81131

@@ -91,6 +141,22 @@ public function testInvalidComparisonToValue($dirtyValue, $dirtyValueAsString, $
91141
));
92142
}
93143

144+
/**
145+
* @return array
146+
*/
147+
public function provideAllInvalidComparisons()
148+
{
149+
// The provider runs before setUp(), so we need to manually fix
150+
// the default timezone
151+
$this->setDefaultTimezone('UTC');
152+
153+
$comparisons = self::addPhp5Dot5Comparisons($this->provideInvalidComparisons());
154+
155+
$this->restoreDefaultTimezone();
156+
157+
return $comparisons;
158+
}
159+
94160
/**
95161
* @return array
96162
*/

src/Symfony/Component/Validator/Tests/Constraints/AbstractConstraintValidatorTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ abstract class AbstractConstraintValidatorTest extends \PHPUnit_Framework_TestCa
5252

5353
protected $constraint;
5454

55+
protected $defaultTimezone;
56+
5557
protected function setUp()
5658
{
5759
$this->group = 'MyGroup';
@@ -74,6 +76,31 @@ protected function setUp()
7476
$this->validator->initialize($this->context);
7577

7678
\Locale::setDefault('en');
79+
80+
$this->setDefaultTimezone('UTC');
81+
}
82+
83+
protected function tearDown()
84+
{
85+
$this->restoreDefaultTimezone();
86+
}
87+
88+
protected function setDefaultTimezone($defaultTimezone)
89+
{
90+
// Make sure this method can not be called twice before calling
91+
// also restoreDefaultTimezone()
92+
if (null === $this->defaultTimezone) {
93+
$this->defaultTimezone = ini_get('date.timezone');
94+
ini_set('date.timezone', $defaultTimezone);
95+
}
96+
}
97+
98+
protected function restoreDefaultTimezone()
99+
{
100+
if (null !== $this->defaultTimezone) {
101+
ini_set('date.timezone', $this->defaultTimezone);
102+
$this->defaultTimezone = null;
103+
}
77104
}
78105

79106
protected function createContext()

src/Symfony/Component/Validator/Tests/Constraints/EqualToValidatorTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public function provideValidComparisons()
4545
array(3, '3'),
4646
array('a', 'a'),
4747
array(new \DateTime('2000-01-01'), new \DateTime('2000-01-01')),
48+
array(new \DateTime('2000-01-01'), '2000-01-01'),
49+
array(new \DateTime('2000-01-01 UTC'), '2000-01-01 UTC'),
4850
array(new ComparisonTest_Class(5), new ComparisonTest_Class(5)),
4951
array(null, 1),
5052
);
@@ -59,6 +61,8 @@ public function provideInvalidComparisons()
5961
array(1, '1', 2, '2', 'integer'),
6062
array('22', '"22"', '333', '"333"', 'string'),
6163
array(new \DateTime('2001-01-01'), 'Jan 1, 2001, 12:00 AM', new \DateTime('2000-01-01'), 'Jan 1, 2000, 12:00 AM', 'DateTime'),
64+
array(new \DateTime('2001-01-01'), 'Jan 1, 2001, 12:00 AM', '2000-01-01', 'Jan 1, 2000, 12:00 AM', 'DateTime'),
65+
array(new \DateTime('2001-01-01 UTC'), 'Jan 1, 2001, 12:00 AM', '2000-01-01 UTC', 'Jan 1, 2000, 12:00 AM', 'DateTime'),
6266
array(new ComparisonTest_Class(4), '4', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'),
6367
);
6468
}

src/Symfony/Component/Validator/Tests/Constraints/GreaterThanOrEqualValidatorTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public function provideValidComparisons()
4545
array(1, 1),
4646
array(new \DateTime('2010/01/01'), new \DateTime('2000/01/01')),
4747
array(new \DateTime('2000/01/01'), new \DateTime('2000/01/01')),
48+
array(new \DateTime('2010/01/01'), '2000/01/01'),
49+
array(new \DateTime('2000/01/01'), '2000/01/01'),
50+
array(new \DateTime('2010/01/01 UTC'), '2000/01/01 UTC'),
51+
array(new \DateTime('2000/01/01 UTC'), '2000/01/01 UTC'),
4852
array('a', 'a'),
4953
array('z', 'a'),
5054
array(null, 1),
@@ -59,6 +63,8 @@ public function provideInvalidComparisons()
5963
return array(
6064
array(1, '1', 2, '2', 'integer'),
6165
array(new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', new \DateTime('2005/01/01'), 'Jan 1, 2005, 12:00 AM', 'DateTime'),
66+
array(new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', '2005/01/01', 'Jan 1, 2005, 12:00 AM', 'DateTime'),
67+
array(new \DateTime('2000/01/01 UTC'), 'Jan 1, 2000, 12:00 AM', '2005/01/01 UTC', 'Jan 1, 2005, 12:00 AM', 'DateTime'),
6268
array('b', '"b"', 'c', '"c"', 'string')
6369
);
6470
}

src/Symfony/Component/Validator/Tests/Constraints/GreaterThanValidatorTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public function provideValidComparisons()
4343
return array(
4444
array(2, 1),
4545
array(new \DateTime('2005/01/01'), new \DateTime('2001/01/01')),
46+
array(new \DateTime('2005/01/01'), '2001/01/01'),
47+
array(new \DateTime('2005/01/01 UTC'), '2001/01/01 UTC'),
4648
array(new ComparisonTest_Class(5), new ComparisonTest_Class(4)),
4749
array('333', '22'),
4850
array(null, 1),
@@ -59,6 +61,10 @@ public function provideInvalidComparisons()
5961
array(2, '2', 2, '2', 'integer'),
6062
array(new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', new \DateTime('2005/01/01'), 'Jan 1, 2005, 12:00 AM', 'DateTime'),
6163
array(new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', 'DateTime'),
64+
array(new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', '2005/01/01', 'Jan 1, 2005, 12:00 AM', 'DateTime'),
65+
array(new \DateTime('2000/01/01'), 'Jan 1, 2000, 12:00 AM', '2000/01/01', 'Jan 1, 2000, 12:00 AM', 'DateTime'),
66+
array(new \DateTime('2000/01/01 UTC'), 'Jan 1, 2000, 12:00 AM', '2005/01/01 UTC', 'Jan 1, 2005, 12:00 AM', 'DateTime'),
67+
array(new \DateTime('2000/01/01 UTC'), 'Jan 1, 2000, 12:00 AM', '2000/01/01 UTC', 'Jan 1, 2000, 12:00 AM', 'DateTime'),
6268
array(new ComparisonTest_Class(4), '4', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'),
6369
array(new ComparisonTest_Class(5), '5', new ComparisonTest_Class(5), '5', __NAMESPACE__.'\ComparisonTest_Class'),
6470
array('22', '"22"', '333', '"333"', 'string'),

src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ protected function createConstraint(array $options)
3535
return new IdenticalTo($options);
3636
}
3737

38+
public function provideAllValidComparisons()
39+
{
40+
$this->setDefaultTimezone('UTC');
41+
42+
// Don't call addPhp5Dot5Comparisons() automatically, as it does
43+
// not take care of identical objects
44+
$comparisons = $this->provideValidComparisons();
45+
46+
$this->restoreDefaultTimezone();
47+
48+
return $comparisons;
49+
}
50+
3851
/**
3952
* {@inheritdoc}
4053
*/
@@ -43,13 +56,20 @@ public function provideValidComparisons()
4356
$date = new \DateTime('2000-01-01');
4457
$object = new ComparisonTest_Class(2);
4558

46-
return array(
59+
$comparisons = array(
4760
array(3, 3),
4861
array('a', 'a'),
4962
array($date, $date),
5063
array($object, $object),
5164
array(null, 1),
5265
);
66+
67+
if (version_compare(PHP_VERSION, '>=', '5.5')) {
68+
$immutableDate = new \DateTimeImmutable('2000-01-01');
69+
$comparisons[] = array($immutableDate, $immutableDate);
70+
}
71+
72+
return $comparisons;
5373
}
5474

5575
/**

src/Symfony/Component/Validator/Tests/Constraints/LessThanOrEqualValidatorTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public function provideValidComparisons()
4545
array(1, 1),
4646
array(new \DateTime('2000-01-01'), new \DateTime('2000-01-01')),
4747
array(new \DateTime('2000-01-01'), new \DateTime('2020-01-01')),
48+
array(new \DateTime('2000-01-01'), '2000-01-01'),
49+
array(new \DateTime('2000-01-01'), '2020-01-01'),
50+
array(new \DateTime('2000-01-01 UTC'), '2000-01-01 UTC'),
51+
array(new \DateTime('2000-01-01 UTC'), '2020-01-01 UTC'),
4852
array(new ComparisonTest_Class(4), new ComparisonTest_Class(5)),
4953
array(new ComparisonTest_Class(5), new ComparisonTest_Class(5)),
5054
array('a', 'a'),
@@ -61,6 +65,8 @@ public function provideInvalidComparisons()
6165
return array(
6266
array(2, '2', 1, '1', 'integer'),
6367
array(new \DateTime('2010-01-01'), 'Jan 1, 2010, 12:00 AM', new \DateTime('2000-01-01'), 'Jan 1, 2000, 12:00 AM', 'DateTime'),
68+
array(new \DateTime('2010-01-01'), 'Jan 1, 2010, 12:00 AM', '2000-01-01', 'Jan 1, 2000, 12:00 AM', 'DateTime'),
69+
array(new \DateTime('2010-01-01 UTC'), 'Jan 1, 2010, 12:00 AM', '2000-01-01 UTC', 'Jan 1, 2000, 12:00 AM', 'DateTime'),
6470
array(new ComparisonTest_Class(5), '5', new ComparisonTest_Class(4), '4', __NAMESPACE__.'\ComparisonTest_Class'),
6571
array('c', '"c"', 'b', '"b"', 'string')
6672
);

0 commit comments

Comments
 (0)
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