Новый элемент коллекции перезаписывает последний элемент, если он уже существует при обновлении записи - PullRequest
0 голосов
/ 25 октября 2019

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

Миссия:

    /**
     * @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;
    } 

Опция:

    /**
     * @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'
                )
            ))
        ;

Тип миссии:

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

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

В моей ветке:

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

Показ: Показать правку:

enter image description here

При добавлении нового:

enter image description here

После обновления:

enter image description here

Заранее благодарю за помощь

1 Ответ

0 голосов
/ 27 октября 2019

Наблюдаемые вами наблюдения согласуются со следующим ...

Когда ваша форма создается, она добавляет поля с именами

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][0][tagname]
mission_edit[options][0][prix]
mission_edit[options][1][tagname]
mission_edit[options][1][prix]

, а затем вы отбрасываете первый вариант, оставляя вас (включая исходные параметры!)

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]

, и так как php всегда будет перезаписыватьдубликаты имен var ... вы перезаписываете свой второй вариант (с индексом 1) новым параметром.

Вы должны проверить это, посмотрев на DOM. Если мое предположение верно, то вы «умно» пропустили определенную часть вашей формы в своих шаблонах веток, где создаются оригинальные элементы, которые, вероятно, вне <ul>, что приводит к index Счетчик ul должен быть 0, и соответственно перезаписывать элементы.

Честно говоря, я не люблю использовать определенную строку кода из примера Symfony - которую вы также используете - дляИменно по этой причине строка кода выглядит следующим образом:

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

, поскольку она использует неочевидный набор предположений, в частности: ваша форма имеет по крайней мере один тип входного полячто может быть и не так! (например, редактируемые + много js), и для вашего примера важно, чтобы все существующих потомков были помещены в держатель коллекции , чего вы не делаете. Чтобы быть справедливым, пример Symfony, где это показано, абсолютно работает, поскольку предположения там справедливы.

Вместо * я бы использовал (* означает удаление другой строки кода!)

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

, потому что он правильно устанавливает индекс на количество детей, которое должно быть.

...