bug #28731 [Form] invalidate forms on transformation failures (xabbuh)

This PR was merged into the 2.8 branch.

Discussion
----------

[Form] invalidate forms on transformation failures

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #20916, #21242, #28584
| License       | MIT
| Doc PR        |

Commits
-------

385d9df29c invalidate forms on transformation failures
This commit is contained in:
Fabien Potencier 2018-11-12 08:14:53 +01:00
commit 893237d58f
5 changed files with 156 additions and 1 deletions

View File

@ -166,6 +166,11 @@
<tag name="form.type" alias="currency" />
</service>
<service id="form.type_extension.form.transformation_failure_handling" class="Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension">
<tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />
<argument type="service" id="translator" on-invalid="ignore" />
</service>
<!-- FormTypeHttpFoundationExtension -->
<service id="form.type_extension.form.http_foundation" class="Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension">
<argument type="service" id="form.type_extension.form.request_handler" />

View File

@ -16,8 +16,10 @@ use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Represents the main form extension, which loads the core functionality.
@ -28,11 +30,13 @@ class CoreExtension extends AbstractExtension
{
private $propertyAccessor;
private $choiceListFactory;
private $translator;
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
public function __construct(PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null, TranslatorInterface $translator = null)
{
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
$this->translator = $translator;
}
protected function loadTypes()
@ -71,4 +75,11 @@ class CoreExtension extends AbstractExtension
new Type\CurrencyType(),
);
}
protected function loadTypeExtensions()
{
return array(
new TransformationFailureExtension($this->translator),
);
}
}

View File

@ -0,0 +1,64 @@
<?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\Core\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class TransformationFailureListener implements EventSubscriberInterface
{
private $translator;
public function __construct(TranslatorInterface $translator = null)
{
$this->translator = $translator;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::POST_SUBMIT => array('convertTransformationFailureToFormError', -1024),
);
}
public function convertTransformationFailureToFormError(FormEvent $event)
{
$form = $event->getForm();
if (null === $form->getTransformationFailure() || !$form->isValid()) {
return;
}
foreach ($form as $child) {
if (!$child->isSynchronized()) {
return;
}
}
$clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : \gettype($form->getViewData());
$messageTemplate = 'The value {{ value }} is not valid.';
if (null !== $this->translator) {
$message = $this->translator->trans($messageTemplate, array('{{ value }}' => $clientDataAsString));
} else {
$message = strtr($messageTemplate, array('{{ value }}' => $clientDataAsString));
}
$form->addError(new FormError($message, $messageTemplate, array('{{ value }}' => $clientDataAsString), null, $form->getTransformationFailure()));
}
}

View File

@ -0,0 +1,42 @@
<?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\Core\Type;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\EventListener\TransformationFailureListener;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class TransformationFailureExtension extends AbstractTypeExtension
{
private $translator;
public function __construct(TranslatorInterface $translator = null)
{
$this->translator = $translator;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (!isset($options['invalid_message']) && !isset($options['invalid_message_parameters'])) {
$builder->addEventSubscriber(new TransformationFailureListener($this->translator));
}
}
public function getExtendedType()
{
return 'Symfony\Component\Form\Extension\Core\Type\FormType';
}
}

View File

@ -0,0 +1,33 @@
<?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\Tests\Extension\Core;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\Extension\Core\CoreExtension;
use Symfony\Component\Form\FormFactoryBuilder;
class CoreExtensionTest extends TestCase
{
public function testTransformationFailuresAreConvertedIntoFormErrors()
{
$formFactoryBuilder = new FormFactoryBuilder();
$formFactory = $formFactoryBuilder->addExtension(new CoreExtension())
->getFormFactory();
$form = $formFactory->createBuilder()
->add('foo', 'Symfony\Component\Form\Extension\Core\Type\DateType')
->getForm();
$form->submit('foo');
$this->assertFalse($form->isValid());
}
}