8000 feature #20978 [Form] TransformationFailedException: Support specifyi… · symfony/symfony@79aeed6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 79aeed6

Browse files
committed
feature #20978 [Form] TransformationFailedException: Support specifying message to display (ogizanagi)
This PR was merged into the 4.3-dev branch. Discussion ---------- [Form] TransformationFailedException: Support specifying message to display | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #22501 | License | MIT | Doc PR | N/A Currently, a failed transformation can't display a very accurate message, as it only uses the value of the `invalid_message` option. I suggest to add more flexibility, somehow the same way we did for `CustomUserMessageAuthenticationException`. A test case in `FormTypeTest` shows a use-case based on @webmozart's [blog post about value/immutable objects in Symfony forms](https://webmozart.io/blog/2015/09/09/value-objects-in-symfony-forms/). ~I named the exception properties and methods the same way as for `CustomUserMessageAuthenticationException`, but do you think the followings would be better:~ - ~`setSafeMessage` ➜ `setInvalidMessage`~ - ~`getMessageKey` ➜ `getInvalidMessageKey`~ - ~`getMessageData` ➜ `getInvalidMessageParameters`~ ~in order to echoes `invalid_message` and `invalid_message_parameters` options?~ => Replaced to use `invalidMessage` & `invalidMessageParameters` Commits ------- d11055c [Form] TransformationFailedException: Support specifying message to display
2 parents 76260e7 + d11055c commit 79aeed6

File tree

5 files changed

+169
-7
lines changed
  • Tests/Extension
  • 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