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