AbstractComparisonValidator.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. * Provides a base class for the validation of property comparisons.
  20. *
  21. * @author Daniel Holmes <daniel@danielholmes.org>
  22. * @author Bernhard Schussek <bschussek@gmail.com>
  23. */
  24. abstract class AbstractComparisonValidator extends ConstraintValidator
  25. {
  26. private $propertyAccessor;
  27. public function __construct(PropertyAccessorInterface $propertyAccessor = null)
  28. {
  29. $this->propertyAccessor = $propertyAccessor;
  30. }
  31. /**
  32. * {@inheritdoc}
  33. */
  34. public function validate($value, Constraint $constraint)
  35. {
  36. if (!$constraint instanceof AbstractComparison) {
  37. throw new UnexpectedTypeException($constraint, AbstractComparison::class);
  38. }
  39. if (null === $value) {
  40. return;
  41. }
  42. if ($path = $constraint->propertyPath) {
  43. if (null === $object = $this->context->getObject()) {
  44. return;
  45. }
  46. try {
  47. $comparedValue = $this->getPropertyAccessor()->getValue($object, $path);
  48. } catch (NoSuchPropertyException $e) {
  49. throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e);
  50. }
  51. } else {
  52. $comparedValue = $constraint->value;
  53. }
  54. // Convert strings to DateTimes if comparing another DateTime
  55. // This allows to compare with any date/time value supported by
  56. // the DateTime constructor:
  57. // https://php.net/datetime.formats
  58. if (\is_string($comparedValue) && $value instanceof \DateTimeInterface) {
  59. // If $value is immutable, convert the compared value to a DateTimeImmutable too, otherwise use DateTime
  60. $dateTimeClass = $value instanceof \DateTimeImmutable ? \DateTimeImmutable::class : \DateTime::class;
  61. try {
  62. $comparedValue = new $dateTimeClass($comparedValue);
  63. } catch (\Exception $e) {
  64. throw new ConstraintDefinitionException(sprintf('The compared value "%s" could not be converted to a "%s" instance in the "%s" constraint.', $comparedValue, $dateTimeClass, get_debug_type($constraint)));
  65. }
  66. }
  67. if (!$this->compareValues($value, $comparedValue)) {
  68. $violationBuilder = $this->context->buildViolation($constraint->message)
  69. ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING | self::PRETTY_DATE))
  70. ->setParameter('{{ compared_value }}', $this->formatValue($comparedValue, self::OBJECT_TO_STRING | self::PRETTY_DATE))
  71. ->setParameter('{{ compared_value_type }}', $this->formatTypeOf($comparedValue))
  72. ->setCode($this->getErrorCode());
  73. if (null !== $path) {
  74. $violationBuilder->setParameter('{{ compared_value_path }}', $path);
  75. }
  76. $violationBuilder->addViolation();
  77. }
  78. }
  79. private function getPropertyAccessor(): PropertyAccessorInterface
  80. {
  81. if (null === $this->propertyAccessor) {
  82. $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
  83. }
  84. return $this->propertyAccessor;
  85. }
  86. /**
  87. * Compares the two given values to find if their relationship is valid.
  88. *
  89. * @param mixed $value1 The first value to compare
  90. * @param mixed $value2 The second value to compare
  91. *
  92. * @return bool true if the relationship is valid, false otherwise
  93. */
  94. abstract protected function compareValues($value1, $value2);
  95. /**
  96. * Returns the error code used if the comparison fails.
  97. *
  98. * @return string|null The error code or `null` if no code should be set
  99. */
  100. protected function getErrorCode()
  101. {
  102. return null;
  103. }
  104. }