8000 [Validator] Allow to use property paths to get limits in range constr… · symfony/symfony@2b50990 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2b50990

Browse files
Lctrsfabpot
authored andcommitted
[Validator] Allow to use property paths to get limits in range constraint
1 parent dca9325 commit 2b50990

File tree

5 files changed

+547
-12
lines changed

5 files changed

+547
-12
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ CHANGELOG
88
* added the `compared_value_path` parameter in violations when using any
99
comparison constraint with the `propertyPath` option.
1010
* added support for checking an array of types in `TypeValidator`
11+
* Added new `minPropertyPath` and `maxPropertyPath` options
12+
to `Range` constraint in order to get the value to compare
13+
from an array or object
14+
* added the `limit_path` parameter in violations when using
15+
`Range` constraint with the `minPropertyPath` or
16+
`maxPropertyPath` options.
1117

1218
4.3.0
1319
-----

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
namespace Symfony\Component\Validator\Constraints;
1313

14+
use Symfony\Component\PropertyAccess\PropertyAccess;
1415
use Symfony\Component\Validator\Constraint;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
use Symfony\Component\Validator\Exception\LogicException;
1518
use Symfony\Component\Validator\Exception\MissingOptionsException;
1619

1720
/**
@@ -36,14 +39,30 @@ class Range extends Constraint
3639
public $maxMessage = 'This value should be {{ limit }} or less.';
3740
public $invalidMessage = 'This value should be a valid number.';
3841
public $min;
42+
public $minPropertyPath;
3943
public $max;
44+
public $maxPropertyPath;
4045

4146
public function __construct($options = null)
4247
{
48+
if (\is_array($options)) {
49+
if (isset($options['min']) && isset($options['minPropertyPath'])) {
50+
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', \get_class($this)));
51+
}
52+
53+
if (isset($options['max']) && isset($options['maxPropertyPath'])) {
54+
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', \get_class($this)));
55+
}
56+
< E864 /td>57+
if ((isset($options['minPropertyPath']) || isset($options['maxPropertyPath'])) && !class_exists(PropertyAccess::class)) {
58+
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', \get_class($this)));
59+
}
60+
}
61+
4362
parent::__construct($options);
4463

45-
if (null === $this->min && null === $this->max) {
46-
throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint %s', __CLASS__), ['min', 'max']);
64+
if (null === $this->min && null === $this->minPropertyPath && null === $this->max && null === $this->maxPropertyPath) {
65+
throw new MissingOptionsException(sprintf('Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given for constraint %s', __CLASS__), ['min', 'max']);
4766
}
4867
}
4968
}

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

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,26 @@
1111

1212
namespace Symfony\Component\Validator\Constraints;
1313

14+
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
15+
use Symfony\Component\PropertyAccess\PropertyAccess;
16+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1417
use Symfony\Component\Validator\Constraint;
1518
use Symfony\Component\Validator\ConstraintValidator;
19+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1620
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
1721

1822
/**
1923
* @author Bernhard Schussek <bschussek@gmail.com>
2024
*/
2125
class RangeValidator extends ConstraintValidator
2226
{
27+
private $propertyAccessor;
28+
29+
public function __construct(PropertyAccessorInterface $propertyAccessor = null)
30+
{
31+
$this->propertyAccessor = $propertyAccessor;
32+
}
33+
2334
/**
2435
* {@inheritdoc}
2536
*/
@@ -42,8 +53,8 @@ public function validate($value, Constraint $constraint)
4253
return;
4354
}
4455

45-
$min = $constraint->min;
46-
$max = $constraint->max;
56+
$min = $this->getLimit($constraint->minPropertyPath, $constraint->min, $constraint);
57+
$max = $this->getLimit($constraint->maxPropertyPath, $constraint->max, $constraint);
4758

4859
// Convert strings to DateTimes if comparing another DateTime
4960
// This allows to compare with any date/time value supported by
@@ -59,22 +70,66 @@ public function validate($value, Constraint $constraint)
5970
}
6071
}
6172

