我正在尝试在表单中嵌入一组表单。这个话题对我来说是新的。我创建了一个带有描述字段的简单任务表单,从任务表单中您可以添加和删除' n'点击'添加标签'链接。标记字段只有一个字段' name'。使用原则我已经在任务和标签实体之间建立了一对多的双向关系,标签是拥有方。提交后,除Tag表中的task_id(引用)列之外的所有数据都将成功保存在数据库中。
由于我在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(),
));
}
}
答案 0 :(得分:0)
我已经能够找到第二个问题的答案,即。使用自定义加法器和移除方法,在选项数组中设置'by_reference' => false,
&#39;标记&#39; TaskType.php中的CollectionType字段。由于设置'by_reference' => false,
不会发生服务器错误,因此它完美地工作并指示教条使用自定义addTag(),addTask()和removeTag()来添加和删除标记而不是内部方法,例如。 $task->getTags()->add($tag)
。
现在addTag(),addTask()和removeTag()方法也被定义为必需,但错误的实际原因仍然是cascade={"persist"}
。在提交原则之后,首先保留嵌入的Tag对象,这些对象都与单个Task对象相关,然后它会持久保存相关的Task对象。问题是当时doctrine持久化标记对象时,Task对象尚未保留在数据库中,这意味着Tag对象的user_id(引用)值尚未设置。只能在持久化Task对象后设置此值。所以addTag(),addTask()接收空输入,从而给出服务器错误。问题仍然是,是否有可能以不同或定制的方式进行级联?