Symfony 3.3:无法添加和删除集合表单类型相同实体的项

Symfony 3.3:无法添加和删除集合表单类型相同实体的项,symfony,doctrine-orm,symfony-forms,symfony-3.3,Symfony,Doctrine Orm,Symfony Forms,Symfony 3.3,我是Symfony的新手,所以我从官方教程开始,安装了Symfony framework 3.3.2,并完成了教程,同时根据我的具体需要定制实体、控制器、表单和视图 因此,基本上我有一个名为BasePreset的实体,数据库中已经有几行了,我终于创建了一个表单集合类型,它呈现了一个带有“add”和“remove”链接的可编辑BasePreset实体字段列表:第一个将新的空白字段添加到列表中,每个“删除”链接都会从DOM中删除相应的字段。一切都根据文件 因此,我成功地更新了现有字段(在重新加载表单

我是Symfony的新手,所以我从官方教程开始,安装了Symfony framework 3.3.2,并完成了教程,同时根据我的具体需要定制实体、控制器、表单和视图

因此,基本上我有一个名为BasePreset的实体,数据库中已经有几行了,我终于创建了一个表单集合类型,它呈现了一个带有“add”和“remove”链接的可编辑BasePreset实体字段列表:第一个将新的空白字段添加到列表中,每个“删除”链接都会从DOM中删除相应的字段。一切都根据文件

因此,我成功地更新了现有字段(在重新加载表单HTML和数据库后,我看到了正确的更改)

问题是,添加/删除不起作用。没有给出错误。签入Chrome开发工具:按预期发送参数

关于表单生成器,我使用了以下文档(当然还有大量的谷歌搜索):

现在,在本报告中指出:

否则,必须同时创建addTag()和removeTag()方法 即使by_引用为false,表单仍将使用setTag()。你会 在本文后面部分了解有关removeTag()方法的更多信息

目前,我没有任何引用的子实体,如示例中所述。我只希望能够编辑相同的普通实体,包括添加新项和删除现有项。也许我错了,但在我看来这只是一个微不足道的基本目标。我不明白如何正确地将“setBasePreset”和“removeBasePreset”方法添加到“BasePreset”实体本身

当然,我可以跳过使用FormBuilder,但我想利用它作为框架一部分的功能。任何建议,例如,可能指向一些相关的文件/教程,我错过了-将不胜感激

发布示例(一次添加一次删除):

base_presets[base_presets][5][name]:new_preset
base_presets[base_presets][5][description]:new, not really added
base_presets[base_presets][0][name]:ffffas44432df
base_presets[base_presets][0][description]:asdfffff2333
base_presets[base_presets][2][name]:ffffasdf2222
base_presets[base_presets][2][description]:asdff3fff2333
base_presets[base_presets][3][name]:yoyoshka
base_presets[base_presets][3][description]:nananaf
base_presets[base_presets][4][name]:123fffdsaasdf
base_presets[base_presets][4][description]:pop123
base_presets[_token]:H2QwRHdvZW1WAdc6VTONnspxvH1U-oC8rCEEprDdMCQ
<?php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class BasePresetsType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('base_presets', CollectionType::class, array(
            'entry_type' => BasePresetType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false // also tried 'true' but without any success
        ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null,
        ));
    }
}
<?php
namespace AppBundle\Form;

use AppBundle\Entity\BasePreset;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class BasePresetType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('name', TextType::class)
            ->add('description', TextareaType::class);
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults([
            'data_class' => BasePreset::class,
        ]);
    }
}
<?php
// ...
    /**
     * @Route("/admin/list_base_presets", name="list_base_presets")
     */
    public function listBasePresetsAction(Request $request, EntityManagerInterface $em, LoggerInterface $logger) {
        $base_presets = $em->getRepository("AppBundle\Entity\BasePreset")->findAll();

        $form = $this->createForm(BasePresetsType::class, array('base_presets' => $base_presets));

        $form->handleRequest($request);
        $current_route = $request->get('_route');

        if ($form->isSubmitted() && $form->isValid()) {
            foreach ($base_presets as $base_preset) {
                $em->persist($base_preset);
            }
            $em->flush();
            // ... do other work - like sending an email, etc..
            // maybe set a 'flash' success message for the user
            return $this->redirectToRoute($current_route);
        }
        return $this->render('manage_params/list_base_presets.html.twig', array(
            'form' => $form->createView()
        ));
    }
{% extends "base.html.twig" %}

