diff --git a/src/Symfony/Component/Validator/Constraints/AllValidator.php b/src/Symfony/Component/Validator/Constraints/AllValidator.php index d45417ff48..cb952f93a7 100644 --- a/src/Symfony/Component/Validator/Constraints/AllValidator.php +++ b/src/Symfony/Component/Validator/Constraints/AllValidator.php @@ -41,10 +41,11 @@ class AllValidator extends ConstraintValidator $context = $this->context; $group = $context->getGroup(); + $validator = $context->getValidator()->inContext($context); foreach ($value as $key => $element) { foreach ($constraint->constraints as $constr) { - $context->validateValue($element, $constr, '['.$key.']', $group); + $validator->atPath('['.$key.']')->validate($element, $constr, $group); } } } diff --git a/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php b/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php index f0bb884a2f..4bfe206209 100644 --- a/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ChoiceValidator.php @@ -63,25 +63,35 @@ class ChoiceValidator extends ConstraintValidator if ($constraint->multiple) { foreach ($value as $_value) { if (!in_array($_value, $choices, $constraint->strict)) { - $this->context->addViolation($constraint->multipleMessage, array('{{ value }}' => $_value)); + $this->context->buildViolation($constraint->multipleMessage) + ->setParameter('{{ value }}', $_value) + ->addViolation(); } } $count = count($value); if ($constraint->min !== null && $count < $constraint->min) { - $this->context->addViolation($constraint->minMessage, array('{{ limit }}' => $constraint->min), null, (int) $constraint->min); + $this->context->buildViolation($constraint->minMessage) + ->setParameter('{{ limit }}', $constraint->min) + ->setPlural((int) $constraint->min) + ->addViolation(); return; } if ($constraint->max !== null && $count > $constraint->max) { - $this->context->addViolation($constraint->maxMessage, array('{{ limit }}' => $constraint->max), null, (int) $constraint->max); + $this->context->buildViolation($constraint->maxMessage) + ->setParameter('{{ limit }}', $constraint->max) + ->setPlural((int) $constraint->max) + ->addViolation(); return; } } elseif (!in_array($value, $choices, $constraint->strict)) { - $this->context->addViolation($constraint->message, array('{{ value }}' => $value)); + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); } } } diff --git a/src/Symfony/Component/Validator/Constraints/CollectionValidator.php b/src/Symfony/Component/Validator/Constraints/CollectionValidator.php index 56a6cbca23..bf8e6d0b91 100644 --- a/src/Symfony/Component/Validator/Constraints/CollectionValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CollectionValidator.php @@ -50,6 +50,7 @@ class CollectionValidator extends ConstraintValidator // to validate() instead. $context = $this->context; $group = $context->getGroup(); + $validator = $context->getValidator()->inContext($context); foreach ($constraint->fields as $field => $fieldConstraint) { if ( @@ -58,21 +59,23 @@ class CollectionValidator extends ConstraintValidator ($value instanceof \ArrayAccess && $value->offsetExists($field)) ) { foreach ($fieldConstraint->constraints as $constr) { - $context->validateValue($value[$field], $constr, '['.$field.']', $group); + $validator->atPath('['.$field.']')->validate($value[$field], $constr, $group); } } elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) { - $context->addViolationAt('['.$field.']', $constraint->missingFieldsMessage, array( - '{{ field }}' => $field - ), null); + $context->buildViolation($constraint->missingFieldsMessage) + ->atPath('['.$field.']') + ->setParameter('{{ field }}', $field) + ->addViolation(); } } if (!$constraint->allowExtraFields) { foreach ($value as $field => $fieldValue) { if (!isset($constraint->fields[$field])) { - $context->addViolationAt('['.$field.']', $constraint->extraFieldsMessage, array( - '{{ field }}' => $field - ), $fieldValue); + $context->buildViolation($constraint->extraFieldsMessage) + ->atPath('['.$field.']') + ->setParameter('{{ field }}', $field) + ->addViolation(); } } } diff --git a/src/Symfony/Component/Validator/Constraints/CountValidator.php b/src/Symfony/Component/Validator/Constraints/CountValidator.php index d7d5025b1c..160ba7ac0b 100644 --- a/src/Symfony/Component/Validator/Constraints/CountValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CountValidator.php @@ -36,28 +36,34 @@ class CountValidator extends ConstraintValidator $count = count($value); if ($constraint->min == $constraint->max && $count != $constraint->min) { - $this->context->addViolation($constraint->exactMessage, array( - '{{ count }}' => $count, - '{{ limit }}' => $constraint->min, - ), $value, (int) $constraint->min); + $this->context->buildViolation($constraint->exactMessage) + ->setParameter('{{ count }}', $count) + ->setParameter('{{ limit }}', $constraint->min) + ->setValue($value) + ->setPlural((int) $constraint->min) + ->addViolation(); return; } if (null !== $constraint->max && $count > $constraint->max) { - $this->context->addViolation($constraint->maxMessage, array( - '{{ count }}' => $count, - '{{ limit }}' => $constraint->max, - ), $value, (int) $constraint->max); + $this->context->buildViolation($constraint->maxMessage) + ->setParameter('{{ count }}', $count) + ->setParameter('{{ limit }}', $constraint->max) + ->setValue($value) + ->setPlural((int) $constraint->max) + ->addViolation(); return; } if (null !== $constraint->min && $count < $constraint->min) { - $this->context->addViolation($constraint->minMessage, array( - '{{ count }}' => $count, - '{{ limit }}' => $constraint->min, - ), $value, (int) $constraint->min); + $this->context->buildViolation($constraint->minMessage) + ->setParameter('{{ count }}', $count) + ->setParameter('{{ limit }}', $constraint->min) + ->setValue($value) + ->setPlural((int) $constraint->min) + ->addViolation(); } } } diff --git a/src/Symfony/Component/Validator/Constraints/LegacyAllValidator.php b/src/Symfony/Component/Validator/Constraints/LegacyAllValidator.php new file mode 100644 index 0000000000..47e037a9b4 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/LegacyAllValidator.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5.3, to be removed in 3.0. + */ +class LegacyAllValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof All) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\All'); + } + + if (null === $value) { + return; + } + + if (!is_array($value) && !$value instanceof \Traversable) { + throw new UnexpectedTypeException($value, 'array or Traversable'); + } + + $context = $this->context; + $group = $context->getGroup(); + + foreach ($value as $key => $element) { + foreach ($constraint->constraints as $constr) { + $context->validateValue($element, $constr, '['.$key.']', $group); + } + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/LegacyChoiceValidator.php b/src/Symfony/Component/Validator/Constraints/LegacyChoiceValidator.php new file mode 100644 index 0000000000..26912152fc --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/LegacyChoiceValidator.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * ChoiceValidator validates that the value is one of the expected values. + * + * @author Fabien Potencier + * @author Florian Eckerstorfer + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5.3, to be removed in 3.0. + */ +class LegacyChoiceValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Choice) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Choice'); + } + + if (!$constraint->choices && !$constraint->callback) { + throw new ConstraintDefinitionException('Either "choices" or "callback" must be specified on constraint Choice'); + } + + if (null === $value) { + return; + } + + if ($constraint->multiple && !is_array($value)) { + throw new UnexpectedTypeException($value, 'array'); + } + + if ($constraint->callback) { + if (is_callable(array($this->context->getClassName(), $constraint->callback))) { + $choices = call_user_func(array($this->context->getClassName(), $constraint->callback)); + } elseif (is_callable($constraint->callback)) { + $choices = call_user_func($constraint->callback); + } else { + throw new ConstraintDefinitionException('The Choice constraint expects a valid callback'); + } + } else { + $choices = $constraint->choices; + } + + if ($constraint->multiple) { + foreach ($value as $_value) { + if (!in_array($_value, $choices, $constraint->strict)) { + $this->context->addViolation($constraint->multipleMessage, array('{{ value }}' => $_value)); + } + } + + $count = count($value); + + if ($constraint->min !== null && $count < $constraint->min) { + $this->context->addViolation($constraint->minMessage, array('{{ limit }}' => $constraint->min), null, (int) $constraint->min); + + return; + } + + if ($constraint->max !== null && $count > $constraint->max) { + $this->context->addViolation($constraint->maxMessage, array('{{ limit }}' => $constraint->max), null, (int) $constraint->max); + + return; + } + } elseif (!in_array($value, $choices, $constraint->strict)) { + $this->context->addViolation($constraint->message, array('{{ value }}' => $value)); + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/LegacyCollectionValidator.php b/src/Symfony/Component/Validator/Constraints/LegacyCollectionValidator.php new file mode 100644 index 0000000000..6618079fd1 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/LegacyCollectionValidator.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5.3, to be removed in 3.0. + */ +class LegacyCollectionValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Collection) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Collection'); + } + + if (null === $value) { + return; + } + + if (!is_array($value) && !($value instanceof \Traversable && $value instanceof \ArrayAccess)) { + throw new UnexpectedTypeException($value, 'array or Traversable and ArrayAccess'); + } + + // We need to keep the initialized context when CollectionValidator + // calls itself recursively (Collection constraints can be nested). + // Since the context of the validator is overwritten when initialize() + // is called for the nested constraint, the outer validator is + // acting on the wrong context when the nested validation terminates. + // + // A better solution - which should be approached in Symfony 3.0 - is to + // remove the initialize() method and pass the context as last argument + // to validate() instead. + $context = $this->context; + $group = $context->getGroup(); + + foreach ($constraint->fields as $field => $fieldConstraint) { + if ( + // bug fix issue #2779 + (is_array($value) && array_key_exists($field, $value)) || + ($value instanceof \ArrayAccess && $value->offsetExists($field)) + ) { + foreach ($fieldConstraint->constraints as $constr) { + $context->validateValue($value[$field], $constr, '['.$field.']', $group); + } + } elseif (!$fieldConstraint instanceof Optional && !$constraint->allowMissingFields) { + $context->addViolationAt('['.$field.']', $constraint->missingFieldsMessage, array( + '{{ field }}' => $field + ), null); + } + } + + if (!$constraint->allowExtraFields) { + foreach ($value as $field => $fieldValue) { + if (!isset($constraint->fields[$field])) { + $context->addViolationAt('['.$field.']', $constraint->extraFieldsMessage, array( + '{{ field }}' => $field + ), $fieldValue); + } + } + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/LegacyCountValidator.php b/src/Symfony/Component/Validator/Constraints/LegacyCountValidator.php new file mode 100644 index 0000000000..5e34fb57dd --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/LegacyCountValidator.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5.3, to be removed in 3.0. + */ +class LegacyCountValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (null === $value) { + return; + } + + if (!is_array($value) && !$value instanceof \Countable) { + throw new UnexpectedTypeException($value, 'array or \Countable'); + } + + $count = count($value); + + if ($constraint->min == $constraint->max && $count != $constraint->min) { + $this->context->addViolation($constraint->exactMessage, array( + '{{ count }}' => $count, + '{{ limit }}' => $constraint->min, + ), $value, (int) $constraint->min); + + return; + } + + if (null !== $constraint->max && $count > $constraint->max) { + $this->context->addViolation($constraint->maxMessage, array( + '{{ count }}' => $count, + '{{ limit }}' => $constraint->max, + ), $value, (int) $constraint->max); + + return; + } + + if (null !== $constraint->min && $count < $constraint->min) { + $this->context->addViolation($constraint->minMessage, array( + '{{ count }}' => $count, + '{{ limit }}' => $constraint->min, + ), $value, (int) $constraint->min); + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/LegacyLengthValidator.php b/src/Symfony/Component/Validator/Constraints/LegacyLengthValidator.php new file mode 100644 index 0000000000..2d2b8f8d34 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/LegacyLengthValidator.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; + +/** + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5.3, to be removed in 3.0. + */ +class LegacyLengthValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof Length) { + throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Length'); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $stringValue = (string) $value; + + if (function_exists('grapheme_strlen') && 'UTF-8' === $constraint->charset) { + $length = grapheme_strlen($stringValue); + } elseif (function_exists('mb_strlen')) { + $length = mb_strlen($stringValue, $constraint->charset); + } else { + $length = strlen($stringValue); + } + + if ($constraint->min == $constraint->max && $length != $constraint->min) { + $this->context->addViolation($constraint->exactMessage, array( + '{{ value }}' => $stringValue, + '{{ limit }}' => $constraint->min, + ), $value, (int) $constraint->min); + + return; + } + + if (null !== $constraint->max && $length > $constraint->max) { + $this->context->addViolation($constraint->maxMessage, array( + '{{ value }}' => $stringValue, + '{{ limit }}' => $constraint->max, + ), $value, (int) $constraint->max); + + return; + } + + if (null !== $constraint->min && $length < $constraint->min) { + $this->context->addViolation($constraint->minMessage, array( + '{{ value }}' => $stringValue, + '{{ limit }}' => $constraint->min, + ), $value, (int) $constraint->min); + } + } +} diff --git a/src/Symfony/Component/Validator/Constraints/LengthValidator.php b/src/Symfony/Component/Validator/Constraints/LengthValidator.php index db9e0ab5ab..2d94ae4d8d 100644 --- a/src/Symfony/Component/Validator/Constraints/LengthValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LengthValidator.php @@ -48,28 +48,34 @@ class LengthValidator extends ConstraintValidator } if ($constraint->min == $constraint->max && $length != $constraint->min) { - $this->context->addViolation($constraint->exactMessage, array( - '{{ value }}' => $stringValue, - '{{ limit }}' => $constraint->min, - ), $value, (int) $constraint->min); + $this->context->buildViolation($constraint->exactMessage) + ->setParameter('{{ value }}', $stringValue) + ->setParameter('{{ limit }}', $constraint->min) + ->setValue($value) + ->setPlural((int) $constraint->min) + ->addViolation(); return; } if (null !== $constraint->max && $length > $constraint->max) { - $this->context->addViolation($constraint->maxMessage, array( - '{{ value }}' => $stringValue, - '{{ limit }}' => $constraint->max, - ), $value, (int) $constraint->max); + $this->context->buildViolation($constraint->maxMessage) + ->setParameter('{{ value }}', $stringValue) + ->setParameter('{{ limit }}', $constraint->max) + ->setValue($value) + ->setPlural((int) $constraint->max) + ->addViolation(); return; } if (null !== $constraint->min && $length < $constraint->min) { - $this->context->addViolation($constraint->minMessage, array( - '{{ value }}' => $stringValue, - '{{ limit }}' => $constraint->min, - ), $value, (int) $constraint->min); + $this->context->buildViolation($constraint->minMessage) + ->setParameter('{{ value }}', $stringValue) + ->setParameter('{{ limit }}', $constraint->min) + ->setValue($value) + ->setPlural((int) $constraint->min) + ->addViolation(); } } } diff --git a/src/Symfony/Component/Validator/LegacyConstraintValidatorFactory.php b/src/Symfony/Component/Validator/LegacyConstraintValidatorFactory.php new file mode 100644 index 0000000000..d85f3bd01d --- /dev/null +++ b/src/Symfony/Component/Validator/LegacyConstraintValidatorFactory.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator; + +use Symfony\Component\Validator\Constraints\ExpressionValidator; + +/** + * Backwards compatible implementation of the {@link ConstraintValidatorFactoryInterface}. + * + * This class uses legacy constraint validators where possible to ensure + * compatibility with the 2.4 validator API. + * + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5.3, to be removed in 3.0. + */ +class LegacyConstraintValidatorFactory implements ConstraintValidatorFactoryInterface +{ + protected $validators = array(); + + private $propertyAccessor; + + public function __construct($propertyAccessor = null) + { + $this->propertyAccessor = $propertyAccessor; + } + + /** + * {@inheritdoc} + */ + public function getInstance(Constraint $constraint) + { + switch (get_class($constraint)) { + case __NAMESPACE__.'\\Constraints\\All': + $className = __NAMESPACE__.'\\Constraints\\LegacyAllValidator'; + break; + case __NAMESPACE__.'\\Constraints\\Choice': + $className = __NAMESPACE__.'\\Constraints\\LegacyChoiceValidator'; + break; + case __NAMESPACE__.'\\Constraints\\Collection': + $className = __NAMESPACE__.'\\Constraints\\LegacyCollectionValidator'; + break; + case __NAMESPACE__.'\\Constraints\\Count': + $className = __NAMESPACE__.'\\Constraints\\LegacyCountValidator'; + break; + case __NAMESPACE__.'\\Constraints\\Length': + $className = __NAMESPACE__.'\\Constraints\\LegacyLengthValidator'; + break; + default: + $className = $constraint->validatedBy(); + break; + } + + if (!isset($this->validators[$className])) { + $this->validators[$className] = 'validator.expression' === $className + ? new ExpressionValidator($this->propertyAccessor) + : new $className(); + } + + return $this->validators[$className]; + } +}