From d11055cc1cfb6b4403eb1bbc2a61c01893abaf99 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Sun, 18 Dec 2016 14:08:54 +0100 Subject: [PATCH] [Form] TransformationFailedException: Support specifying message to display --- .../TransformationFailedException.php | 31 +++++++ .../Validator/Constraints/FormValidator.php | 12 ++- src/Symfony/Component/Form/Form.php | 8 +- .../Extension/Core/Type/FormTypeTest.php | 84 +++++++++++++++++++ .../Constraints/FormValidatorTest.php | 41 +++++++++ 5 files changed, 169 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Form/Exception/TransformationFailedException.php b/src/Symfony/Component/Form/Exception/TransformationFailedException.php index d32896e69c911..89eba088edbdb 100644 --- a/src/Symfony/Component/Form/Exception/TransformationFailedException.php +++ b/src/Symfony/Component/Form/Exception/TransformationFailedException.php @@ -18,4 +18,35 @@ */ class TransformationFailedException extends RuntimeException { + private $invalidMessage; + private $invalidMessageParameters; + + public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, string $invalidMessage = null, array $invalidMessageParameters = []) + { + parent::__construct($message, $code, $previous); + + $this->setInvalidMessage($invalidMessage, $invalidMessageParameters); + } + + /** + * Sets the message that will be shown to the user. + * + * @param string|null $invalidMessage The message or message key + * @param array $invalidMessageParameters Data to be passed into the translator + */ + public function setInvalidMessage(string $invalidMessage = null, array $invalidMessageParameters = []): void + { + $this->invalidMessage = $invalidMessage; + $this->invalidMessageParameters = $invalidMessageParameters; + } + + public function getInvalidMessage(): ?string + { + return $this->invalidMessage; + } + + public function getInvalidMessageParameters(): array + { + return $this->invalidMessageParameters; + } } diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php index 8a5cd14ff4dca..ca3cf80fde358 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php +++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -118,12 +118,18 @@ public function validate($form, Constraint $formConstraint) ? (string) $form->getViewData() : \gettype($form->getViewData()); + $failure = $form->getTransformationFailure(); + $this->context->setConstraint($formConstraint); - $this->context->buildViolation($config->getOption('invalid_message')) - ->setParameters(array_replace(['{{ value }}' => $clientDataAsString], $config->getOption('invalid_message_parameters'))) + $this->context->buildViolation($failure->getInvalidMessage() ?? $config->getOption('invalid_message')) + ->setParameters(array_replace( + ['{{ value }}' => $clientDataAsString], + $config->getOption('invalid_message_parameters'), + $failure->getInvalidMessageParameters() + )) ->setInvalidValue($form->getViewData()) ->setCode(Form::NOT_SYNCHRONIZED_ERROR) - ->setCause($form->getTransformationFailure()) + ->setCause($failure) ->addViolation(); } } diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 40b7f4d23db69..30bd02cc160ba 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -1070,7 +1070,7 @@ private function modelToNorm($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1094,7 +1094,7 @@ private function normToModel($value) $value = $transformers[$i]->reverseTransform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1125,7 +1125,7 @@ private function normToView($value) $value = $transformer->transform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; @@ -1153,7 +1153,7 @@ private function viewToNorm($value) $value = $transformers[$i]->reverseTransform($value); } } catch (TransformationFailedException $exception) { - throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception); + throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters()); } return $value; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php index 0c7f97dc1e744..2607f1d3760ab 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php @@ -12,10 +12,18 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; use Symfony\Component\Form\CallbackTransformer; +use Symfony\Component\Form\DataMapperInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Extension\Core\Type\CurrencyType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Validator\ValidatorExtension; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\Forms; use Symfony\Component\Form\Tests\Fixtures\Author; use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Validator\Validation; class FormTest_AuthorWithoutRefSetter { @@ -624,6 +632,32 @@ public function testNormDataIsPassedToView() $this->assertSame('baz', $view->vars['value']); } + public function testDataMapperTransformationFailedExceptionInvalidMessageIsUsed() + { + $money = new Money(20.5, 'EUR'); + $factory = Forms::createFormFactoryBuilder() + ->addExtensions([new ValidatorExtension(Validation::createValidator())]) + ->getFormFactory() + ; + + $builder = $factory + ->createBuilder(FormType::class, $money, ['invalid_message' => 'not the one to display']) + ->add('amount', TextType::class) + ->add('currency', CurrencyType::class) + ; + $builder->setDataMapper(new MoneyDataMapper()); + $form = $builder->getForm(); + + $form->submit(['amount' => 'invalid_amount', 'currency' => 'USD']); + + $this->assertFalse($form->isValid()); + $this->assertNull($form->getData()); + $this->assertCount(1, $form->getErrors()); + $this->assertSame('Expected numeric value', $form->getTransformationFailure()->getMessage()); + $error = $form->getErrors()[0]; + $this->assertSame('Money amount should be numeric. "invalid_amount" is invalid.', $error->getMessage()); + } + // https://github.com/symfony/symfony/issues/6862 public function testPassZeroLabelToView() { @@ -700,3 +734,53 @@ public function testPreferOwnHelpTranslationParameters() $this->assertEquals(['%parent_param%' => 'parent_value', '%override_param%' => 'child_value'], $view['child']->vars['help_translation_parameters']); } } + +class Money +{ + private $amount; + private $currency; + + public function __construct($amount, $currency) + { + $this->amount = $amount; + $this->currency = $currency; + } + + public function getAmount() + { + return $this->amount; + } + + public function getCurrency() + { + return $this->currency; + } +} + +class MoneyDataMapper implements DataMapperInterface +{ + public function mapDataToForms($data, $forms) + { + $forms = iterator_to_array($forms); + $forms['amount']->setData($data ? $data->getAmount() : 0); + $forms['currency']->setData($data ? $data->getCurrency() : 'EUR'); + } + + public function mapFormsToData($forms, &$data) + { + $forms = iterator_to_array($forms); + + $amount = $forms['amount']->getData(); + if (!is_numeric($amount)) { + $failure = new TransformationFailedException('Expected numeric value'); + $failure->setInvalidMessage('Money amount should be numeric. {{ amount }} is invalid.', ['{{ amount }}' => json_encode($amount)]); + + throw $failure; + } + + $data = new Money( + $forms['amount']->getData(), + $forms['currency']->getData() + ); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php index f23cc204f81a3..45fe0ebd8be7e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php @@ -343,6 +343,47 @@ function () { throw new TransformationFailedException(); } ->assertRaised(); } + public function testTransformationFailedExceptionInvalidMessageIsUsed() + { + $object = $this->createMock('\stdClass'); + + $form = $this + ->getBuilder('name', '\stdClass', [ + 'invalid_message' => 'invalid_message_key', + 'invalid_message_parameters' => ['{{ foo }}' => 'foo'], + ]) + ->setData($object) + ->addViewTransformer(new CallbackTransformer( + function ($data) { return $data; }, + function () { + $failure = new TransformationFailedException(); + $failure->setInvalidMessage('safe message to be used', ['{{ bar }}' => 'bar']); + + throw $failure; + } + )) + ->getForm() + ; + + $form->submit('value'); + + $this->expectNoValidate(); + + $this->validator->validate($form, new Form()); + + $this->buildViolation('safe message to be used') + ->setParameters([ + '{{ value }}' => 'value', + '{{ foo }}' => 'foo', + '{{ bar }}' => 'bar', + ]) + ->setInvalidValue('value') + ->setCode(Form::NOT_SYNCHRONIZED_ERROR) + ->setCause($form->getTransformationFailure()) + ->assertRaised() + ; + } + // https://github.com/symfony/symfony/issues/4359 public function testDontMarkInvalidIfAnyChildIsNotSynchronized() { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy