8000 [Form] TransformationFailedException: Support specifying message to d… · symfony/symfony@d11055c · GitHub
[go: up one dir, main page]

Skip to content

Commit d11055c

Browse files
committed
[Form] TransformationFailedException: Support specifying message to display
1 parent 76260e7 commit d11055c

File tree

5 files changed

+169
-7
lines changed

5 files changed

+169
-7
lines changed

src/Symfony/Component/Form/Exception/TransformationFailedException.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,35 @@
1818
*/
1919
class TransformationFailedException extends RuntimeException
2020
{
21+
private $invalidMessage;
22+
private $invalidMessageParameters;
23+
24+
public function __construct(string $message = '', int $code = 0, \Throwable $previous = null, string $invalidMessage = null, array $invalidMessageParameters = [])
25+
{
26+
parent::__construct($message, $code, $previous);
27+
28+
$this->setInvalidMessage($invalidMessage, $invalidMessageParameters);
29+
}
30+
31+
/**
32+
* Sets the message that will be shown to the user.
33+
*
34+
* @param string|null $invalidMessage The message or message key
35+
* @param array $invalidMessageParameters Data to be passed into the translator
36+
*/
37+
public function setInvalidMessage(string $invalidMessage = null, array $invalidMessageParameters = []): void
38+
{
39+
$this->invalidMessage = $invalidMessage;
40+
$this->invalidMessageParameters = $invalidMessageParameters;
41+
}
42+
43+
public function getInvalidMessage(): ?string
44+
{
45+
return $this->invalidMessage;
46+
}
47+
48+
public function getInvalidMessageParameters(): array
49+
{
50+
return $this->invalidMessageParameters;
51+
}
2152
}

src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,18 @@ public function validate($form, Constraint $formConstraint)
118118
? (string) $form->getViewData()
119119
: \gettype($form->getViewData());
120120

121+
$failure = $form->getTransformationFailure();
122+
121123
$this->context->setConstraint($formConstraint);
122-
$this->context->buildViolation($config->getOption('invalid_message'))
123-
->setParameters(array_replace(['{{ value }}' => $clientDataAsString], $config->getOption('invalid_message_parameters')))
124+
$this->context->buildViolation($failure->getInvalidMessage() ?? $config->getOption('invalid_message'))
125+
->setParameters(array_replace(
126+
['{{ value }}' => $clientDataAsString],
127+
$config->getOption('invalid_message_parameters'),
128+
$failure->getInvalidMessageParameters()
129+
))
124130
->setInvalidValue($form->getViewData())
125131
->setCode(Form::NOT_SYNCHRONIZED_ERROR)
126-
->setCause($form->getTransformationFailure())
132+
->setCause($failure)
127133
->addViolation();
128134
}
129135
}

src/Symfony/Component/Form/Form.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ private function modelToNorm($value)
10701070
$value = $transformer->transform($value);
10711071
}
10721072
} catch (TransformationFailedException $exception) {
1073-
throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception);
1073+
throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
10741074
}
10751075

10761076
return $value;
@@ -1094,7 +1094,7 @@ private function normToModel($value)
10941094
$value = $transformers[$i]->reverseTransform($value);
10951095
}
10961096
} catch (TransformationFailedException $exception) {
1097-
throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception);
1097+
throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
10981098
}
10991099

11001100
return $value;
@@ -1125,7 +1125,7 @@ private function normToView($value)
11251125
$value = $transformer->transform($value);
11261126
}
11271127
} catch (TransformationFailedException $exception) {
1128-
throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception);
1128+
throw new TransformationFailedException('Unable to transform value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
11291129
}
11301130

11311131
return $value;
@@ -1153,7 +1153,7 @@ private function viewToNorm($value)
11531153
$value = $transformers[$i]->reverseTransform($value);
11541154
}
11551155
} catch (TransformationFailedException $exception) {
1156-
throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception);
1156+
throw new TransformationFailedException('Unable to reverse value for property path "'.$this->getPropertyPath().'": '.$exception->getMessage(), $exception->getCode(), $exception, $exception->getInvalidMessage(), $exception->getInvalidMessageParameters());
11571157
}
11581158

11591159
return $value;

src/Symfony/Component/Form/Tests/Extension/Core/Type/FormTypeTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@
1212
namespace Symfony\Component\Form\Tests\Extension\Core\Type;
1313

1414
use Symfony\Component\Form\CallbackTransformer;
15+
use Symfony\Component\Form\DataMapperInterface;
16+
use Symfony\Component\Form\Exception\TransformationFailedException;
17+
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
18+
use Symfony\Component\Form\Extension\Core\Type\FormType;
19+
use Symfony\Component\Form\Extension\Core\Type\TextType;
20+
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
1521
use Symfony\Component\Form\FormError;
22+
use Symfony\Component\Form\Forms;
1623
use Symfony\Component\Form\Tests\Fixtures\Author;
1724
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
1825
use Symfony\Component\PropertyAccess\PropertyPath;
26+
use Symfony\Component\Validator\Validation;
1927