{% block body %}
    {{ form_start(form) }}
    {{ form_label(form.base_presets) }}
    {{ form_errors(form.base_presets) }}
    <ul class="base-presets" data-prototype="{{ form_widget(form.base_presets.vars.prototype)|e('html_attr') }}">
        {% for base_preset in form.base_presets %}
            <li class="base-preset-item">
                {{ form_errors(base_preset) }}
                {{ form_widget(base_preset) }}
            </li>
        {% endfor %}
        <li class="form-submit">
            <input type="submit" value="Submit" class="btn btn-default pull-right" />
        </li>
    </ul>
    {{ form_end(form) }}
{% endblock %}
{% block javascripts %}
    <script>
        var $collection_holder;
        // Setup an "add a base preset" link
        var $add_base_preset_link = $('<a href="#" class="add_base_preset_link">Add a base preset</a>');
        var $new_link_li = $('<li></li>').append($add_base_preset_link);
        $(document).ready(function () {
            // Get the ul that holds the collection of base presets
            $collection_holder = $('ul.base-presets');
            // add the "add a base preset" anchor and li to the tags ul
            $collection_holder.prepend($new_link_li);
            // count the current form inputs we have, use that as the new index when inserting a new item
            $collection_holder.data('index', $collection_holder.find('li.base-preset-item').length);

            $add_base_preset_link.on('click', function (e) {
                e.preventDefault();
                addBasePresetForm($collection_holder, $new_link_li);
            });

            // add a delete link to all of the existing base presets form li elements
            $collection_holder.find('li.base-preset-item').each(function () {
                addBasePresetFormDeleteLink($(this));
            });
        });

        function addBasePresetForm($collection_holder, $new_link_li) {
            // Get the data-prototype
            var prototype = $collection_holder.data('prototype');
            // Get the new index
            var index = $collection_holder.data('index');
            // Replace '__name__' in the prototype's HTML to instead be a number based on how many items we have
            var new_form = prototype.replace(/__name__/g, index);
            // increment the index for the next item
            $collection_holder.data('index', index + 1);
            // Display the form in the page in an li, before the "Add a base preset" link li
            var $new_form_li = $('<li class="base-preset-item"></li>').append(new_form);
            $new_link_li.after($new_form_li);
            addBasePresetFormDeleteLink($new_form_li);
        }

        function addBasePresetFormDeleteLink($base_preset_form_li) {
            var $remove_form_a = $('<a href="#">Delete this base preset</a>');
            $base_preset_form_li.append($remove_form_a);
            $remove_form_a.on('click', function (e) {
                e.preventDefault();
                $base_preset_form_li.remove();
            })
        }
    </script>
{% endblock %}
<?php
namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;


/**
 * Class BasePreset
 * @package AppBundle\Entity
 *
 * @ORM\Entity
 * @ORM\Table(name="base_presets")
 * @UniqueEntity(fields="name", message="There should be only one (unique) base preset")
 */
class BasePreset {
    /**
     * @ORM\OneToMany(targetEntity="BaseParamsGroup", mappedBy="base_preset")
     */
    private $base_params_groups;

    /**
     * @var int
     *
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=100)
     */
    private $name;

    /**
     * @var string
     *
     * @Assert\NotBlank()
     * @ORM\Column(type="string", length=4000)
     */
    private $description;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime")
     */
    private $created;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime", columnDefinition="TIMESTAMP on update CURRENT_TIMESTAMP")
     */
    private $updated;

