vendor/symfony/validator/Constraints/RangeValidator.php line 29

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Validator\Constraints;
  11. use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
  12. use Symfony\Component\PropertyAccess\PropertyAccess;
  13. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  14. use Symfony\Component\Validator\Constraint;
  15. use Symfony\Component\Validator\ConstraintValidator;
  16. use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
  17. use Symfony\Component\Validator\Exception\UnexpectedTypeException;
  18. /**
  19.  * @author Bernhard Schussek <bschussek@gmail.com>
  20.  */
  21. class RangeValidator extends ConstraintValidator
  22. {
  23.     private ?PropertyAccessorInterface $propertyAccessor;
  24.     public function __construct(PropertyAccessorInterface $propertyAccessor null)
  25.     {
  26.         $this->propertyAccessor $propertyAccessor;
  27.     }
  28.     public function validate(mixed $valueConstraint $constraint)
  29.     {
  30.         if (!$constraint instanceof Range) {
  31.             throw new UnexpectedTypeException($constraintRange::class);
  32.         }
  33.         if (null === $value) {
  34.             return;
  35.         }
  36.         $min $this->getLimit($constraint->minPropertyPath$constraint->min$constraint);
  37.         $max $this->getLimit($constraint->maxPropertyPath$constraint->max$constraint);
  38.         if (!is_numeric($value) && !$value instanceof \DateTimeInterface) {
  39.             if ($this->isParsableDatetimeString($min) && $this->isParsableDatetimeString($max)) {
  40.                 $this->context->buildViolation($constraint->invalidDateTimeMessage)
  41.                     ->setParameter('{{ value }}'$this->formatValue($valueself::PRETTY_DATE))
  42.                     ->setCode(Range::INVALID_CHARACTERS_ERROR)
  43.                     ->addViolation();
  44.             } else {
  45.                 $this->context->buildViolation($constraint->invalidMessage)
  46.                     ->setParameter('{{ value }}'$this->formatValue($valueself::PRETTY_DATE))
  47.                     ->setCode(Range::INVALID_CHARACTERS_ERROR)
  48.                     ->addViolation();
  49.             }
  50.             return;
  51.         }
  52.         // Convert strings to DateTimes if comparing another DateTime
  53.         // This allows to compare with any date/time value supported by
  54.         // the DateTime constructor:
  55.         // https://php.net/datetime.formats
  56.         if ($value instanceof \DateTimeInterface) {
  57.             $dateTimeClass null;
  58.             if (\is_string($min)) {
  59.                 $dateTimeClass $value instanceof \DateTimeImmutable \DateTimeImmutable::class : \DateTime::class;
  60.                 try {
  61.                     $min = new $dateTimeClass($min);
  62.                 } catch (\Exception) {
  63.                     throw new ConstraintDefinitionException(sprintf('The min value "%s" could not be converted to a "%s" instance in the "%s" constraint.'$min$dateTimeClassget_debug_type($constraint)));
  64.                 }
  65.             }
  66.             if (\is_string($max)) {
  67.                 $dateTimeClass $dateTimeClass ?: ($value instanceof \DateTimeImmutable \DateTimeImmutable::class : \DateTime::class);
  68.                 try {
  69.                     $max = new $dateTimeClass($max);
  70.                 } catch (\Exception) {
  71.                     throw new ConstraintDefinitionException(sprintf('The max value "%s" could not be converted to a "%s" instance in the "%s" constraint.'$max$dateTimeClassget_debug_type($constraint)));
  72.                 }
  73.             }
  74.         }
  75.         $hasLowerLimit null !== $min;
  76.         $hasUpperLimit null !== $max;
  77.         if ($hasLowerLimit && $hasUpperLimit && ($value $min || $value $max)) {
  78.             $message $constraint->notInRangeMessage;
  79.             $code Range::NOT_IN_RANGE_ERROR;
  80.             $violationBuilder $this->context->buildViolation($message)
  81.                 ->setParameter('{{ value }}'$this->formatValue($valueself::PRETTY_DATE))
  82.                 ->setParameter('{{ min }}'$this->formatValue($minself::PRETTY_DATE))
  83.                 ->setParameter('{{ max }}'$this->formatValue($maxself::PRETTY_DATE))
  84.                 ->setCode($code);
  85.             if (null !== $constraint->maxPropertyPath) {
  86.                 $violationBuilder->setParameter('{{ max_limit_path }}'$constraint->maxPropertyPath);
  87.             }
  88.             if (null !== $constraint->minPropertyPath) {
  89.                 $violationBuilder->setParameter('{{ min_limit_path }}'$constraint->minPropertyPath);
  90.             }
  91.             $violationBuilder->addViolation();
  92.             return;
  93.         }
  94.         if ($hasUpperLimit && $value $max) {
  95.             $violationBuilder $this->context->buildViolation($constraint->maxMessage)
  96.                 ->setParameter('{{ value }}'$this->formatValue($valueself::PRETTY_DATE))
  97.                 ->setParameter('{{ limit }}'$this->formatValue($maxself::PRETTY_DATE))
  98.                 ->setCode(Range::TOO_HIGH_ERROR);
  99.             if (null !== $constraint->maxPropertyPath) {
  100.                 $violationBuilder->setParameter('{{ max_limit_path }}'$constraint->maxPropertyPath);
  101.             }
  102.             if (null !== $constraint->minPropertyPath) {
  103.                 $violationBuilder->setParameter('{{ min_limit_path }}'$constraint->minPropertyPath);
  104.             }
  105.             $violationBuilder->addViolation();
  106.             return;
  107.         }
  108.         if ($hasLowerLimit && $value $min) {
  109.             $violationBuilder $this->context->buildViolation($constraint->minMessage)
  110.                 ->setParameter('{{ value }}'$this->formatValue($valueself::PRETTY_DATE))
  111.                 ->setParameter('{{ limit }}'$this->formatValue($minself::PRETTY_DATE))
  112.                 ->setCode(Range::TOO_LOW_ERROR);
  113.             if (null !== $constraint->maxPropertyPath) {
  114.                 $violationBuilder->setParameter('{{ max_limit_path }}'$constraint->maxPropertyPath);
  115.             }
  116.             if (null !== $constraint->minPropertyPath) {
  117.                 $violationBuilder->setParameter('{{ min_limit_path }}'$constraint->minPropertyPath);
  118.             }
  119.             $violationBuilder->addViolation();
  120.         }
  121.     }
  122.     private function getLimit(?string $propertyPathmixed $defaultConstraint $constraint): mixed
  123.     {
  124.         if (null === $propertyPath) {
  125.             return $default;
  126.         }
  127.         if (null === $object $this->context->getObject()) {
  128.             return $default;
  129.         }
  130.         try {
  131.             return $this->getPropertyAccessor()->getValue($object$propertyPath);
  132.         } catch (NoSuchPropertyException $e) {
  133.             throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: '$propertyPathget_debug_type($constraint)).$e->getMessage(), 0$e);
  134.         }
  135.     }
  136.     private function getPropertyAccessor(): PropertyAccessorInterface
  137.     {
  138.         return $this->propertyAccessor ??= PropertyAccess::createPropertyAccessor();
  139.     }
  140.     private function isParsableDatetimeString(mixed $boundary): bool
  141.     {
  142.         if (null === $boundary) {
  143.             return true;
  144.         }
  145.         if (!\is_string($boundary)) {
  146.             return false;
  147.         }
  148.         try {
  149.             new \DateTime($boundary);
  150.         } catch (\Exception) {
  151.             return false;
  152.         }
  153.         return true;
  154.     }
  155. }