Skip to content

[Form] Explain how to trigger errors from DataMapper #13513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

Nek-
Copy link
Contributor

@Nek- Nek- commented Apr 8, 2020

Symfony allows the user to send a form error from the DataMapper in case the data does not fit the object. This powerful feature was not documented.

Symfony allows the user to send a form error from the DataMapper in case
the data does not fit the object. This powerful feature was not
documented.
@Nek- Nek- changed the title Explain trigger errors from DataMapper [Form] Explain how to trigger errors from DataMapper Apr 8, 2020
Copy link
Member

@wouterj wouterj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, didn't know this feature!

Can you maybe add the foreach/if statements you introduced to the original code example (around line 130)? Then we can add a comment like // TransformationFailedException will be transformed into a FormError. That would keep this short and simple, while not loosing important information.

@wouterj wouterj added the Form label Apr 10, 2020
@Nek- Nek- changed the base branch from master to 4.4 April 10, 2020 14:05
Nek- added a commit to Nek-/symfony-docs that referenced this pull request Apr 10, 2020
Nek- added a commit to Nek-/symfony-docs that referenced this pull request Apr 10, 2020
@Nek- Nek- force-pushed the document-form-error-from-datamapper branch from ab0dd30 to c4c5280 Compare April 10, 2020 17:56
@Nek-
Copy link
Contributor Author

Nek- commented Apr 10, 2020

@wouterj this is updated according to your comment.

@Nek- Nek- force-pushed the document-form-error-from-datamapper branch from c4c5280 to 250f237 Compare April 10, 2020 17:57
Copy link
Member

@wouterj wouterj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks a lot for the quick response!

@HeahDude
Copy link
Contributor

Hello @Nek-, thank you for proposing this change.

I'm really sorry, I comment after you put some work into it, and @wouterj approved, but I'm 👎 for now on merging this PR.

I know we can do this, but this is hack and it's against separation of responsibilities.
Instead the proper constraint should be used to validate the data that should allow an invalid state to be validated.
If keys do not exist in $forms or a Color instance is not passed an UnexpectedTypeException should be thrown instead but this is not what this about here (ref https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/DataMapperInterface.php#L59). Because it would be a mistake of the dev configuring the form using the mapper in an invalid context or a wrongly configured type.

Also, the TransformationFailedException is part of the transformer contract, it means the component can change its internal and stop catching it when mapping (because it can happen in cascade due to the composite pattern).

So I'm not in favor of documenting what is IMHO an invalid usage of the API, which would not be supported by our BC policy.

Gentle ping @xabbuh, WDTY about this?

@xabbuh
Copy link
Member

xabbuh commented Apr 11, 2020

I think I agree with Jules here.

@HeahDude
Copy link
Contributor

I'm closing here then, sorry again, thank you for understanding.

@HeahDude HeahDude closed this Apr 11, 2020
@Nek-
Copy link
Contributor Author

Nek- commented Apr 11, 2020

It has been announced as new feature here: https://symfony.com/blog/new-in-symfony-4-3-more-form-improvements#custom-errors-in-data-mappers

Please tell me what is the alternative to map an object without throwing a 500 error on wrong input data, I'll be more than happy to write doc about it.

@HeahDude
Copy link
Contributor

HeahDude commented Apr 11, 2020

The feature is legit about customizing the message without using invalid_message option, because the message is not enough to know which transformer failed or why and must stay generic.

Thank you for reporting that there has been a mistake in the news... that should show an usage of the exception in a transformer instead of a mapper.

Ping @javiereguiluz any chance we can correct it 🙏?

Please tell me what is the alternative to map an object without throwing a 500 error on wrong input data, I'll be more than happy to write doc about it.

@Nek- thank you for proposing but it seems that the feature has been properly document in #11756 already.

If you really need to check the validate state from a mapper (e.g you need to catch an invalid argument exception when calling new or any mutator) you have many options:

  • add a form error directly using Form::addError(new FormError('your message'))
  • check is the field is synchronized (no transformation failure when it was submitted) or has errors already (i.e added by a listener) before calling your logic (new or mutators)

@Nek-
Copy link
Contributor Author

Nek- commented Apr 12, 2020

@HeahDude I suggest adding my example but with the usage of the addError() method. WDYT?

@HeahDude
Copy link
Contributor

HeahDude commented Apr 12, 2020

This is a lesser evil for the docs I think.

What I suggest is adding a note or a caution at the end about validation, saying that a mapper must deal with an invalid state when submitted, either using try/catch block or checking $field->isSynchronized() before mapping (or disabled or valid, see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php#L75 for ref);
but that validation should be done either by constraining the underlying object (allowing it to have an invalid state) or using the constraints option on the compound field or its children (allowing the mapper to not map the invalid data and violations will be added afterwards when cascading the form tree after submission).

@Nek-
Copy link
Contributor Author

Nek- commented Apr 12, 2020

or using the constraints option on the compound field or its children (allowing the mapper to not map the invalid data and violations will be added afterwards when cascading the form tree after submission).

How can validation work if there is no hydrated data? 🤔 (I understand this flow is better, but I do not understand how it could work, except for NotNull validation constraint obviously)

@HeahDude
Copy link
Contributor

With the constraints option forms and fields underlying data are traversed and validated in addition to the root data structure, the root data graph being optional (i.e using an array as root, or a class without metadata).

@Nek-
Copy link
Contributor Author

Nek- commented Apr 12, 2020

@HeahDude yes but what if your class does not allow an invalid state?

@HeahDude
Copy link
Contributor

HeahDude commented Apr 12, 2020

I know it's hard to get the big picture, trust me ;).

I will show you an example. Consider a model class:

// ...
class Apply
{
    /** @Assert\NotNull */
    public ?string firstName;
    // ... more fields
}

Then the following form:

// ...
class ApplyFormType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver)
    {
        // This class is used as underlying data
        $resolver->setDefault('data_class', Apply::class);
    }

    public function buildForm(FormBuilderInterface $builder, array $options = [])
    {
        $builder
            ->add('firstName', TextType::class)
            // .. add every field related to the object graph
            // they will be validated by cascade when validating once $rootForm->getData()
            // reading annotated constraints

            // add a required field for this form to be valid but no data needed in model
            ->add('condition', CheckboxType::class, [
                'label' => 'Accept something',
                'required' => false,
                // we don't want to read and write in apply underlying object
                // this field can still used its own transformers and listeners that may invalid it
                'mapped' => false,
                // add a specific constraint in the form graph that will be validated in a second time
                // on $rootForm->get('condition')->getData()
                'constraints' => new Assert\IsTrue(['message' => 'Condition should be checked']),
            ])
        ;
    }
}

I hope it helps you understanding. The $rootForm->get('condition')->getData() is not really what's happening but this is equivalent to explain.

@HeahDude
Copy link
Contributor

but what if your class does not allow an invalid state?

As I said before:

using try/catch block

and

using the constraints option on the compound field or its children (allowing the mapper to not map the invalid data and violations will be added afterwards when cascading the form tree after submission).

@Nek-
Copy link
Contributor Author

Nek- commented Apr 14, 2020

@HeahDude I tried to do what you said.
I have no idea how this could work. Even if it does, it would give such bad error reporting. Really I don't get it.

[edit] I made a little error by not catching all error, this is my bad we discovered it while discussing. The following example works and return proper errors:

https://gl.nekland.fr/Nek/test-form-datamapper-errors/-/blob/master/src/Controller/TestHeahdudeController.php

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants