Forms 使用@ORM\OneToMany(cascade={“persist”})持久化嵌入表单的集合

Forms 使用@ORM\OneToMany(cascade={“persist”})持久化嵌入表单的集合,forms,symfony,doctrine-orm,Forms,Symfony,Doctrine Orm,我试图在表单中嵌入表单集合。这个话题对我来说是新的。我已经创建了一个带有描述字段的简单任务表单,在任务表单内部,您可以通过单击“添加标记”链接添加和删除“n”个标记表单。标记字段只有一个字段“名称”。通过使用原则,我在任务和标记实体之间建立了一对多的双向关系,标记是拥有方。提交后,除Tag表中的task_id(引用)列外,所有数据都成功保存在数据库中。 当我在我的OneToMany元数据中使用cascade={“persist”}时,条令自动执行从任务对象到任何相关标记的persist操作。然

我试图在表单中嵌入表单集合。这个话题对我来说是新的。我已经创建了一个带有描述字段的简单任务表单,在任务表单内部,您可以通过单击“添加标记”链接添加和删除“n”个标记表单。标记字段只有一个字段“名称”。通过使用原则,我在任务和标记实体之间建立了一对多的双向关系,标记是拥有方。提交后,除Tag表中的task_id(引用)列外,所有数据都成功保存在数据库中。

当我在我的OneToMany元数据中使用
cascade={“persist”}
时,条令自动执行从任务对象到任何相关标记的persist操作。然而,在这种情况下,我的猜测是,条令首先坚持标记形式,然后坚持任务形式,因此任务的用户id值不会出现在相关标记的标记表中。我知道,
cascade={“persist”}
方法的解决方法是手动持久化添加的标记表单,但这会增加代码的大小和复杂性。我的问题是,如何使
cascade={“persist”}
方法工作?还有一个额外的问题,当我试图通过调用custom addTag()方法在TaskType.php中的“tags”CollectionType字段的选项数组中设置
'by_reference'=>false
来在Task类中添加新的标记对象时,在表单提交后,我会得到一个空白页,仅说明服务器错误,没有错误的细节。 以下是任务实体:

<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Task
 *
 * @ORM\Table(name="Task")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\TaskRepository")
 */
class Task
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * 
     * @var string
     *
     * @ORM\Column(name="Description", type="string")
     */
    protected $description;

    /**
     * @ORM\OneToMany(targetEntity="Tag", mappedBy="tasks",cascade={"persist"})
     */
    protected $tags;

    /**
     * @return ArrayCollection 
     */
    public function __construct()
    {
        $this->tags = new ArrayCollection();
    }


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

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

        return $this;
    }

    /**
     * Get tags
     *
     * @return Tags 
     */
    public function getTags()
    {
        return $this->tags;
    }

    /**
     * Set tags
     *
     * @param ArrayCollection $tags
     * @return Task
     */
    public function setTags($tags)
    {
        $this->tags = $tags;

        return $this;
    }

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

//    /**
//     * Add tag
//     * 
//     * @param Tag
//     * @return Task 
//     */
//    public function addTag(Tag $tag)
//{
//    $tag->addTask($this);
//
//    $this->tags->add($tag);
//    
//    return $this;
//}

