Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
207 views
in Technique[技术] by (71.8m points)

php - How to create a form with multiple rows of one entity in Symfony2

First I've read documents for both Collection Field Type and How to Embed a Collection of Forms ... The example is about one entity (Task) that has one-to-many relation with another entity (Tag), and I understand it, but I can not adapt it to what I want!

To make it simpler, let say I have a Task entity, this task entity has some relations with other objects like user and 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 a table that shows information like task.title, task.startdate, task.user.name, task.user.company.name, task.project.name, And it has 2 fields editable, textbox "Description" and checkbox "active". You can edit multiple tasks and submit the form using one button at the bottom of the table in the main form, so basically you should be able to update multiple records in one transaction (instead of making one form and one submit button per row and therefor one record update per submit).

I have many issues with this complicated design:

First I wanted to follow the sample to embed a collection of forms inside the main form, So I made a Form Type for my Task that should be like one form per row. I made these files:

Form Type for Task:

// src/Acme/TaskBundle/Form/Type/TaskType.php
namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolverInterface;

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' => 'AcmeTaskBundleEntityTask',
        ));
    }

    public function getName()
    {
        return 'taskType';
    }
}

Form Type for main form:

// src/Acme/TaskBundle/Form/Type/SaveTasksType.php
namespace AcmeTaskBundleFormType;

use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolverInterface;
use AcmeTaskBundleFormTypeTaskType.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';
    }
}

Tasks Form Controller:

// src/Acme/TaskBundle/Controller/ManageTasksController.php
namespace AcmeTaskBundleController;

use AcmeTaskBundleEntityTask;
use AcmeTaskBundleFormTypeSaveTaskType;
use SymfonyComponentHttpFoundationRequest;
use SymfonyBundleFrameworkBundleControllerController;

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

Task Form Twig Template (just related part):

<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 an issue here, when I get the result from query builder it is a mess of arrays containing objects in them, I get an error about

The form's view data is expected to be an instance of class AcmeTaskBundleEntityTask, but is a(n) array. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) array to an instance of AcmeTaskBundleEntityTask.

This is the query:

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

Soooo, I used Partial Objects guide to see if it can help, it helps to make the task object in the query result and I could extract it and send it to form, but still it seems the rest of form is unaware of the rest of objects...

Ok, so maybe I'm choosing the wrong approach, I'm not sure! please if you have any suggestions about what should I do, put a note here... I'm struggling with this for more than a week! Thanks in advance for your time! Even if you don't put any note, I appreciate that you spend time reading my very long question! Thanks! :)

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here's a start on a possible solution. The example below takes a single entity Skills and presents all of them on a single page. What I don't know is whether this technique can be used to persist children objects. I would expect one could loop through the returned data and persist as required.

The code below results in a page with a list of all possible Skills and a checkbox for declaring each enabled or enabled.

In a 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 a 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 %}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...