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