//    /**
//     * Remove tags
//     *
//     * @param \AppBundle\Entity\Tag $tags
//     */
//    public function removeTag(\AppBundle\Entity\Tag $tags)
//    {
//        $this->tags->removeElement($tags);
//    }

   }

  • 我已经能够找到第二个问题的答案,即通过在TaskType.php中的“tags”CollectionType字段的选项数组中设置
    'by_reference'=>false,
    ,使用自定义加法器和移除器方法。由于将
    'by_reference'=>设置为false,
    ,因此不会发生严重错误,事实上,它工作得很好,并指示条令使用自定义addTag()、addTask()和removeTag()来添加和删除标记,而不是使用内部方法,例如
    $task->getTags()->add($tag)

  • 现在addTag()、addTask()和removeTag()方法也根据需要定义,但错误的实际原因仍然是
    cascade={“persist”}
    。在提交原则之后,首先持久化嵌入的标记对象,它们都与单个任务对象相关,然后持久化相关的任务对象。问题是,当doctrine保存标记对象时,任务对象尚未保存在数据库中,这意味着标记对象的用户id(引用)值尚未设置。此值只能在任务对象持久化后设置。因此addTag(),addTask()接收空输入,并因此给出服务器错误。问题仍然是,是否有一种方法可以以不同的或定制的方式进行级联

  • 我已经能够找到第二个问题的答案,即通过在TaskType.php中的“tags”CollectionType字段的选项数组中设置
    'by_reference'=>false,
    ,使用自定义加法器和移除器方法。由于将
    'by_reference'=>设置为false,
    ,因此不会发生严重错误,事实上,它工作得很好,并指示条令使用自定义addTag()、addTask()和removeTag()来添加和删除标记,而不是使用内部方法,例如
    $task->getTags()->add($tag)

  • 现在addTag()、addTask()和removeTag()方法也根据需要定义,但错误的实际原因仍然是
    cascade={“persist”}
    。在提交原则之后,首先持久化嵌入的标记对象,它们都与单个任务对象相关,然后持久化相关的任务对象。问题是,当doctrine保存标记对象时,任务对象尚未保存在数据库中,这意味着标记对象的用户id(引用)值尚未设置。此值只能在任务对象持久化后设置。因此addTag(),addTask()接收空输入,并因此给出服务器错误。问题仍然是,是否有一种方法可以以不同的或定制的方式进行级联


  • 为什么有些方法被注释掉了?addTag()、removeTag()和addTask()被注释掉了,因为我没有在TaskType.php中的“tags”CollectionType字段的选项数组中设置
    “by_reference”=>false、
    。因此,标签的添加和删除是通过调用内部方法来处理的,例如
    $task->getTags()->add($tag)
    。因为如果我将
    'by_reference'=>设置为false,
    它会给我带来严重的错误。你检查过你的日志了吗?您正在使用
    app\u dev.php
    ?是的,我正在使用
    app\u dev.php
    。检查我的日志以了解服务器错误。为什么有些方法被注释掉了?addTag()、removeTag()和addTask()被注释掉了,因为我没有在TaskType.php中的“tags”CollectionType字段的选项数组中设置
    “by_reference”=>false、
    。因此,标签的添加和删除是通过调用内部方法来处理的,例如
    $task->getTags()->add($tag)
    。因为如果我将
    'by_reference'=>设置为false,
    它会给我带来严重的错误。你检查过你的日志了吗?您正在使用
    app\u dev.php
    ?是的,我正在使用
    app\u dev.php
    。检查我的日志中的?服务器错误。
    <?php
    
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * Tag
     *
     * @ORM\Table(name="Tag")
     * @ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
     */
    class Tag {
    
        /**
         * @var int
         *
         * 
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         * @ORM\Column(name="id", type="integer")
         */
        public $id;
    
        /**
         * 
         * @var string
         *
         * @ORM\Column(name="Name", type="string")
         */
        public $name;
    
        /**
         * @ORM\ManyToOne(targetEntity="Task", inversedBy="tags", cascade={"persist"})
         * @ORM\JoinColumn(name="task_id", referencedColumnName="id")
         */
        protected $tasks;
    
        /**
         * Set name
         *
         * @param string $name
         * @return Tag
         */
        public function setName($name) {
            $this->name = $name;
    
            return $this;
        }
    
        /**
         * Get name
         *
         * @return string 
         */
        public function getName() {
            return $this->name;
        }
    
        /**
         * Get id
         *
         * @return integer 
         */
        public function getId() {
            return $this->id;
        }
    
        /**
         * Set tasks
         *
         * @param \AppBundle\Entity\Task $tasks
         * @return Tag
         */
        public function setTasks(\AppBundle\Entity\Task $tasks = null) {
            $this->tasks = $tasks;
    
            return $this;
        }
    
        /**
         * Get tasks
         *
         * @return \AppBundle\Entity\Task 
         */
        public function getTasks() {
            return $this->tasks;
        }
    
    //    /**
    //     * Add Task
    //     * 
    //     * @param Task
    //     * @return Tag 
    //     */
    //    public function addTask(Task $task) {
    //        if (!$this->tasks->contains($task)) {
    //            $this->tasks->add($task);
    //        }
    //      }
    
       }
    
    <?php
    
    namespace AppBundle\Form;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    
    class TagType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('name');
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => 'AppBundle\Entity\Tag',
            ));
        }
    }
    
    <?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;
    use Symfony\Component\Form\Extension\Core\Type\SubmitType;
    
    class TaskType extends AbstractType {
    
    
        public function buildForm(FormBuilderInterface $builder, array $options) {
            $builder->add('description');
    
            $builder->add('tags', CollectionType::class, array(
                'entry_type' => TagType::class,
                'allow_add' => true,
                'allow_delete' => true,
    //            'by_reference' => false,
            ))
            ->add('save', SubmitType::class, array('label' => 'Submit'));
        }
    
        public function configureOptions(OptionsResolver $resolver) {
            $resolver->setDefaults(array(
                'data_class' => 'AppBundle\Entity\Task',
            ));
        }
    
    }
    
    // setup an "add a tag" link
    var $addTagLink = $('<a href="#" class="add_tag_link">Add a tag</a>');
    var $newLinkLi = $('<li></li>').append($addTagLink);
    
    jQuery(document).ready(function() {
        // Get the ul that holds the collection of tags
       var $collectionHolder = $('ul.tags');
    
        // add a delete link to all of the existing tag form li elements
        $collectionHolder.find('li').each(function() {
            addTagFormDeleteLink($(this));
        });
    
        // 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);
    
        $addTagLink.on('click', function(e) {
            // prevent the link from creating a "#" on the URL
            e.preventDefault();
    
            // add a new tag form (see code block below)
            addTagForm($collectionHolder, $newLinkLi);
        });
    
    
    });
    
    function addTagForm($collectionHolder, $newLinkLi) {
        // Get the data-prototype explained earlier
        var prototype = $collectionHolder.data('prototype');
    
        // get the new index
        var index = $collectionHolder.data('index');
    
        // 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);
    
        // increase the index with one for the next item
        $collectionHolder.data('index', index + 1);
    
        // Display the form in the page in an li, before the "Add a tag" link li
        var $newFormLi = $('<li></li>').append(newForm);
    
        // also add a remove button, just for this example
        $newFormLi.append('<a href="#" class="remove-tag">x</a>');
    
        $newLinkLi.before($newFormLi);
    
        // handle the removal, just for this example
        $('.remove-tag').click(function(e) {
            e.preventDefault();
    
            $(this).parent().remove();
    
            return false;
        });
    
        // add a delete link to the new form
        addTagFormDeleteLink($newFormLi);
    }
    
    function addTagFormDeleteLink($tagFormLi) {
        var $removeFormA = $('<a href="#">delete this tag</a>');
        $tagFormLi.append($removeFormA);
    
        $removeFormA.on('click', function(e) {
            // prevent the link from creating a "#" on the URL
            e.preventDefault();
    
            // remove the li for the tag form
            $tagFormLi.remove();
        });
    }
    
    {% extends 'base.html.twig' %}
    
    {% block body %}
    
        <h3>Embedded Collection of Forms!</h3>
    
        {% javascripts '@AppBundle/Resources/public/js/jquery-2.2.3.min.js' %}
        <script src="{{ asset_url }}"></script>
        {% endjavascripts %}
    
        {% javascripts '@AppBundle/Resources/public/js/AddTagg.js' %}
        <script src="{{ asset_url }}"></script>
        {% endjavascripts %}
    
        {{ form_start(form) }}
        {# render the task's only field: description #}
        {{ form_row(form.description) }}
    
        <h3>Tags</h3>
        <ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}">
            {# iterate over each existing tag and render its only field: name #}
            {% for tag in form.tags %}
                <li>{{ form_row(tag.name) }}</li>
                {% endfor %}
        </ul>
        {{ form_end(form) }}
    {% endblock %}
    
    <?php
    
    namespace AppBundle\Controller;
    
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Request;
    use AppBundle\Entity\Task;
    use AppBundle\Entity\Tag;
    use AppBundle\Form\TaskType;
    use Doctrine\Common\Collections\ArrayCollection;
    
    class DefaultController extends Controller {
    
        /**
         * @Route("/", name="homepage")
         */
        public function indexAction(Request $request) {
            $task = new Task();
    
            $form = $this->createForm(TaskType::class, $task);
    
            $form->handleRequest($request);
    
            if ($form->isValid()) {
    
    
                $em = $this->getDoctrine()->getManager();
                $em->persist($task);
                $em->flush();
            }
    
            return $this->render('default/index.html.twig', array(
                        'form' => $form->createView(),
            ));
        }
    
        /**
         * @Route("/edit", name="editpage")
         */
        public function editAction($id, Request $request) {
            $em = $this->getDoctrine()->getManager();
            $task = $em->getRepository('AppBundle:Task')->find($id);
    
            if (!$task) {
                throw $this->createNotFoundException('No task found for id ' . $id);
            }
    
            $originalTags = new ArrayCollection();
    
            // Create an ArrayCollection of the current Tag objects in the database
            foreach ($task->getTags() as $tag) {
                $originalTags->add($tag);
            }
    
            $editForm = $this->createForm(TaskType::class, $task);
    
            $editForm->handleRequest($request);
    
            if ($editForm->isValid()) {
    
                // remove the relationship between the tag and the Task
                foreach ($originalTags as $tag) {
                    if (false === $task->getTags()->contains($tag)) {
                        // remove the Task from the Tag
                        $tag->getTasks()->removeElement($task);
    
                        // if it was a many-to-one relationship, remove the relationship like this
                        $tag->setTask(null);
    
                        $em->persist($tag);
    
                        // if you wanted to delete the Tag entirely, you can also do that
                        // $em->remove($tag);
                    }
                }
    
                $em->persist($task);
                $em->flush();
    
                // redirect back to some edit page
                return $this->redirectToRoute('task_edit', array('id' => $id));
            }
    
            // render some form template
            return $this->render('default/index.html.twig', array(
                        'form' => $editForm->createView(),
            ));
        }
    
    }