使用@ORM \ OneToMany保留嵌入表单的集合(cascade = {" persist"})

时间:2016-05-02 07:16:01

标签: forms symfony doctrine-orm

我正在尝试在表单中嵌入一组表单。这个话题对我来说是新的。我创建了一个带有描述字段的简单任务表单,从任务表单中您可以添加和删除' n'点击'添加标签'链接。标记字段只有一个字段' name'。使用原则我已经在任务和标签实体之间建立了一对多的双向关系,标签是拥有方。提交后,除Tag表中的task_id(引用)列之外的所有数据都将成功保存在数据库中。 enter image description here

由于我在OneToMany元数据中使用cascade={"persist"},因此该原则会自动执行从Task对象到任何相关标记的持久操作。但是在这种情况下,我的猜测是Doctrine首先保留Tag表单,然后是Task表单,因此Task的user_id值没有出现在相关Tag的Tag表中。我知道cascade={"persist"}方法的解决方法是手动保留添加的Tag表单,但这会增加我的代码的大小和复杂性。我的问题是,如何使cascade={"persist"}方法有效?另外一个额外的问题是,当我尝试通过调用自定义addTag()方法在Task类中添加新标记对象时,通过在'标记的选项数组中设置'by_reference' => false。 TaskType.php中的CollectionType字段,在表单提交后我得到一个空白页面,只说明服务器错误,没有错误的详细信息。 以下是任务实体:

<?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);
//    }

   }

以下是标签实体:

<?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);
//        }
//      }

   }

下面是Tag表单类:

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

}

以下是AddTagg.js:

// 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(),
        ));
    }

}

1 个答案:

答案 0 :(得分:0)

  1. 我已经能够找到第二个问题的答案,即。使用自定义加法器和移除方法,在选项数组中设置'by_reference' => false,&#39;标记&#39; TaskType.php中的CollectionType字段。由于设置'by_reference' => false,不会发生服务器错误,因此它完美地工作并指示教条使用自定义addTag(),addTask()和removeTag()来添加和删除标记而不是内部方法,例如。 $task->getTags()->add($tag)

  2. 现在addTag(),addTask()和removeTag()方法也被定义为必需,但错误的实际原因仍然是cascade={"persist"}。在提交原则之后,首先保留嵌入的Tag对象,这些对象都与单个Task对象相关,然后它会持久保存相关的Task对象。问题是当时doctrine持久化标记对象时,Task对象尚未保留在数据库中,这意味着Tag对象的user_id(引用)值尚未设置。只能在持久化Task对象后设置此值。所以addTag(),addTask()接收空输入,从而给出服务器错误。问题仍然是,是否有可能以不同或定制的方式进行级联?