How to create a form with multiple lines of one object in Symfony2

First I read the documents for Collection field type and How to insert a collection of forms ... An example consists of one object (task) that has a one-to-many relationship with another object (tag), and I understand it, but I can’t adapt it to what I want!

To simplify, say, I have a Task object, this task object has some relationships with other objects, such as a user and a project (each task can have one user and one project)

I want to make one form, inside this form, a list of tasks, each task in one row of the table displaying information like task.title , task.startdate , task.user.name , task.user.company.name , task.project.name , and it has 2 fields for editing, a text field "Description" and a flag "active" . You can edit several tasks and submit the form with one button at the bottom of the table in the main form, so basically you should be able to update several records in one transaction (instead of doing one form and one submit button per line and for this one record update for each).

I have a lot of problems with this complex design:

First, I wanted to follow the pattern to insert a collection of forms inside the main form. Therefore, I created a form type for my task, which should be like one form for each row. I made these files:

Type of form for the task:

// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('description', 'text', ['label' => false, 'required' => false, 'attr' => ['placeholder' => 'description']]); $builder->add('active', 'checkbox', ['label' => false, 'required' => false, 'data' => true]); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Acme\TaskBundle\Entity\Task', )); } public function getName() { return 'taskType'; } } 

Type of form for the main form:

 // src/Acme/TaskBundle/Form/Type/SaveTasksType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Acme\TaskBundle\Form\Type\TaskType.php; class SaveTasksType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('tasksCollection', 'collection', ['type' => new TaskType()]); $builder->add('tasksSubmit', 'submit', ['label' => 'Save']); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults([ 'attr' => ['class' => 'form-horizontal'], 'method' => 'POST' ]); } public function getName() { return 'saveTasksType'; } } 

Task form controller:

 // src/Acme/TaskBundle/Controller/ManageTasksController.php namespace Acme\TaskBundle\Controller; use Acme\TaskBundle\Entity\Task; use Acme\TaskBundle\Form\Type\SaveTaskType; use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class ManageTasksController extends Controller { public function showListAction(Request $request) { $repository = $this->getDoctrine()->getRepository('ExampleBundle:Task'); $tasks = $repository->findAll(); $taskSaveForm = $this->createForm(new SaveTasksType(['tasks' => $tasks])); return $this->render('AcmeTaskBundle:Task:list.html.twig', array( 'taskSaveForm' => $taskSaveForm->createView(), )); } } 

Twig task form template (related part only):

 <div class="innerAll"> {{ form_start(taskSaveForm) }} {{ form_errors(taskSaveForm) }} <table class="table table-bordered table-striped table-primary list-table"> <thead> <tr> <th>Task ID</th> <th>Title</th> <th>Start Date</th> <th>User</th> <th>Company</th> <th>Project</th> <th>Description</th> <th>Active</th> </tr> </thead> <tbody> {% for task in taskSaveForm.tasksCollection %} <tr> <td>{{ task.id }}</td> <td><a href="https://localhost/taskid={{ task.id }}">{{ task.title }}</a></td> <td>{{ task.startDate }}</td> <td>{{ task.userName }}</td> <td>{{ task.companyName }}</td> <td>{{ task.projectName }}</td> <td>{{ form_widget(task.description) }}</td> <td>{{ form_widget(task.active) }}</td> <td></td> </tr> {% endfor %} </tbody> </table> <div>{{ form_row(taskSaveForm.tasksSubmit) }}</div> {{ form_end(taskSaveForm) }} </div> 

BUT there is a problem here, when I get the result from the query builder, this is a mess of arrays containing objects in them, I get an error

