Symfony 2.8:表单集合和一对多关系不能正确保持

时间:2017-04-26 08:40:51

标签: php forms doctrine one-to-many symfony-2.8

我需要在其父表单中嵌入一个实体表单。我跟随官方documentation将一张表格嵌入另一个与一对多关系的表格中。

问题是虽然我设法正确显示表单,但是当需要持久保存另一个子实体时,它可以正常使用前两个对象,当我尝试创建第三个对象时,它会删除数组中的第二个对象并创建第三个。

示例:我有一个与WorkExperience实体有一对多关系的CV实体

代码:

实体:

    class Curriculum
    {
        /**
        * @ORM\OneToMany(targetEntity="appBundle\Entity\WorkExperience", mappedBy="curriculum", cascade={"persist", "remove"}, orphanRemoval=true)
        */
        private $workExperience;

        public function __construct()
        {
            $this->workExperience = new \Doctrine\Common\Collections\ArrayCollection();
        }


     /**
     * Add workExperience
     *
     * @param appBundle\Entity\WorkExperience $workExperience
     * @return Curriculum
     */
    public function addWorkExperience(appBundle\Entity\WorkExperience $workExperience)
    {
        $this->workExperience->add($workExperience);
        $workExperience->setCurriculum($this);

        return $this;
    }

     /**
     * Remove workExperience
     *
     * @param appBundle\Entity\WorkExperience $workExperience
     */
    public function removeWorkExperience(appBundle\Entity\WorkExperience $workExperience)
    {
        $this->workExperience->removeElement($workExperience);
    }

     /**
     * Get workExperience
     *
     * @return \Doctrine\Common\Collections\Collection
     */
        public function getWorkExperience()
        {
            return $this->workExperience;
        }
}

WorkExperience:

class WorkExperience
{
    /**
     * @ORM\ManyToOne(targetEntity="appBundle\Entity\Curriculum", inversedBy="workExperience")
     * @ORM\JoinColumn(name="curriculum", referencedColumnName="id")
     */
    private $curriculum;

    /**
     * Set curriculum
     *
     * @param string $curriculum
     * @return WorkExperience
     */
    public function setCurriculum($curriculum)
    {
        $this->curriculum = $curriculum;

        return $this;
    }

    /**
     * Get curriculum
     *
     * @return string 
     */
    public function getCurriculum()
    {
        return $this->curriculum;
    }
}

然后,formType(我只创建了workExperience表单,因为它是我在collectionType字段中需要的)

WorkExperienceType:

<?php

namespace appBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use appBundle\Entity\WorkExperience;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Intl\Intl;
use Symfony\Component\Form\Extension\Core\Type\DateType;

class WorkExperienceType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $builder
            ->add('fromDate', DateType::class, array(
                'widget' => 'single_text',
                'html5' => false,
                'attr' => ['class' => 'form-control js-datepicker'],
                'format' => 'dd-MM-yyyy', 
                'label' => 'From')
            )
            ->add('toDate', DateType::class, array(
                'widget' => 'single_text',
                'html5' => false,
                'attr' => ['class' => 'form-control js-datepicker'],
                'format' => 'dd-MM-yyyy', 
                'label' => 'To',
                'required' => false,
                )
            )
            ->add('ongoing', 'checkbox', array('required' => false,))
            ->add('jobProfile', EntityType::class, array(
                'class' => 'appBundle:JobProfile',
                'query_builder' => function (EntityRepository $er) use ($options) {
                    return $er->createQueryBuilder('j')
                        ->where('j.company = :company')
                        ->setParameter('company', $options['company']);
                },
                'choice_label' => 'name',
                'required' => false,
                'placeholder' => 'Job Profile'
            ))
            ->add('employerName', 'text', array('label' => "Name"))
            ->add('employerCity', 'text', array('label' => "City"))
            ->add('activities', 'textarea', array('label' => "Activities and responsibilities"));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefined('company');
        $resolver->setDefaults(array(
          'data_class' => WorkExperience::class,
        ));
    }
}

课程管理员:

我只向您展示表单管理,因为我遇到了问题(如果您想知道我没有传递ID,因为连接的用户只能有一个课程)

