Search code examples
symfonysymfony-formsevent-listenersymfony6

Symfony POST_SUBMIT update data of underlying Object


I am working on a symfony 6.4 project in which I have a form ProductType containing 2 fields, both of which are EntityType. Only on the creation of the project, i need to be able to set the forbidden countries based with default values and update the default values based on user input.

->add('series', EntityType::class, [
    'label' => 'Collection',
    'placeholder' => 'Choisir une collection',
    'class' => Series::class,
    'choice_label' => 'name',
    'query_builder' => function (EntityRepository $repository) use ($product): QueryBuilder {
        return $repository->createQueryBuilder('s')
            ->andWhere('s.brand =' . $product->getBrand()->getId());
    }
])
->add('forbiddenCountries', EntityType::class, [
    'attr' => [
        'class' => 'searchable-select-countries'
    ],
    'data' => $product->getForbiddenCountries(),
    'class' => Country::class,
    'choice_label' => 'name',
    'choice_value' => 'alpha2',
    'multiple' => true
])

The forbidden countries are preset like this:

public function updateForbiddenCountries(FormInterface $form, Collection $forbiddenCountries): void
{
    $form
        ->add('forbiddenCountries', EntityType::class, [
            'attr' => [
                'class' => 'searchable-select-countries'
            ],
            'data' => $forbiddenCountries,
            'class' => Country::class,
            'choice_label' => 'name',
            'choice_value' => 'alpha2',
            'multiple' => true
        ]);
}
->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) {
    /** @var Product $product */
    $product = $event->getData();

    if (null !== $product->getId()) {
        return;
    }

    $this->updateForbiddenCountries($event->getForm(), $product->getBrand()->getForbiddenCountries());
})

This first part works properly, here is the expected output: enter image description here

I also need to be able to change the preset base on the change on the series field. It should look something like this:

->get('series')->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) {
    /** @var Product $product */
    $product = $event->getForm()->getParent()->getData();

    if (null !== $product->getId()) {
        return;
    }

    /** @var Series $series */
    $series = $event->getForm()->getData();

    $forbiddenCountries = $series === null ? $product->getBrand()->getForbiddenCountries() : $series->getForbiddenCountries();

dump($forbiddenCountries->toArray());

    $this->updateForbiddenCountries($event->getForm()->getParent(), $forbiddenCountries);
})

When i change the series field, i am not getting the expected view although the forbidden countries variable is updated correctly:

enter image description here

As you can see i am expecting to have 3 values instead of 2.

I tried, to add the POST_SUBMIT eventlistener on the form itself (without the ->get('series')), like this:

->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) {
    /** @var Product $product */
    $product = $event->getForm()->getData();

    if (null !== $product->getId()) {
        return;
    }

    /** @var Series $series */
    $series = $event->getForm()->get('series')->getData();
    $product->setSeries($series);

    $forbiddenCountries = $series === null ? $product->getBrand()->getForbiddenCountries() : $series->getForbiddenCountries();

    $this->updateForbiddenCountries($event->getForm(), $forbiddenCountries);
})

which triggers You cannot add children to a submitted form. exception

I even tried to update my Object to set the forbidden countries and setData from the event:

->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) {
    /** @var Product $product */
    $product = $event->getForm()->getData();

    if (null !== $product->getId()) {
        return;
    }

    /** @var Series $series */
    $series = $event->getForm()->get('series')->getData();
    $product->setSeries($series);

    $forbiddenCountries = $series === null ? $product->getBrand()->getForbiddenCountries() : $series->getForbiddenCountries();

    foreach ($forbiddenCountries as $forbiddenCountry) {
        $product->addForbiddenCountry($forbiddenCountry);
    }

    $event->setData($product);
})

which would have no impact. Notice here the setData is deprecated.

And trying to set the data on the form itself ($event->getForm()->setData($product);) would trigger an exception as expected : You cannot change the data of a submitted form.

I am guessing there is a small mistake in what i am doing that i am not seeing. It would be great if i could get some help.


Solution

  • After much troubles and a lot of time with the documentation and different forums, i finally understood that it just cant be done. The problem is that the form being submitted with errors, symfony need to re-display the form's previous state which is why whatever you do to change the data, symfony need to re-display the form's previous state. And this makes sense.

    Thus, i decided not to do this with symfony but instead to handle it with an api which would return the next list of elements which i would call in ajax and update the second field with js.

    Its not a great solution since we are increasing troubles for maintainance but to do such a thing on the server side would be to handle denormalized data with unmapped fields which you would the normalize yourself which is a lot of work for not much value and it still wont be a good solution.