New collection element overwrites the last element if it already exists when updating an entry

流过昼夜 提交于 2020-01-25 09:22:24

问题


so I have a Mission entity connected to an Option entity through a ManyToMany relationship. At the time of creating a new mission it is possible to add several options (this point is ok). Now when updating a mission, when I add a new option it overwrites the last option if it already exists otherwise it fits well. I want to be able to add as many options when modifying a mission without overwriting the last existing option. I post my code:

Mission entity :

    /**
     * @var ArrayCollection $options
     *
     * @ORM\ManyToMany(targetEntity="App\Entity\Option", inversedBy="missionOptions", cascade={"persist","remove"})
     * @ORM\JoinColumn(nullable=true)
     */
    protected $options;

    /**
     * Mission constructor.
     */
    public function __construct()
    {
        $this->options = new ArrayCollection();
    }

    public function addOption(Option $option)
    {
        $tag->addMission($this);
        $this->options->add($option);
        return $this;
    }


    public function addOptions($options)
    {
        foreach($options as $option){
            $this->addOption($option);
        }
        return $this;
    }

    public function removeOption(Option $option)
    {
        $this->options->removeElement($option);
        return $this;
    }

    public function removeOptions($options)
    {
        foreach($options as $option){
            $this->removeOption($option);
        }
        return $this;
    }

    public function setOptions(Option $option)
    {
        $this->options[] = $option;
        return $this;
    }

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

Option entity:

    /**
     * @ORM\ManyToMany(targetEntity="App\Entity\Mission", mappedBy="options", cascade={"persist","remove"})
     */
    private $missionOptions;

    /**
     * Option constructor.
     */
    public function __construct()
    {
        $this->missionOptions = new ArrayCollection();
    }

    public function setMission($missions)
    {
        $this->missionOptions = $missions;
    }

    public function getMission()
    {
        return $this->missionOptions;
    }

    public function addMission(Mission $mission)
    {
        if (!$this->missionOptions->contains($mission)) {
            $this->missionOptions->add($mission);
        }
        return $this;
    }

OptionType:

        $builder->add('tagname', TextType::class, array(
                'required' => true,
                'translation_domain' => 'FOSUserBundle',
                'label' => 'form.option_name',
                'attr' => array(
                    'class' => 'with-border',
                    'placeholder' => 'Nouvelle option'
                )
            ))
            ->add('prix', NumberType::class, array(
                'required' => true,
                'translation_domain' => 'FOSUserBundle',
                'label' => 'form.option_price',
                'attr' => array(
                    'min' => '2000',
                    'class' => 'with-border',
                    'placeholder'  => 'prix'
                )
            ))
        ;

MissionType:

          $builder->add('options', CollectionType::class, [
                'entry_type' => OptionType::class,
                'entry_options' => ['label' => false],
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'required' => false
            ])

In MissionController:

    public function updateAction(Request $request, $id)
    {
        $em = $this->getDoctrine()->getManager();
        $entity = $em->getRepository('App:Mission')->find($id);

        $originalOptions = new ArrayCollection();
        foreach ($entity->getOptions() as $option) {
            $originalOptions->add($option);
        }
        $editForm = $this->createEditForm($entity);
        $editForm->handleRequest($request);
        if ($editForm->isSubmitted() && $editForm->isValid()) {

            foreach ($originalOptions as $option) {
                if ($option->getTagname() == null || $option->getPrix() == null) {
                    $option->getMission()->removeElement($entity);
                    $em->persist($option);
                    $em->remove($option);
                }
            }

            $em->flush();
            return $this->redirectToRoute('dashboard_missions');
        }
     }

In my twig:

<ul class="options" data-prototype="{{ form_widget(edit_form.options.vars.prototype)|e('html_attr') }}"></ul>

<script>
        var $collectionHolder;

        var $addTagButton = $('<button type="button" class="button add_option_link"> Ajouter une option</button>');
        var $newLinkLi = $('<li></li>').append($addTagButton);

        jQuery(document).ready(function() {
            $collectionHolder = $('ul.options');
            $collectionHolder.append($newLinkLi);
            $collectionHolder.data('index', $collectionHolder.find(':input').length);
            $addTagButton.on('click', function(e) {
                addTagForm($collectionHolder, $newLinkLi);
            });
        });

        function addTagForm($collectionHolder, $newLinkLi) {
            var prototype = $collectionHolder.data('prototype');
            var index = $collectionHolder.data('index');
            var newForm = prototype;
            newForm = newForm.replace(/__name__/g, index);
            $collectionHolder.data('index', index + 1);
            var $newFormLi = $('<li></li>').append(newForm);
            $newLinkLi.before($newFormLi);
            addTagFormDeleteLink($newFormLi);
        }

        function addTagFormDeleteLink($tagFormLi) {
            var $removeFormButton = $('<button style="margin-left: 10px" type="button" class="button"></button>');
            $tagFormLi.append($removeFormButton);
            $removeFormButton.on('click', function(e) {
                $tagFormLi.remove();
            });
        }
    </script>

Screening: Show edit :

When add new:

After update:

Thank you in advance for all help


回答1:


The observations you describe are consistent with the following ...

When your form is created, it adds fields with the names

mission_edit[options][0][tagname]
mission_edit[options][0][prix]
mission_edit[options][1][tagname]
mission_edit[options][1][prix]
mission_edit[options][2][tagname]
mission_edit[options][2][prix]

and when you add two other option, it adds:

mission_edit[options][0][tagname]
mission_edit[options][0][prix]
mission_edit[options][1][tagname]
mission_edit[options][1][prix]

and then you scrap the first option leaving you with (including the original options!)

mission_edit[options][0][tagname]
mission_edit[options][0][prix]
mission_edit[options][1][tagname]
mission_edit[options][1][prix]
mission_edit[options][2][tagname]
mission_edit[options][2][prix]
mission_edit[options][1][tagname]
mission_edit[options][1][prix]

and since php will always overwrite duplicate var names ... you overwrite your original second option (with the index 1) with the new option.

You should verify by actually looking at the DOM. If my assumption is correct, than you "cleverly" omitted the specific part of your form in your twig templates, where the original elements are created, which is probably outside the <ul> which leads to the index counter on the ul to be 0, and overwriting the elements accordingly.

To be honest, I dislike using a specific line of code from the symfony example - which you're also using - for exactly that reason, the line of code is:

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

since it uses a non-obvious set of assumptions, particularly: your sub-form has at least one input type of field, which might not be the case! (e.g. editable + lots of js), and relevant for your example, that all existing children are put inside the collection holder, which you don't do. To be fair the symfony example where this is shown absolutely works, since the assumptions hold true there.

Instead* I would use (* meaning, removing the other line of code!)

<ul class="options" 
    data-prototype="{{ form_widget(edit_form.options.vars.prototype)|e('html_attr') }}"
    data-index="{{ edit_form.options.children|length }}"> {# <- this is the important part!! #}

    {# I would also add the existing children here, but there may be reasons not to! #}
</ul>

because it correctly sets the index to the number of children, which it is supposed to be.



来源:https://stackoverflow.com/questions/58556002/new-collection-element-overwrites-the-last-element-if-it-already-exists-when-upd

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!