<?php

namespace appBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use appBundle\Entity\Curriculum;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use appBundle\Form\Type\WorkExperienceType;
use appBundle\Form\Type\EducationCvType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Intl\Intl;
use Doctrine\Common\Collections\ArrayCollection;
use appBundle\Entity\WorkExperience;

class CurriculumController extends Controller
{
public function curriculumEditAction(Request $request)
    {
        if (!$this->getUser() || !$this->get('session')) {
            $response = $this->generateUrl('homepage');
            return new RedirectResponse($response);
        }

        $em = $this->getDoctrine()->getManager();

        $curriculum = $this->getUser()->getEmployee()->getCurriculum();

        $originalWorkExperience = new ArrayCollection();

        foreach ($curriculum->getWorkExperience() as $workExperience) {
            $originalWorkExperience->add($workExperience);
        }

        $originalEducation = new ArrayCollection();

        foreach ($curriculum->getEducationTraining() as $educationTraining) {
            $originalEducation->add($educationTraining);
        }

        $countries = Intl::getRegionBundle()->getCountryNames();

        $formCurriculum = $this->createFormBuilder($curriculum)
                ->add('workExperience', CollectionType::class, array(
                    'entry_type'   => WorkExperienceType::class,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'by_reference' => false,
                    'required' => false,
                    'type' => new WorkExperience(),
                    'entry_options'  => array(
                        'company' => $this->getUser()->getEmployee()->getCompany(),
                    ),
                ))
                ->add('save', 'submit', array('label' => 'Save changes', 'attr' => array('class' => 'btn btn-blue')))
                ->getForm();


        if ('POST' === $request->getMethod()) {

            $formCurriculum->bind($request);

            if ($formCurriculum->isValid()) {
                $curriculum = $formCurriculum->getData();

                try {

                    foreach ($curriculum->getWorkExperience() as $workExperience) {
                        $em->persist($workExperience);

                        $em->flush();
                    }

                    // remove the relationship between the tag and the Task
                    foreach ($originalWorkExperience as $workExperience) {
                        if (false === $curriculum->getWorkExperience()->contains($workExperience)) {
                            // remove the Task from the Tag
                            $workExperience->getCurriculum()->removeWorkExperience($workExperience);

                            // if you wanted to delete the Tag entirely, you can also do that
                            $em->remove($workExperience);
                        }
                    }

                    $em->persist($curriculum);
                    $em->flush();
                    $request->getSession()
                        ->getFlashBag()
                        ->add('success', 'edit_curriculum_ok')
                    ;
                } catch (Exception $e) {
                    $request->getSession()
                        ->getFlashBag()
                        ->add('error', 'edit_curriculum_ko')
                    ;
                }

                return $this->render('appBundle:Curriculum:curriculum_edit.html.twig', array('curriculum' => $curriculum, 'formCurriculum' => $formCurriculum->createView(),));
            }
        }

        return $this->render('appBundle:Curriculum:curriculum_edit.html.twig', array('curriculum' => $curriculum, 'formCurriculum' => $formCurriculum->createView(),));
    }
}

模板:

{% form_theme formCurriculum _self %}

{% block work_experience_widget %}
    <div class="form-group row">
        <div class="col-sm-6">
            <label for="">Nombre Empresa</label>
            {{ form_widget(form.employerName, {'attr': {'class': 'form-control'}}) }}
        </div>
        <div class="col-sm-5">
            <label for="">Categoría Profesional</label>

            {{ form_widget(form.jobProfile, {'attr': {'class': 'form-control'}}) }}
        </div>
        <div class="col-sm-1">
            <a href="#" class="btn btn-danger btn-xs text-center remove-tag"><i class="glyphicon glyphicon-remove"></i></a>
        </div>
    </div>
    <div class="form-group row">
        <div class="col-sm-3">
            <label for="">Fecha desde</label>
            <div class="input-group">
                {{ form_widget(form.fromDate) }}
                <span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
            </div>
        </div>
        <div class="col-sm-5">
            <label for="">Fecha hasta</label>
            <div class="input-group">
                <span class="input-group-addon">
                    {{ form_widget(form.ongoing) }}
                  </span>
                {{ form_widget(form.toDate) }}
                <span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
            </div>
        </div>
        <div class="col-sm-4">
            <label for="">Ciudad</label>
            {{ form_widget(form.employerCity, {'attr': {'class': 'form-control'}}) }}
        </div>
    </div>
    <div class="form-group">
        <label for="">Actividades y Responsabilidades</label>
        {{ form_widget(form.activities, {'attr': {'class': 'form-control'}}) }}
        {{ form_rest(form) }}
        {{ form_errors(form)}}
    </div>
    <hr />
{% endblock %}

