Skip to content

Commit

Permalink
[Form] Fixed FormValidator compatibility with the non-BC 2.5 Validati…
Browse files Browse the repository at this point in the history
…on API
  • Loading branch information
webmozart committed Aug 5, 2014
1 parent 98c0621 commit 6ac130e
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 205 deletions.
Expand Up @@ -29,6 +29,7 @@
class LegacyConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
{
const BASE_NAMESPACE = 'Symfony\\Component\\Validator\\Constraints';
const FORM_BASE_NAMESPACE = 'Symfony\\Component\\Form\\Extension\\Validator\\Constraints';

protected $container;
protected $validators;
Expand Down Expand Up @@ -75,6 +76,9 @@ public function getInstance(Constraint $constraint)
case self::BASE_NAMESPACE.'\\Length':
$name = self::BASE_NAMESPACE.'\\LegacyLengthValidator';
break;
case self::FORM_BASE_NAMESPACE.'\\Form':
$name = self::FORM_BASE_NAMESPACE.'\\LegacyFormValidator';
break;
}

$this->validators[$name] = new $name();
Expand Down
Expand Up @@ -53,6 +53,7 @@ public function validate($form, Constraint $constraint)

/* @var FormInterface $form */
$config = $form->getConfig();
$validator = $this->context->getValidator()->inContext($this->context);

if ($form->isSynchronized()) {
// Validate the form data only if transformation succeeded
Expand All @@ -61,7 +62,7 @@ public function validate($form, Constraint $constraint)
// Validate the data against its own constraints
if (self::allowDataWalking($form)) {
foreach ($groups as $group) {
$this->context->validate($form->getData(), 'data', $group, true);
$validator->atPath('data')->validate($form->getData(), null, $group);
}
}

Expand All @@ -71,7 +72,7 @@ public function validate($form, Constraint $constraint)
foreach ($constraints as $constraint) {
foreach ($groups as $group) {
if (in_array($group, $constraint->groups)) {
$this->context->validateValue($form->getData(), $constraint, 'data', $group);
$validator->atPath('data')->validate($form->getData(), $constraint, $group);

// Prevent duplicate validation
continue 2;
Expand Down Expand Up @@ -100,23 +101,20 @@ public function validate($form, Constraint $constraint)
? (string) $form->getViewData()
: gettype($form->getViewData());

$this->context->addViolation(
$config->getOption('invalid_message'),
array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')),
$form->getViewData(),
null,
Form::ERR_INVALID
);
$this->context->buildViolation($config->getOption('invalid_message'))
->setParameters(array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')))
->setInvalidValue($form->getViewData())
->setCode(Form::ERR_INVALID)
->addViolation();
}
}

// Mark the form with an error if it contains extra fields
if (count($form->getExtraData()) > 0) {
$this->context->addViolation(
$config->getOption('extra_fields_message'),
array('{{ extra_fields }}' => implode('", "', array_keys($form->getExtraData()))),
$form->getExtraData()
);
$this->context->buildViolation($config->getOption('extra_fields_message'))
->setParameter('{{ extra_fields }}', implode('", "', array_keys($form->getExtraData())))
->setInvalidValue($form->getExtraData())
->addViolation();
}

// Mark the form with an error if the uploaded size was too large
Expand All @@ -126,11 +124,10 @@ public function validate($form, Constraint $constraint)
$max = $this->serverParams->getPostMaxSize();

if (!empty($max) && $length > $max) {
$this->context->addViolation(
$config->getOption('post_max_size_message'),
array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()),
$length
);
$this->context->buildViolation($config->getOption('post_max_size_message'))
->setParameter('{{ max }}', $this->serverParams->getNormalizedIniPostMaxSize())
->setInvalidValue($length)
->addViolation();
}
}
}
Expand Down
@@ -0,0 +1,223 @@
<?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\Form\Extension\Validator\Constraints;

