Search code examples
symfonysymfony-forms

Symfony Forms: how to map only one of the fields of an embedded Form-Type


I am trying to develop a form that will later be used as a base-form for specialized variants. So it will have inherit_data => true. It is furthermore mapped to a base-data-class.

In this base-form I also use a custom form-type that has some fields. This is where I am currently stuck. I want to have only one of the fields of the custom inner form-type to map back to the actual base-form so the mapping to the base-data-class can be done correctly.

I will try to show this by some simplified classes that hopefully convey what I want:

class BaseFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('unproblematicProperty')
            ->add('myProblematicProperty', InnerFormType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'inherit_data' => true,
            'data_class' => BaseDataClass::class,
        ]);
    }
}

Then some InnerFormType:

class InnerFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('shouldBeMappedToMyProblematicProperty')
            ->add('shouldntBeMapped1')
            ->add('shouldntBeMapped2')
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
        ]);
    }
}

The goal is that the data-class can be instantiated correctly without me having to make parts of the BaseFormType unmapped even though they hold actual data for the objects to be instantiated. I.e. I don't want to have part of the object hydration to be done by binding the form-type to the data-class and part of it manually in a controller.

So, how can I achieve a direct "backmapping" of the value from shouldBeMappedToMyProblematicProperty to myProblematicProperty?

If the question needs improvement or further information I will try my best to edit accordingly.


Solution

  • As I found out, this can elegantly be achieved by implementing DataMapperInterface in the InnerFormType.

    The contract is to implement two methods:

    • mapDataToForms($viewData, iterable $forms)
    • mapFormsToData(iterable $forms, &$viewData)

    Remark that if you need to change the representation of your data (in my case for example wrapping strings into UnicodeStrings and vice-versa), that the DataMapper is not the place to do so.

    To do this use DataTransformers (implement DataTransformerInterface).

    Remark that on get the DataMapper's method mapDataToForms is called before DataTransformers transform while on form-submit DataTransformer's reverseTransform is called before DataMapper's mapFormsToData.

    See also: https://symfony.com/doc/current/form/data_mappers.html

    To get a better understanding of the inner ongoings I found enlightening to dd(...) some of the involved vars at certain points in the flow as the documentation is somewhat lacking in this area in my opinion.