62-
if (null !== $constraint->max && $value > $max) {
63-
$this->context->buildViolation($constraint->maxMessage)
73+
if (null !== $max && $value > $max) {
74+
$violationBuilder = $this->context->buildViolation($constraint->maxMessage)
6475
->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
6576
->setParameter('{{ limit }}', $this->formatValue($max, self::PRETTY_DATE))
66-
->setCode(Range::TOO_HIGH_ERROR)
67-
->addViolation();
77+
->setCode(Range::TOO_HIGH_ERROR);
78+
79+
if (null !== $constraint->maxPropertyPath) {
80+
$violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
81+
}
82+
83+
if (null !== $constraint->minPropertyPath) {
84+
$violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
85+
}
86+
87+
$violationBuilder->addViolation();
6888

6989
return;
7090
}
7191

72-
if (null !== $constraint->min && $value < $min) {
73-
$this->context->buildViolation($constraint->minMessage)
92+
if (null !== $min && $value < $min) {
93+
$violationBuilder = $this->context->buildViolation($constraint->minMessage)
7494
->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
7595
->setParameter('{{ limit }}', $this->formatValue($min, self::PRETTY_DATE))
76-
->setCode(Range::TOO_LOW_ERROR)
77-
->addViolation();
96+
->setCode(Range::TOO_LOW_ERROR);
97+
98+
if (null !== $constraint->maxPropertyPath) {
99+
$violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
100+
}
101+
102+
if (null !== $constraint->minPropertyPath) {
103+
$violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
104+
}
105+
106+
$violationBuilder->addViolation();
107+
}
108+
}
109+
110+
private function getLimit($propertyPath, $default, Constraint $constraint)
111+
{
112+
if (null === $propertyPath) {
113+
return $default;
114+
}
115+
116+
if (null === $object = $this->context->getObject()) {
117+
return $default;
78118
}
119+
120+
try {
121+
return $this->getPropertyAccessor()->getValue($object, $propertyPath);
122+
} catch (NoSuchPropertyException $e) {
123+
throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s', $propertyPath, \get_class($constraint), $e->getMessage()), 0, $e);
124+
}
125+
}
126+
127+
private function getPropertyAccessor(): PropertyAccessorInterface
128+
{
129+
if (null === $this->propertyAccessor) {
130+
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
131+
}
132+
133+
return $this->propertyAccessor;
79134
}
80135
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Symfony\Component\Validator\Tests\Constraints;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Validator\Constraints\Range;
7+
8+
class RangeTest extends TestCase
9+
{
10+
/**
11+
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
12+
* @expectedExceptionMessage requires only one of the "min" or "minPropertyPath" options to be set, not both.
13+
*/
14+
public function testThrowsConstraintExceptionIfBothMinLimitAndPropertyPath()
15+
{
16+
new Range([
17+
'min' => 'min',
18+
'minPropertyPath' => 'minPropertyPath',
19+
]);
20+
}
21+
22+
/**
23+
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
24+
* @expectedExceptionMessage requires only one of the "max" or "maxPropertyPath" options to be set, not both.
25+
*/
26+
public function testThrowsConstraintExceptionIfBothMaxLimitAndPropertyPath()
27+
{
28+
new Range([
29+
'max' => 'min',
30+
'maxPropertyPath' => 'maxPropertyPath',
31+
]);
32+
}
33+
34+
/**
35+
* @expectedException \Symfony\Component\Validator\Exception\MissingOptionsException
36+
* @expectedExceptionMessage Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given
37+
*/
38+
public function testThrowsConstraintExceptionIfNoLimitNorPropertyPath()
39+
{
40+
new Range([]);
41+
}
42+
43+
/**
44+
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
45+
* @expectedExceptionMessage No default option is configured
46+
*/
47+
public function testThrowsNoDefaultOptionConfiguredException()
48+
{
49+
new Range('value');
50+
}
51+
}

0 commit comments

Comments
 (0)
0