    public function __construct() {
        $this->base_params_groups = new ArrayCollection();
        $this->created = new \DateTime();
        $this->updated = new \DateTime();
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return BasePreset
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * Set description
     *
     * @param string $description
     *
     * @return BasePreset
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

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

    /**
     * Set created
     *
     * @param \DateTime $created
     *
     * @return BasePreset
     */
    public function setCreated($created)
    {
        $this->created = $created;

        return $this;
    }

    /**
     * Get created
     *
     * @return \DateTime
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * Set updated
     *
     * @param \DateTime $updated
     *
     * @return BasePreset
     */
    public function setUpdated($updated)
    {
        $this->updated = $updated;

        return $this;
    }

    /**
     * Get updated
     *
     * @return \DateTime
     */
    public function getUpdated()
    {
        return $this->updated;
    }

    /**
     * Add baseParamsGroup
     *
     * @param \AppBundle\Entity\BaseParamsGroup $baseParamsGroup
     *
     * @return BasePreset
     */
    public function addBaseParamsGroup(\AppBundle\Entity\BaseParamsGroup $baseParamsGroup)
    {
        $this->base_params_groups[] = $baseParamsGroup;

        return $this;
    }

    /**
     * Remove baseParamsGroup
     *
     * @param \AppBundle\Entity\BaseParamsGroup $baseParamsGroup
     */
    public function removeBaseParamsGroup(\AppBundle\Entity\BaseParamsGroup $baseParamsGroup)
    {
        $this->base_params_groups->removeElement($baseParamsGroup);
    }

    /**
     * Get baseParamsGroups
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getBaseParamsGroups()
    {
        return $this->base_params_groups;
    }
}
基本预设类型:

base_presets[base_presets][5][name]:new_preset
base_presets[base_presets][5][description]:new, not really added
base_presets[base_presets][0][name]:ffffas44432df
base_presets[base_presets][0][description]:asdfffff2333
base_presets[base_presets][2][name]:ffffasdf2222
base_presets[base_presets][2][description]:asdff3fff2333
base_presets[base_presets][3][name]:yoyoshka
base_presets[base_presets][3][description]:nananaf
base_presets[base_presets][4][name]:123fffdsaasdf
base_presets[base_presets][4][description]:pop123
base_presets[_token]:H2QwRHdvZW1WAdc6VTONnspxvH1U-oC8rCEEprDdMCQ
<?php
namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class BasePresetsType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('base_presets', CollectionType::class, array(
            'entry_type' => BasePresetType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false // also tried 'true' but without any success
        ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null,
        ));
    }
}
<?php
namespace AppBundle\Form;

use AppBundle\Entity\BasePreset;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class BasePresetType extends AbstractType {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder
            ->add('name', TextType::class)
            ->add('description', TextareaType::class);
    }

    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults([
            'data_class' => BasePreset::class,
        ]);
    }
}
<?php
// ...
    /**
     * @Route("/admin/list_base_presets", name="list_base_presets")
     */
    public function listBasePresetsAction(Request $request, EntityManagerInterface $em, LoggerInterface $logger) {
        $base_presets = $em->getRepository("AppBundle\Entity\BasePreset")->findAll();

        $form = $this->createForm(BasePresetsType::class, array('base_presets' => $base_presets));

        $form->handleRequest($request);
        $current_route = $request->get('_route');

        if ($form->isSubmitted() && $form->isValid()) {
            foreach ($base_presets as $base_preset) {
                $em->persist($base_preset);
            }
            $em->flush();
            // ... do other work - like sending an email, etc..
            // maybe set a 'flash' success message for the user
            return $this->redirectToRoute($current_route);
        }
        return $this->render('manage_params/list_base_presets.html.twig', array(
            'form' => $form->createView()
        ));
    }
{% extends "base.html.twig" %}

{% block body %}
    {{ form_start(form) }}
    {{ form_label(form.base_presets) }}
    {{ form_errors(form.base_presets) }}
    <ul class="base-presets" data-prototype="{{ form_widget(form.base_presets.vars.prototype)|e('html_attr') }}">
        {% for base_preset in form.base_presets %}
            <li class="base-preset-item">
                {{ form_errors(base_preset) }}
                {{ form_widget(base_preset) }}
            </li>
        {% endfor %}
        <li class="form-submit">
            <input type="submit" value="Submit" class="btn btn-default pull-right" />
        </li>
    </ul>
    {{ form_end(form) }}
{% endblock %}
{% block javascripts %}
    <script>
        var $collection_holder;
        // Setup an "add a base preset" link
        var $add_base_preset_link = $('<a href="#" class="add_base_preset_link">Add a base preset</a>');
        var $new_link_li = $('<li></li>').append($add_base_preset_link);
        $(document).ready(function () {
            // Get the ul that holds the collection of base presets
            $collection_holder = $('ul.base-presets');
            // add the "add a base preset" anchor and li to the tags ul
            $collection_holder.prepend($new_link_li);
            // count the current form inputs we have, use that as the new index when inserting a new item
            $collection_holder.data('index', $collection_holder.find('li.base-preset-item').length);

            $add_base_preset_link.on('click', function (e) {
                e.preventDefault();
                addBasePresetForm($collection_holder, $new_link_li);
            });

            // add a delete link to all of the existing base presets form li elements
            $collection_holder.find('li.base-preset-item').each(function () {
                addBasePresetFormDeleteLink($(this));
            });
        });

        function addBasePresetForm($collection_holder, $new_link_li) {
            // Get the data-prototype
            var prototype = $collection_holder.data('prototype');
            // Get the new index
            var index = $collection_holder.data('index');
            // Replace '__name__' in the prototype's HTML to instead be a number based on how many items we have
            var new_form = prototype.replace(/__name__/g, index);
            // increment the index for the next item
            $collection_holder.data('index', index + 1);
            // Display the form in the page in an li, before the "Add a base preset" link li
            var $new_form_li = $('<li class="base-preset-item"></li>').append(new_form);
            $new_link_li.after($new_form_li);
            addBasePresetFormDeleteLink($new_form_li);
        }

        function addBasePresetFormDeleteLink($base_preset_form_li) {
            var $remove_form_a = $('<a href="#">Delete this base preset</a>');
            $base_preset_form_li.append($remove_form_a);
            $remove_form_a.on('click', function (e) {
                e.preventDefault();
                $base_preset_form_li.remove();
            })
        }
    </script>
{% endblock %}
<?php
namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;


/**
 * Class BasePreset
 * @package AppBundle\Entity
 *
 * @ORM\Entity
 * @ORM\Table(name="base_presets")
 * @UniqueEntity(fields="name", message="There should be only one (unique) base preset")
 */
class BasePreset {
    /**
     * @ORM\OneToMany(targetEntity="BaseParamsGroup", mappedBy="base_preset")
     */
    private $base_params_groups;

    /**
     * @var int
     *
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=100)
     */
    private $name;

    /**
     * @var string
     *
     * @Assert\NotBlank()
     * @ORM\Column(type="string", length=4000)
     */
    private $description;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime")
     */
    private $created;

    /**
     * @var \DateTime
     *
     * @ORM\Column(type="datetime", columnDefinition="TIMESTAMP on update CURRENT_TIMESTAMP")
     */
    private $updated;

    public function __construct() {
        $this->base_params_groups = new ArrayCollection();
        $this->created = new \DateTime();
        $this->updated = new \DateTime();
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return BasePreset
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * Set description
     *
     * @param string $description
     *
     * @return BasePreset
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

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

    /**
     * Set created
     *
     * @param \DateTime $created
     *
     * @return BasePreset
     */
    public function setCreated($created)
    {
        $this->created = $created;

        return $this;
    }

    /**
     * Get created
     *
     * @return \DateTime
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * Set updated
     *
     * @param \DateTime $updated
     *
     * @return BasePreset
     */
    public function setUpdated($updated)
    {
        $this->updated = $updated;

        return $this;
    }

    /**
     * Get updated
     *
     * @return \DateTime
     */
    public function getUpdated()
    {
        return $this->updated;
    }

    /**
     * Add baseParamsGroup
     *
     * @param \AppBundle\Entity\BaseParamsGroup $baseParamsGroup
     *
     * @return BasePreset
     */
    public function addBaseParamsGroup(\AppBundle\Entity\BaseParamsGroup $baseParamsGroup)
    {
        $this->base_params_groups[] = $baseParamsGroup;

        return $this;
    }

    /**
     * Remove baseParamsGroup
     *
     * @param \AppBundle\Entity\BaseParamsGroup $baseParamsGroup
     */
    public function removeBaseParamsGroup(\AppBundle\Entity\BaseParamsGroup $baseParamsGroup)
    {
        $this->base_params_groups->removeElement($baseParamsGroup);
    }

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

由于使用的表单没有数据类,因此需要直接从表单对象访问提交的数据:

foreach ($form->get('base_presets')->getData() as $base_preset) {
    $em->persist($base_preset);
}
更新

这与管理现有的和持续的新项目有关。如果需要删除实体,可以将从DB加载的实体与提交的实体进行比较,然后删除筛选的实体。

它适用于添加新行,但不适用于删除现有行(仍然没有错误,只是没有更改)。这是可以解决的吗?也许我应该创建一些收集类,将其作为数据类放入收集表单类型?@a1111exe您可以将
$base\u预设的ID与提交的ID进行比较,然后调用
$em->remove($notSubmittedEntity)在每一个不同的上。我显然可以,而且可能会这样做。我很好奇这是不是唯一的办法。例如,对于官方示例中的子实体,不需要这样做。在我看来,这个解决方案就像一个微妙的黑客,可以缓解表单系统缺乏灵活性的问题。不管怎样,我必须继续前进,所以这对我来说是可行的。你能把这个添加到你的答案中吗?我会把它标记为接受?太好了!非常感谢!:)感谢你们所有人。。。但我正在处理同一个问题,但有一点不同。我有一个映射:
操作
操作入口
产品
之间的映射,表单上的集合用于注册操作(购买和销售)。保存工作很好,当我删除条目或添加条目时,更新很好。但是如果我删除然后添加一个新条目,则
OperationEntry::product
为空,重定向到
sale\u show
会显示“找不到空的指定”(即
product::designation
通过路径
entity.OperationEntry[INDEX].product.designation