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+
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)
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