Skip to content

[Validator] Constraint validators now use the 2.5 API #11485

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ private function registerValidationConfiguration(array $config, ContainerBuilder
switch ($config['api']) {
case '2.4':
$api = Validation::API_VERSION_2_4;
$container->setParameter('validator.validator_factory.class', $container->getParameter('validator.legacy_validator_factory.class'));
break;
case '2.5':
$api = Validation::API_VERSION_2_5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<parameter key="validator.mapping.cache.apc.class">Symfony\Component\Validator\Mapping\Cache\ApcCache</parameter>
<parameter key="validator.mapping.cache.prefix" />
<parameter key="validator.validator_factory.class">Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory</parameter>
<parameter key="validator.legacy_validator_factory.class">Symfony\Bundle\FrameworkBundle\Validator\LegacyConstraintValidatorFactory</parameter>
<parameter key="validator.expression.class">Symfony\Component\Validator\Constraints\ExpressionValidator</parameter>
<parameter key="validator.email.class">Symfony\Component\Validator\Constraints\EmailValidator</parameter>
</parameters>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Validator;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

/**
* Like {@link ConstraintValidatorFactory}, but aware of services compatible
* with the 2.4 API.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kris Wallsmith <kris@symfony.com>
*
* @see ConstraintValidatorFactory
*/
class LegacyConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
{
const BASE_NAMESPACE = 'Symfony\\Component\\Validator\\Constraints';

protected $container;
protected $validators;

/**
* Constructor.
*
* @param ContainerInterface $container The service container
* @param array $validators An array of validators
*/
public function __construct(ContainerInterface $container, array $validators = array())
{
$this->container = $container;
$this->validators = $validators;
}

/**
* Returns the validator for the supplied constraint.
*
* @param Constraint $constraint A constraint
*
* @return ConstraintValidatorInterface A validator for the supplied constraint
*
* @throws UnexpectedTypeException When the validator is not an instance of ConstraintValidatorInterface
*/
public function getInstance(Constraint $constraint)
{
$name = $constraint->validatedBy();

if (!isset($this->validators[$name])) {
switch (get_class($constraint)) {
case self::BASE_NAMESPACE.'\\All':
$name = self::BASE_NAMESPACE.'\\LegacyAllValidator';
break;
case self::BASE_NAMESPACE.'\\Choice':
$name = self::BASE_NAMESPACE.'\\LegacyChoiceValidator';
break;
case self::BASE_NAMESPACE.'\\Collection':
$name = self::BASE_NAMESPACE.'\\LegacyCollectionValidator';
break;
case self::BASE_NAMESPACE.'\\Count':
$name = self::BASE_NAMESPACE.'\\LegacyCountValidator';
break;
case self::BASE_NAMESPACE.'\\Length':
$name = self::BASE_NAMESPACE.'\\LegacyLengthValidator';
break;
}

$this->validators[$name] = new $name();
} elseif (is_string($this->validators[$name])) {
$this->validators[$name] = $this->container->get($this->validators[$name]);
}

if (!$this->validators[$name] instanceof ConstraintValidatorInterface) {
throw new UnexpectedTypeException($this->validators[$name], 'Symfony\Component\Validator\ConstraintValidatorInterface');
}

return $this->validators[$name];
}
}
5 changes: 2 additions & 3 deletions src/Symfony/Component/Validator/Constraints/AllValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ public function validate($value, Constraint $constraint)

$context = $this->context;
$group = $context->getGroup();
$validator = $context->getValidator()->inContext($context);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is correct at the moment. But it looks kinda strange from an API point of view.
Would it not make more sense if $context->getValidator() automatically returns a ContextualValidatorInterface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because you don't always want to validate in the same context. For example, imagine validating some constraints and, depending on the result, adding one single combined violation. Then you need to validate in a different context.

This was in fact one of the motivations for the API change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #9888 for example


foreach ($value as $key => $element) {
foreach ($constraint->constraints as $constr) {
$context->validateValue($element, $constr, '['.$key.']', $group);
}
$validator->atPath('['.$key.']')->validate($element, $constraint->constraints, $group);
}
}
}
18 changes: 14 additions & 4 deletions src/Symfony/Component/Validator/Constraints/ChoiceValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,35 @@ public function validate($value, Constraint $constraint)
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();
}
}
}
32 changes: 19 additions & 13 deletions src/Symfony/Component/Validator/Constraints/CollectionValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,35 @@ public function validate($value, Constraint $constraint)
// to validate() instead.
$context = $this->context;
$group = $context->getGroup();
$validator = $context->getValidator()->inContext($context);

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);
// bug fix issue #2779
$existsInArray = is_array($value) && array_key_exists($field, $value);
$existsInArrayAccess = $value instanceof \ArrayAccess && $value->offsetExists($field);

if ($existsInArray || $existsInArrayAccess) {
if (count($fieldConstraint->constraints) > 0) {
$validator->atPath('['.$field.']')
->validate($value[$field], $fieldConstraint->constraints, $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)
->setInvalidValue(null)
->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)
->setInvalidValue($fieldValue)
->addViolation();
}
}
}
Expand Down
30 changes: 18 additions & 12 deletions src/Symfony/Component/Validator/Constraints/CountValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,34 @@ public function validate($value, Constraint $constraint)
$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)
->setInvalidValue($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)
->setInvalidValue($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)
->setInvalidValue($value)
->setPlural((int) $constraint->min)
->addViolation();
}
}
}
49 changes: 49 additions & 0 deletions src/Symfony/Component/Validator/Constraints/LegacyAllValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <bschussek@gmail.com>
*
* @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) {
$context->validateValue($element, $constraint->constraints, '['.$key.']', $group);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <fabien@symfony.com>
* @author Florian Eckerstorfer <florian@eckerstorfer.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @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), $value, (int) $constraint->min);

return;
}

if ($constraint->max !== null && $count > $constraint->max) {
$this->context->addViolation($constraint->maxMessage, array('{{ limit }}' => $constraint->max), $value, (int) $constraint->max);

return;
}
} elseif (!in_array($value, $choices, $constraint->strict)) {
$this->context->addViolation($constraint->message, array('{{ value }}' => $value));
}
}
}
Loading