2028
class FormTest_AuthorWithoutRefSetter
2129
{
@@ -624,6 +632,32 @@ public function testNormDataIsPassedToView()
624632
$this->assertSame('baz', $view->vars['value']);
625633
}
626634

635+
public function testDataMapperTransformationFailedExceptionInvalidMessageIsUsed()
636+
{
637+
$money = new Money(20.5, 'EUR');
638+
$factory = Forms::createFormFactoryBuilder()
639+
->addExtensions([new ValidatorExtension(Validation::createValidator())])
640+
->getFormFactory()
641+
;
642+
643+
$builder = $factory
644+
->createBuilder(FormType::class, $money, ['invalid_message' => 'not the one to display'])
645+
->add('amount', TextType::class)
646+
->add('currency', CurrencyType::class)
647+
;
648+
$builder->setDataMapper(new MoneyDataMapper());
649+
$form = $builder->getForm();
650+
651+
$form->submit(['amount' => 'invalid_amount', 'currency' => 'USD']);
652+
653+
$this->assertFalse($form->isValid());
654+
$this->assertNull($form->getData());
655+
$this->assertCount(1, $form->getErrors());
656+
$this->assertSame('Expected numeric value', $form->getTransformationFailure()->getMessage());
657+
$error = $form->getErrors()[0];
658+
$this->assertSame('Money amount should be numeric. "invalid_amount" is invalid.', $error->getMessage());
659+
}
660+
627661
// https://github.com/symfony/symfony/issues/6862
628662
public function testPassZeroLabelToView()
629663
{
@@ -700,3 +734,53 @@ public function testPreferOwnHelpTranslationParameters()
700734
$this->assertEquals(['%parent_param%' => 'parent_value', '%override_param%' => 'child_value'], $view['child']->vars['help_translation_parameters']);
701735
}
702736
}
737+
738+
class Money
739+
{
740+
private $amount;
741+
private $currency;
742+
743+
public function __construct($amount, $currency)
744+
{
745+
$this->amount = $amount;
746+
$this->currency = $currency;
747+
}
748+
749+
public function getAmount()
750+
{
751+
return $this->amount;
752+
}
753+
754+
public function getCurrency()
755+
{
756+
return $this->currency;
757+
}
758+
}
759+
760+
class MoneyDataMapper implements DataMapperInterface
761+
{
762+
public function mapDataToForms($data, $forms)
763+
{
764+
$forms = iterator_to_array($forms);
765+
$forms['amount']->setData($data ? $data->getAmount() : 0);
766+
$forms['currency']->setData($data ? $data->getCurrency() : 'EUR');
767+
}
768+
769+
public function mapFormsToData($forms, &$data)
770+
{
771+
$forms = iterator_to_array($forms);
772+
773+
$amount = $forms['amount']->getData();
774+
if (!is_numeric($amount)) {
775+
$failure = new TransformationFailedException('Expected numeric value');
776+
$failure->setInvalidMessage('Money amount should be numeric. {{ amount }} is invalid.', ['{{ amount }}' => json_encode($amount)]);
777+
778+
throw $failure;
779+
}
780+
781+
$data = new Money(
782+
$forms['amount']->getData(),
783+
$forms['currency']->getData()
784+
);
785+
}
786+
}

src/Symfony/Component/Form/Tests/Extension/Validator/Constraints/FormValidatorTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,47 @@ function () { throw new TransformationFailedException(); }
343343
->assertRaised();
344344
}
345345

346+
public function testTransformationFailedExceptionInvalidMessageIsUsed()
347+
{
348+
$object = $this->createMock('\stdClass');
349+
350+
$form = $this
351+
->getBuilder('name', '\stdClass', [
352+
'invalid_message' => 'invalid_message_key',
353+
'invalid_message_parameters' => ['{{ foo }}' => 'foo'],
354+
])
355+
->setData($object)
356+
->addViewTransformer(new CallbackTransformer(
357+
function ($data) { return $data; },
358+
function () {
359+
$failure = new TransformationFailedException();
360+
$failure->setInvalidMessage('safe message to be used', ['{{ bar }}' => 'bar']);
361+
362+
throw $failure;
363+
}
364+
))
365+
->getForm()
366+
;
367+
368+
$form->submit('value');
369+
370+
$this->expectNoValidate();
371+
372+
$this->validator->validate($form, new Form());
373+
374+
$this->buildViolation('safe message to be used')
375+
->setParameters([
376+
'{{ value }}' => 'value',
377+
'{{ foo }}' => 'foo',
378+
'{{ bar }}' => 'bar',
379+
])
380+
->setInvalidValue('value')
381+
->setCode(Form::NOT_SYNCHRONIZED_ERROR)
382+
->setCause($form->getTransformationFailure())
383+
->assertRaised()
384+
;
385+
}
386+
346387
// https://github.com/symfony/symfony/issues/4359
347388
public function testDontMarkInvalidIfAnyChildIsNotSynchronized()
348389
{

0 commit comments

Comments
 (0)
0