{{ form_start(formCurriculum) }}

                        <div class="col-lg-8 col-xs-12">
                            <h3>Experiencia Laboral</h3>

                            <div class="workExperience" data-prototype="{{ form_widget(formCurriculum.workExperience.vars.prototype)|e('html_attr') }}">
                                {% for workExperience in formCurriculum.workExperience %}
                                    <div class="workExperienceUnit">
                                        {{ form(workExperience)}}
                                    </div>
                                {% endfor %}
                            </div>


                            <div class="form-group text-right">
                                {{ form_widget(formCurriculum.save, {'attr': {'class': 'btn btn-blue'}}) }}
                            </div>
                            <div class="clearfix"></div>
                        </div>
                    {{ form_rest(formCurriculum) }}
                    {{ form_end(formCurriculum) }}

表单集合的Ajax和js:

{% block javascripts %}
    {{ parent() }}    
    <script type="text/javascript">
        var $collectionHolder;

        // setup an "add a tag" link
        var $addWorkExperienceLink = $('<button type="button" class="btn btn-primary width100x100 add_workexperience_link"><span class="badge"><i class="glyphicon glyphicon-plus"></i></span> Agregar nueva experiencia laboral</button>');

        var $newLinkLi = $('<div class="form-group row"><div class="col-sm-12"></div></div>').append($addWorkExperienceLink);

        jQuery(document).ready(function() {

            $collectionHolder = $('div.workExperience');

            // add the "add a tag" anchor and li to the tags ul
            $collectionHolder.append($newLinkLi);

            // count the current form inputs we have (e.g. 2), use that as the new
            // index when inserting a new item (e.g. 2)


            $collectionHolder.data('index', $collectionHolder.find(':input').length);

            $addWorkExperienceLink.on('click', function(e) {
                // prevent the link from creating a "#" on the URL
                e.preventDefault();

                // add a new tag form (see next code block)
                addWorkExperienceForm($collectionHolder, $newLinkLi);
            });

        });

        function addWorkExperienceForm($collectionHolder, $newLinkLi) {
            // Get the data-prototype explained earlier
            var prototype = $collectionHolder.data('prototype');

            // get the new index
            var index = $collectionHolder.data('index');

            $collectionHolder.data('index', index + 1);

            // Replace '$$name$$' in the prototype's HTML to
            // instead be a number based on how many items we have
            var newForm = prototype.replace(/__name__/g, index);

            // Display the form in the page in an li, before the "Add a tag" link li
            var $newFormLi = $('<div class="workExperienceUnit"></div>').append(newForm);

            $newLinkLi.before($newFormLi);

            $('.remove-tag').click(function(e) {
                e.preventDefault();

                $(this).parent().parent().parent().remove();

                return false;
            });
        }

        // handle the removal, just for this example
        $('.remove-tag').click(function(e) {
            e.preventDefault();

            $(this).parent().parent().parent().remove();

            return false;
        });

    </script>
{% endblock %}

以下是截图,关键是它正确删除对象,唯一的问题是创建新对象时

enter image description here

1 个答案:

答案 0 :(得分:0)

有些事情似乎很奇怪。

  1. 使用$ form-&gt; handleRequest($ request);而不是bind()
  2. 你为什么使用&#39; type&#39; =&GT;新的WorkExperience()?我认为你不必在CollectionType字段中使用thois,因为实体的类型是在WorkexperienceTpe中定义的。
  3. 当您使用handlerequest函数发布表单时,您不需要从表单中检索所有数据,因为所有的工作经验都已自动添加到$ formCurriculum中