The form view data is expected to be an instance of the Acme \ TaskBundle \ Entity \ Task class, but is an (n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that converts the array (n) to an instance of Acme \ TaskBundle \ Entity \ Task.

This is the request:

 createQueryBuilder() ->select( " task.id, task.title, task.startDate, task.description, user.name as userName, company.name as companyName, project.name as projectName, " ) ->from('Acme\TaskBundle\Entity\Task', 'task') ->innerJoin('task.project', 'project') ->innerJoin('task.user', 'user') ->innerJoin('Acme\TaskBundle\Entity\Company', 'company', 'with', 'store.company = company') ->where('task.active = :isActive')->setParameter('isActive', true); 

Soooo, I used Partial Objects to find out if it can help, it helps to make the task object as a result of the request, and I could extract it and submit to the form, but still it seems that the rest of the form does not know the rest of the objects .. .

Well, maybe I am choosing the wrong approach, I'm not sure! please, if you have any suggestions on what I should do, put a note here ... I’m afraid with this for more than a week! Thanks in advance for your time! Even if you do not put any comments, I appreciate that you spend time on my very long question! Thank you :)

+6
source share
2 answers

This is where a possible solution begins. The example below uses one skill level and is presented on one page. What I do not know is whether it is possible to use this technique to save child objects. I would expect that it would be possible to go through the returned data and persist as needed.

The code below provides a page with a list of all the possible skills and a check box to declare each included or included.

In the controller:

  $skills = $em->getRepository("TruckeeMatchingBundle:Skill")->getSkills(); $formSkills = $this->createForm(new SkillsType(), array('skills' => $skills)); ... if ($request->getMethod() == 'POST') { $formSkills->handleRequest($request); foreach ($skills as $existingSkill) { $em->persist($existingSkill); } } ... return ['formSkills' => $formSkills->createView(),...] 

In the template:

 {% for skill in formSkills.skills %} {{ skill.vars.value.skill }} <input type="hidden" name="skills[skills][{{ loop.index0 }}][skill]" value="{{ skill.vars.value.skill }}"> <input type="checkbox" name="skills[skills][{{ loop.index0 }}][enabled]" {%if skill.vars.value.enabled %}checked="checked"{%endif%} {% endfor %} 
+4
source

I use a different strategy. My TWIG file looks like a file with Monica's question. But there are a few, but very useful differences. Here is your code:

  {{ form_start(form) }} {% for docente in docentes %} Id: <input type="integer" name="{{ docente.id }}" required="required" style="width:30px" value="{{ docente.id }}" readonly> Apellido: <input type="text" name="{{ docente.apellido }}" required="required" style="width: 80px" value="{{ docente.apellido }}" readonly> Nombres: <input type="text" name="{{ docente.nombres }}" required="required" style="width: 80px" value="{{ docente.nombres }}" readonly> Discrecional: <input type="checkbox" name="D{{ docente.id }}" value="{{ docente.discrecional }}" {% if docente.discrecional==1 %}checked{% endif %}> <br> <br> {% endfor %} <input type="submit" value="Grabar" /> {{ form_end(form) }} 

In the TWIG file, I proceed to create a different name for each record in the form for the "discrecional" field. This will help me identify each entry in the controller. Look.

As soon as the user clicks the submit button, I iterate through my controller file, as shown below:

 if ($form->isSubmitted() && $form->isValid()) { $i=0; foreach ($defaultData as $value) { $data2= array('id' =>$request->request->get($defaultData[$i]['id']), 'discrecional' =>$request->request->get('D'.$defaultData[$i]['id'])); if (($request->request->get('D'.$defaultData[$i]['id'])== '0' and $defaultData[$i]['discrecional']=='0') or ($request->request->get('D'.$defaultData[$i]['id'])== NULL and $defaultData[$i]['discrecional']=='1')) { $em->getRepository('BackendBundle:Docentes')->findDocenteFiltId2($data2); } $i=$i+1; } 

But updating the registers is the work that is done in my repository file through a query using UPDATE, instead of doing it in the Controller file. To avoid unnecessary requests and server overload, I only do UPDATE entries that were previously changed. In this example, the following lines of my controller check if there has been a change in the record (in my case, I just edit the “discrecional” field. If the field has been changed, I call the query and update the record):

 if (($request->request->get('D'.$defaultData[$i]['id'])== '0' and $Data[$i]['discrecional']=='0') or ($request->request->get('D'.$defaultData[$i]['id'])== NULL and $defaultData[$i]['discrecional']=='1')) { $em->getRepository('BackendBundle:Docentes')->findDocenteFiltId2($data2); } 

My full controller file is here:

 public function discrecionalAction(Request $request) { $em = $this->getDoctrine()->getManager(); $defaultData= $em->getRepository('BackendBundle:Docentes')->buscarDocentesActivos2(); // construimos un formulario "vacío" sin campos definido $form = $this->createFormBuilder($defaultData); $form = $form->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $i=0; foreach ($defaultData as $value) { $data2= array('id' =>$request->request->get($defaultData[$i]['id']), 'discrecional' =>$request->request->get('D'.$defaultData[$i]['id'])); if (($request->request->get('D'.$defaultData[$i]['id'])== '0' and $defaultData[$i]['discrecional']=='0') or ($request->request->get('D'.$defaultData[$i]['id'])== NULL and $defaultData[$i]['discrecional']=='1')) { $em->getRepository('BackendBundle:Docentes')->findDocenteFiltId2($data2); } $i=$i+1; } return $this->redirectToRoute('docentes_discrecional'); } return $this->render('docentes/discrecional.html.twig', array( 'docentes' =>$defaultData, 'form' => $form->createView() )); } 

My first repository request is here:

 public function buscarDocentesActivos2() { $fields = array('d.id', 'd.apellido', 'd.nombres', 'd.discrecional'); $query = $this->getEntityManager()->createQueryBuilder(); $query ->select($fields) ->from('BackendBundle:Docentes', 'd') ->where('d.activo=true') ->orderBy('d.apellido, d.nombres'); $consulta = $query->getQuery()->getResult(); return $consulta; } 

My complete final repository request with the UPDATE function is here:

 public function findDocenteFiltId2($filtro) { if (is_null($filtro['discrecional'])){ $discrec= '0'; }; if ($filtro['discrecional']=='0'){ $discrec= '1'; }; $em = $this->getEntityManager(); $consulta = $em->createQuery(' UPDATE BackendBundle:Docentes d SET d.discrecional = :disc WHERE d.id = :idver '); $consulta->setParameters(array( 'idver' => $filtro['id'], 'disc' => $discrec, )); return $consulta->getArrayResult(); } 
0
source

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


All Articles