How to submit multiple forms of the same type with a single button in symfony2

I have a todolist where I display three forms of a task type

$task1 = new Task(); $form1 = $this->createForm(new MyForm('f1'), $task1); $task2 = new Task('fo'); $form2 = $this->createForm(new MyForm('f2'), $task2); $task3 = new Task(); $form3 = $this->createForm(new MyForm('f3'), $task3); 

Now the problem is that I only have a submit button. How can I save these three tasks in one controller. and the user can add additional forms also dynamically.

so how to solve this problem

+6
source share
3 answers

Create a Form Model - for example TaskList - that contains a collection of Task s. Then create a TaskListType that contains the collection of TaskType s. This way you will have one form with as many tasks as you want.

+5
source

For completeness, we find below a complete example.

You should create a new model that represents the desired shape. The fact is that you probably do not want to influence Doctrine (for example, see the doctrine: schema: update command). He may try to create a table for an entity that does not actually exist. To avoid this, simply put your model class in the Model folder (\ src \ Acme \ Bundle \ DemoBundle \ Model \ TaskList.php).

Suppose the following class of form TaskType:

 <?php namespace Acme\Bundle\DemoBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TaskType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('id', null, array('read_only' => true)) ->add('name'); } /** * @param OptionsResolverInterface $resolver */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults( array( 'data_class' => 'Acme\Bundle\DemoBundle\Entity\Task' ) ); } /** * @return string */ public function getName() { return 'acme_demo_task'; } } 

This should be the class of the TaskList model:

 <?php namespace Acme\Bundle\DemoBundle\Model; use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; /** * Class TaskList * @package Acme\Bundle\DemoBundle\Model * * @ORM\Entity() */ class TaskList { /** * @var \Doctrine\Common\Collections\ArrayCollection * @ORM\ManyToMany(targetEntity="\Acme\Bundle\DemoBundle\Entity\Task") */ private $tasks; public function __construct() { $this->tasks = new ArrayCollection(); } /** * @param \Acme\Bundle\DemoBundle\Entity\Task $task * @return $this */ public function addTask(\Acme\Bundle\DemoBundle\Entity\Task $task) { $this->tasks[] = $task; return $this; } /** * @param \Acme\Bundle\DemoBundle\Entity\Task $task * @return $this */ public function removeTask(\Acme\Bundle\DemoBundle\Entity\Task $task) { $this->tasks->remove($task); return $this; } /** * @return ArrayCollection */ public function getTasks() { return $this->tasks; } /** * @param \Doctrine\Common\Collections\Collection $tasks * @return $this */ public function setTasks(\Doctrine\Common\Collections\Collection $tasks) { $this->tasks = $tasks; return $this; } /** * @param \Knp\Component\Pager\Pagination\PaginationInterface $pagination * @return $this */ public function setFromPagination(\Knp\Component\Pager\Pagination\PaginationInterface $pagination) { foreach ($pagination as $task) { $this->addTask($task); } return $this; } } 

And find below the TaskListType class:

 <?php namespace Acme\Bundle\DemoBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TaskListType extends AbstractType { /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add( 'tasks', 'collection', array( 'type' => new \Acme\Bundle\DemoBundle\Form\TaskType(), ) ) ->add('save', 'submit'); } /** * @param OptionsResolverInterface $resolver */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults( array( 'data_class' => 'Acme\Bundle\DemoBundle\Model\TaskList' ) ); } /** * @return string */ public function getName() { return 'acme_demo_task_list'; } } 

And your services.yml (optional):

 services: acme.demo.form.type.task_list: class: Acme\Bundle\DemoBundle\Form\TaskListType tags: - { name: form.type, alias: acme_demo_task_list } 

And an example controller:

 public function indexAction($page) { ini_set('xdebug.max_nesting_level', 300); // this might be useful with deeply nested forms $search = $this->getRequest()->get( 'search', array( 'name' => '', 'date' => '', 'lang' => $this->container->getParameter('acme_core.default_lang') ) ); /** * @var \Doctrine\ORM\EntityManager $em */ $em = $this->getDoctrine()->getManager(); $paginator = $this->get('knp_paginator'); $pagination = $paginator->paginate( $em->getRepository('AcmeDemoBundle:Task')->getQueryFilteringByLangNameAndDate( $search['lang'], $search['name'], $search['date'] != '' ? new \DateTime($search['date']) : null ), $page, $this->getRequest()->get('elementsPerPage', 10) ); $taskList = new TaskList(); $taskList->setFromPagination($pagination); $form = $this->createForm('acme_demo_task_list', $taskList); // "acme_demo_task_list" has been defined in the services.yml file $form->handleRequest($this->getRequest()); if ($form->isValid()) { foreach ($form->getData() as $task) { $em->merge($task); } $em->flush(); } return $this->render( 'AcmeDemoBundle:Task:index.html.twig', array( 'search' => $search, 'pagination' => $pagination, 'form' => $form->createView() ) ); } 

Hope this helps!

+5
source

We followed the exapmle featured by "Francesco Casula" and it worked great.

For orur purposes, we don’t need pagination, so this is how we filled our collection (in controller ):

 $entities = $em->getRepository('AcmeBundle:Stock')->findAll(); $stockList = new StockList(); // This is our model, what Francesco called 'TaskList' foreach ($entities as $entity) { $stockList->addStock($entity); } // StockListType() is what Francesco called TaskListType $form = $this->createForm(new StockListType(), $stockList, array( 'action' => $this->generateUrl('stock_take_update'), 'method' => 'POST', 'attr' => array('class' => 'form-horizontal'), )); 

For those who need to customize the output of form collections, we were able to access the {% for form in forms.children.stocks %} by repeating it at {% for form in forms.children.stocks %} . "Stocks" is the name of the field in our form builder (in StockListType ):

 public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add( 'stocks', 'collection', array( 'type' => new StockType(), ) ) ->add('submit', 'submit') ; } 

This is what we ended up using in our twig view:

 {{ form_start(forms) }} <table class="table table-stripped table-hover"> <thead> <th>#</th> <th>Name</th> <th>Code</th> <th>Location</th> <th>Total</th> <th>Updated Toal</th> </thead> <tbody> {% set counter = 1 %} {% for form in forms.children.stocks %} <tr> <div class="hidden"> {{ form_widget(form.name) }} {{ form_widget(form.code) }} {{ form_widget(form.location) }} {{ form_widget(form.available) }} {{ form_widget(form.assigned) }} {{ form_widget(form.minLevel) }} {{ form_widget(form.type) }} {{ form_widget(form.colourCode) }} </div> <td>{{ counter }}</td> <td> {% if form.vars.data.name is defined %} {{ form.vars.data.name }} {% endif %} </td> <td> {% if form.vars.data.code is defined %} {{ form.vars.data.code }} {% endif %} </td> <td> {% if form.vars.data.location is defined %} {{ form.vars.data.location }} {% endif %} </td> <td> {% if form.vars.data.total is defined %} {{ form.vars.data.total }} {% endif %} </td> <td>{{ form_widget(form.total) }}</td> </tr> {% set counter = counter + 1 %} {% endfor %} </tbody> </table> {{ form_widget(forms.submit) }} {{ form_end(forms) }} 
+4
source

Source: https://habr.com/ru/post/921827/


All Articles