'Symfony embedded form collection not persisting
I'm trying to build a form that can contain multiple questions (Age, sex, ...), and for that I need to add fields dynamically, I followed this guide, and the button to add fields works great.
But then, when I submit my form, it only contains the values of the form minus the added fields via JS
var_dump(), where it should contain question[0], [1]...
Output of var_dump():
array(1) { ["recruitment_form"]=> array(4) { ["name"]=> string(6) "ezrzer" ["description"]=> string(15) "
zzrrez
" ["submit"]=> string(0) "" ["_token"]=> string(131) "d3e9191f742255bb23bf5d69b628d.u4Ko0rH_5plSnfypsRTxNVL3"
} }
Controller:
#[Route('/admin/server/form/create', name: 'create_form')]
public function admin_create_form(Request $request, ManagerRegistry $doctrine): Response
{
$form = $this->createForm(RecrutementFormType::class);
$form->handleRequest($request);
if($form->isSubmitted() AND $form->isValid()) {
$recrutementForm = $form->getData();
foreach($form->get('questions') as $q) {
$question = new FormInput($q);
$doctrine->getManager()->persist($question);
$recrutementForm->addQuestion($question);
}
$doctrine->getManager()->persist($recrutementForm);
$doctrine->getManager()->flush();
return $this->redirectToRoute('view_server', ['id' => $recrutementForm->getId()]);
}
return $this->render('recrutement_form/admin/form.html.twig', [
'form' => $form->createView()
]);
}
RecrutementFormType:
<?php
namespace App\Form;
use App\Entity\RecrutementForm;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class RecrutementFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom du formulaire',
'attr' => [
'class' => 'form-control'
]
])
->add('description', TextareaType::class, [
'label' => 'Description',
'required' => false,
'attr' => [
'class' => 'ckeditor form-control'
]
])
->add('questions', CollectionType::class, [
'label' => 'Questions',
'entry_type' => QuestionFormType::class,
'allow_add' => true,
'by_reference' => false
])
->add('submit', SubmitType::class, [
'label' => 'Valider',
'attr' => [
'class' => 'btn btn-success'
]
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => RecrutementForm::class,
]);
}
}
QuestionFormType:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\FormInput;
class QuestionFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, [
'label' => 'Nom interne de la question',
'attr' => [
'class' => 'form-control'
]
])
->add('title', TextType::class, [
'label' => 'Titre de la question',
'attr' => [
'class' => 'form-control'
]
])
->add('description', TextareaType::class, [
'label' => 'Description',
'attr' => [
'class' => 'ckeditor form-control'
]
])
->add('type', InputType::class, [
'label' => 'Type de champ',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => FormInput::class,
]);
}
}
Twig Template:
{% extends 'form.html.twig' %}
{% block formblock %}
<div class="container">
{{ form_start(form) }}
{% if form_errors(form) is not empty %}
<div class="alert alert-danger">
{{ form_errors(form) }}
</div>
{% endif %}
<div class="row">
<div class="col lg-12">
{{ form_row(form.name) }}
</div>
</div>
<div class="row>
<div class="col-lg-12">
{{ form_row(form.description) }}
</div>
</div>
<div class="row>
<div class="col-lg-6">
<ul class="questions" data-index="{{ form.questions|length > 0 ? form.questions|last.vars.name + 1 : 0 }}" data-prototype="{{ form_widget(form.questions.vars.prototype)|e('html_attr') }}">
{% for q in form.questions %}
{{ form_row(q) }}
{% endfor %}
</ul>
</div>
<div class="col-lg-6">
<button type="button" class="add_item_link" data-collection-holder-class="questions">Add a tag</button>
</div>
</div>
<div class="row>
<div class="col-lg-12">
{{ form_row(form.submit) }}
</div>
</div>
{{ form_end(form) }}
</div>
<script>
const addFormToCollection = (e) => {
const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);
const item = document.createElement('li');
item.innerHTML = collectionHolder
.dataset
.prototype
.replace(
/__name__/g,
collectionHolder.dataset.index
);
collectionHolder.appendChild(item);
collectionHolder.dataset.index++;
};
document
.querySelectorAll('.add_item_link')
.forEach(btn => {
btn.addEventListener("click", addFormToCollection)
});
</script>
{% endblock %}
I'm using Symfony 6 and PHP 8
Final HTML code when the template is rendered: https://pastebin.com/LWUU1pKv (CKEditor code included, sry)
I guess the fact that the "questions" ul element is outside the form isn't great but can't figure out how to move it as it should already be inside
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