use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Extension\Validator\Util\ServerParams;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LegacyFormValidator extends ConstraintValidator
{
/**
* @var ServerParams
*/
private $serverParams;

/**
* Creates a validator with the given server parameters.
*
* @param ServerParams $params The server parameters. Default
* parameters are created if null.
*/
public function __construct(ServerParams $params = null)
{
$this->serverParams = $params ?: new ServerParams();
}

/**
* {@inheritdoc}
*/
public function validate($form, Constraint $constraint)
{
if (!$constraint instanceof Form) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Form');
}

if (!$form instanceof FormInterface) {
return;
}

/* @var FormInterface $form */
$config = $form->getConfig();

if ($form->isSynchronized()) {
// Validate the form data only if transformation succeeded
$groups = self::getValidationGroups($form);

// Validate the data against its own constraints
if (self::allowDataWalking($form)) {
foreach ($groups as $group) {
$this->context->validate($form->getData(), 'data', $group, true);
}
}

// Validate the data against the constraints defined
// in the form
$constraints = $config->getOption('constraints');
foreach ($constraints as $constraint) {
foreach ($groups as $group) {
if (in_array($group, $constraint->groups)) {
$this->context->validateValue($form->getData(), $constraint, 'data', $group);

// Prevent duplicate validation
continue 2;
}
}
}
} else {
$childrenSynchronized = true;

foreach ($form as $child) {
if (!$child->isSynchronized()) {
$childrenSynchronized = false;
break;
}
}

// Mark the form with an error if it is not synchronized BUT all
// of its children are synchronized. If any child is not
// synchronized, an error is displayed there already and showing
// a second error in its parent form is pointless, or worse, may
// lead to duplicate errors if error bubbling is enabled on the
// child.
// See also https://github.com/symfony/symfony/issues/4359
if ($childrenSynchronized) {
$clientDataAsString = is_scalar($form->getViewData())
? (string) $form->getViewData()
: gettype($form->getViewData());

$this->context->addViolation(
$config->getOption('invalid_message'),
array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')),
$form->getViewData(),
null,
Form::ERR_INVALID
);
}
}

// Mark the form with an error if it contains extra fields
if (count($form->getExtraData()) > 0) {
$this->context->addViolation(
$config->getOption('extra_fields_message'),
array('{{ extra_fields }}' => implode('", "', array_keys($form->getExtraData()))),
$form->getExtraData()
);
}

// Mark the form with an error if the uploaded size was too large
$length = $this->serverParams->getContentLength();

if ($form->isRoot() && null !== $length) {
$max = $this->serverParams->getPostMaxSize();

if (!empty($max) && $length > $max) {
$this->context->addViolation(
$config->getOption('post_max_size_message'),
array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()),
$length
);
}
}
}

/**
* Returns whether the data of a form may be walked.
*
* @param FormInterface $form The form to test.
*
* @return bool Whether the graph walker may walk the data.
*/
private static function allowDataWalking(FormInterface $form)
{
$data = $form->getData();

// Scalar values cannot have mapped constraints
if (!is_object($data) && !is_array($data)) {
return false;
}

// Root forms are always validated
if ($form->isRoot()) {
return true;
}

// Non-root forms are validated if validation cascading
// is enabled in all ancestor forms
while (null !== ($form = $form->getParent())) {
if (!$form->getConfig()->getOption('cascade_validation')) {
return false;
}
}

return true;
}

/**
* Returns the validation groups of the given form.
*
* @param FormInterface $form The form.
*
* @return array The validation groups.
*/
private static function getValidationGroups(FormInterface $form)
{
// Determine the clicked button of the complete form tree
$clickedButton = null;

if (method_exists($form, 'getClickedButton')) {
$clickedButton = $form->getClickedButton();
}

if (null !== $clickedButton) {
$groups = $clickedButton->getConfig()->getOption('validation_groups');

if (null !== $groups) {
return self::resolveValidationGroups($groups, $form);
}
}

do {
$groups = $form->getConfig()->getOption('validation_groups');

if (null !== $groups) {
return self::resolveValidationGroups($groups, $form);
}

$form = $form->getParent();
} while (null !== $form);

return array(Constraint::DEFAULT_GROUP);
}

/**
* Post-processes the validation groups option for a given form.
*
* @param array|callable $groups The validation groups.
* @param FormInterface $form The validated form.
*
* @return array The validation groups.
*/
private static function resolveValidationGroups($groups, FormInterface $form)
{
if (!is_string($groups) && is_callable($groups)) {
$groups = call_user_func($groups, $form);
}

return (array) $groups;
}
}

0 comments on commit 6ac130e

Please sign in to comment.