Как создать форму с несколькими строками одного объекта в Symfony2

Сначала я прочитал документы как для типа поля сбора, так и для вставки коллекции форм … Пример состоит из одного объекта (задачи), который имеет отношение «один ко многим» к другому объекту (тегу), и я это понимаю , но я не могу приспособить его к тому, что хочу!

Чтобы упростить, скажем, у меня есть объект Task, этот объект задачи имеет некоторые отношения с другими объектами, такими как пользователь и проект (каждая задача может иметь один пользователь и один проект)

Я хочу сделать одну форму внутри этой формы списком задач, каждая задача в одной строке таблицы, которая отображает такую ​​информацию, как task.title , task.startdate , task.user.name , task.user.company.name , task .project.name , и он имеет 2 поля редактируемые, текстовое поле «Описание» и флажок «активно» . Вы можете редактировать несколько задач и отправлять форму с помощью одной кнопки в нижней части таблицы в основной форме, поэтому в основном вы должны иметь возможность обновлять несколько записей в одной транзакции (вместо того, чтобы делать одну форму и одну кнопку отправки на строку и для этого одно обновление для каждого отправителя).

У меня много проблем с этим сложным дизайном:

Сначала я хотел следовать образцу, чтобы вставить коллекцию форм внутри основной формы. Поэтому я создал тип формы для своей задачи, который должен быть как одна форма для каждой строки. Я сделал эти файлы:

Тип формы для задачи:

// 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'; } } 

Форма Тип для основной формы:

 // 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'; } } 

Контроллер формы задач:

 // 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 (только что связанная часть):

 <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> 

НО есть проблема здесь, когда я получаю результат от построителя запросов, это беспорядок массивов, содержащих объекты в них, я получаю сообщение об ошибке

Ожидается, что данные вида формы будут экземпляром класса Acme \ TaskBundle \ Entity \ Task, но является (n) массивом. Вы можете избежать этой ошибки, установив опцию «data_class» равной нулю или добавив трансформатор вида, который преобразует массив (n) в экземпляр Acme \ TaskBundle \ Entity \ Task.

Это запрос:

 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, я использовал руководство Partial Objects, чтобы узнать, может ли он помочь, он помогает сделать объект задачи в результате запроса, и я могу извлечь его и отправить его в форму, но все же кажется, что остальная часть формы не знает остальных объекты…

Хорошо, так что, возможно, я выбираю неправильный подход, я не уверен! пожалуйста, если у вас есть какие-либо предложения о том, что мне следует сделать, поместите здесь примечание … Я боюсь с этим больше недели! Спасибо заранее за ваше время! Даже если вы не ставите никаких замечаний, я ценю, что вы тратите время на мой очень длинный вопрос! Благодаря! 🙂

Вот начало возможного решения. В приведенном ниже примере используется один уровень навыков и представлены на одной странице. То, что я не знаю, заключается в том, можно ли использовать эту технику для сохранения дочерних объектов. Я ожидал бы, что можно было бы перебирать возвращаемые данные и сохраняться по мере необходимости.

В приведенном ниже коде представлена ​​страница со списком всех возможных навыков и флажок для объявления каждого включенного или включенного.

В контроллере:

  $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(),...] 

В шаблоне:

 {% 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 %} 

Я использую другую стратегию. Мой файл TWIG похож на вопрос Моники. Но есть несколько, но очень полезных отличий. Вот ваш код:

  {{ 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) }} 

В файле TWIG я перехожу к созданию другого имени для каждой записи в форме для поля «discrecional». Это поможет мне идентифицировать каждую запись в контроллере. Осторожно.

Как только пользователь нажимает кнопку «отправить», я выполняю итерацию в моем файле контроллера, как показано ниже:

 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; } 

Но обновление регистров – это работа, которая выполняется в моем файле репозитория через запрос с использованием UPDATE, вместо того, чтобы делать это в файле Controller. Чтобы избежать ненужных запросов и перегрузки сервера, я делаю только ОБНОВЛЕНИЕ записей, которые ранее были изменены. В этом примере следующие строки моего контроллера проверяют, было ли изменение в записи (в моем случае я просто редактирую поле «discrecional». Если поле было изменено, я вызываю запрос и обновляю запись):

 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); } 

Мой полный файл контроллера находится здесь:

 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() )); } 

Мой полный запрос на репозиторий здесь:

 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; } 

Мой полный окончательный запрос Репозитория с функцией UPDATE находится здесь:

 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(); }