Skip to content

Commit

Permalink
feature #31511 [Validator] Allow to use property paths to get limits …
Browse files Browse the repository at this point in the history
…in range constraint (Lctrs)

This PR was squashed before being merged into the 4.4 branch (closes #31511).

Discussion
----------

[Validator] Allow to use property paths to get limits in range constraint

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | Part of #31503
| License       | MIT
| Doc PR        | symfony/symfony-docs#11793

Similar as #22576, but for the `Range` constraint.

Commits
-------

2b50990 [Validator] Allow to use property paths to get limits in range constraint
  • Loading branch information
fabpot committed Jul 8, 2019
2 parents 2d04e20 + 2b50990 commit e1cea97
Show file tree
Hide file tree
Showing 5 changed files with 547 additions and 12 deletions.
6 changes: 6 additions & 0 deletions src/Symfony/Component/Validator/CHANGELOG.md
Expand Up @@ -12,6 +12,12 @@ CHANGELOG
comparison constraint with the `propertyPath` option.
* added support for checking an array of types in `TypeValidator`
* added a new `allowEmptyString` option to the `Length` constraint to allow rejecting empty strings when `min` is set, by setting it to `false`.
* Added new `minPropertyPath` and `maxPropertyPath` options
to `Range` constraint in order to get the value to compare
from an array or object
* added the `limit_path` parameter in violations when using
`Range` constraint with the `minPropertyPath` or
`maxPropertyPath` options.

4.3.0
-----
Expand Down
23 changes: 21 additions & 2 deletions src/Symfony/Component/Validator/Constraints/Range.php
Expand Up @@ -11,7 +11,10 @@

namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
use Symfony\Component\Validator\Exception\MissingOptionsException;

/**
Expand All @@ -36,14 +39,30 @@ class Range extends Constraint
public $maxMessage = 'This value should be {{ limit }} or less.';
public $invalidMessage = 'This value should be a valid number.';
public $min;
public $minPropertyPath;
public $max;
public $maxPropertyPath;

public function __construct($options = null)
{
if (\is_array($options)) {
if (isset($options['min']) && isset($options['minPropertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "min" or "minPropertyPath" options to be set, not both.', \get_class($this)));
}

if (isset($options['max']) && isset($options['maxPropertyPath'])) {
throw new ConstraintDefinitionException(sprintf('The "%s" constraint requires only one of the "max" or "maxPropertyPath" options to be set, not both.', \get_class($this)));
}

if ((isset($options['minPropertyPath']) || isset($options['maxPropertyPath'])) && !class_exists(PropertyAccess::class)) {
throw new LogicException(sprintf('The "%s" constraint requires the Symfony PropertyAccess component to use the "minPropertyPath" or "maxPropertyPath" option.', \get_class($this)));
}
}

parent::__construct($options);

if (null === $this->min && null === $this->max) {
throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint %s', __CLASS__), ['min', 'max']);
if (null === $this->min && null === $this->minPropertyPath && null === $this->max && null === $this->maxPropertyPath) {
throw new MissingOptionsException(sprintf('Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given for constraint %s', __CLASS__), ['min', 'max']);
}
}
}
75 changes: 65 additions & 10 deletions src/Symfony/Component/Validator/Constraints/RangeValidator.php
Expand Up @@ -11,15 +11,26 @@

namespace Symfony\Component\Validator\Constraints;

use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class RangeValidator extends ConstraintValidator
{
private $propertyAccessor;

public function __construct(PropertyAccessorInterface $propertyAccessor = null)
{
$this->propertyAccessor = $propertyAccessor;
}

/**
* {@inheritdoc}
*/
Expand All @@ -42,8 +53,8 @@ public function validate($value, Constraint $constraint)
return;
}

$min = $constraint->min;
$max = $constraint->max;
$min = $this->getLimit($constraint->minPropertyPath, $constraint->min, $constraint);
$max = $this->getLimit($constraint->maxPropertyPath, $constraint->max, $constraint);

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

if (null !== $constraint->max && $value > $max) {
$this->context->buildViolation($constraint->maxMessage)
if (null !== $max && $value > $max) {
$violationBuilder = $this->context->buildViolation($constraint->maxMessage)
->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
->setParameter('{{ limit }}', $this->formatValue($max, self::PRETTY_DATE))
->setCode(Range::TOO_HIGH_ERROR)
->addViolation();
->setCode(Range::TOO_HIGH_ERROR);

if (null !== $constraint->maxPropertyPath) {
$violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
}

if (null !== $constraint->minPropertyPath) {
$violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
}

$violationBuilder->addViolation();

return;
}

if (null !== $constraint->min && $value < $min) {
$this->context->buildViolation($constraint->minMessage)
if (null !== $min && $value < $min) {
$violationBuilder = $this->context->buildViolation($constraint->minMessage)
->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
->setParameter('{{ limit }}', $this->formatValue($min, self::PRETTY_DATE))
->setCode(Range::TOO_LOW_ERROR)
->addViolation();
->setCode(Range::TOO_LOW_ERROR);

if (null !== $constraint->maxPropertyPath) {
$violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
}

if (null !== $constraint->minPropertyPath) {
$violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
}

$violationBuilder->addViolation();
}
}

private function getLimit($propertyPath, $default, Constraint $constraint)
{
if (null === $propertyPath) {
return $default;
}

if (null === $object = $this->context->getObject()) {
return $default;
}

try {
return $this->getPropertyAccessor()->getValue($object, $propertyPath);
} catch (NoSuchPropertyException $e) {
throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s', $propertyPath, \get_class($constraint), $e->getMessage()), 0, $e);
}
}

private function getPropertyAccessor(): PropertyAccessorInterface
{
if (null === $this->propertyAccessor) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}

return $this->propertyAccessor;
}
}
51 changes: 51 additions & 0 deletions src/Symfony/Component/Validator/Tests/Constraints/RangeTest.php
@@ -0,0 +1,51 @@
<?php

namespace Symfony\Component\Validator\Tests\Constraints;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Range;

class RangeTest extends TestCase
{
/**
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
* @expectedExceptionMessage requires only one of the "min" or "minPropertyPath" options to be set, not both.
*/
public function testThrowsConstraintExceptionIfBothMinLimitAndPropertyPath()
{
new Range([
'min' => 'min',
'minPropertyPath' => 'minPropertyPath',
]);
}

/**
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
* @expectedExceptionMessage requires only one of the "max" or "maxPropertyPath" options to be set, not both.
*/
public function testThrowsConstraintExceptionIfBothMaxLimitAndPropertyPath()
{
new Range([
'max' => 'min',
'maxPropertyPath' => 'maxPropertyPath',
]);
}

/**
* @expectedException \Symfony\Component\Validator\Exception\MissingOptionsException
* @expectedExceptionMessage Either option "min", "minPropertyPath", "max" or "maxPropertyPath" must be given
*/
public function testThrowsConstraintExceptionIfNoLimitNorPropertyPath()
{
new Range([]);
}

/**
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
* @expectedExceptionMessage No default option is configured
*/
public function testThrowsNoDefaultOptionConfiguredException()
{
new Range('value');
}
}

0 comments on commit e1cea97

Please sign in to comment.