这是我第一个问题!使用Doctrine删除与Symfony 3的3实体(一对多对一)关联
我有两个我想要关联的实体:Product
和Category
。一个产品可能有多个类别,而一个类别可能与许多产品相对应。我已决定将这种关系作为一个三级关联实现,具有中间ProductCategory
实体,如下图所示。这使我可以灵活地为将来的协会添加属性。
Representation of my tree-class association
我想现有的类别分配给现有的产品。 我想从实体本身建立关系。我可以在Product
实体中使用接收实体数组Category
的setter方法执行此操作,并为每个传递的类别创建一个新的ProductCategory
实体。其过程如下:
//Product.php
/**
* @param \Doctrine\Common\Collections\ArrayCollection $categories
* @return \TestBundle\Entity\Product
*/
public function setCategories($categories) {
$productCategoryReplacement = new \Doctrine\Common\Collections\ArrayCollection();
foreach ($categories as $category) {
$newProductCategory = new ProductCategory();
$newProductCategory->setProduct($this);
$newProductCategory->setCategory($category);
$productCategoryReplacement[] = $newProductCategory;
}
$this->productCategory = $productCategoryReplacement;
return $this;
}
请注意,我添加新的前清除ProductCategory
集合;通过这种方式,只有在表单中选择的类别才会保存到数据库中。
我的问题是,这种理论不插入新的之前删除数据库中的记录。没有类别分配给产品时没问题,但在尝试更新关联时收到Integrity constraint violation: 1062 Duplicate entry '1-1' for key 'PRIMARY'
。我已经在Doctrine部分检查了Symfony调试面板,并且在INSERT之前没有执行DELETE语句。
是否可以删除实体中的相关实体?如果不是,那么为什么可以添加新的?提前致谢。
我的实体如下:
Product.php:
namespace TestBundle\Entity;
/**
* @ORM\Table(name="product")
* @ORM\Entity(repositoryClass="TestBundle\Repository\ProductRepository")
*/
class Product {
/**
* @var int
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
* @ORM\OneToMany(targetEntity="ProductCategory", mappedBy="product", cascade={"persist"})
*/
private $productCategory;
/**
* Constructor
*/
public function __construct() {
$this->productCategory = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @param \TestBundle\Entity\ProductCategory $productCategory
* @return Product
*/
public function addProductCategory(\TestBundle\Entity\ProductCategory $productCategory) {
$this->productCategory[] = $productCategory;
return $this;
}
/**
* @param \TestBundle\Entity\ProductCategory $productCategory
*/
public function removeProductCategory(\TestBundle\Entity\ProductCategory $productCategory) {
$this->productCategory->removeElement($productCategory);
}
/**
* @return \Doctrine\Common\Collections\Collection
*/
public function getProductCategory() {
return $this->productCategory;
}
/**
* @param \Doctrine\Common\Collections\ArrayCollection $categories
* @return \TestBundle\Entity\Product
*/
public function setCategories($categories) {
$productCategoryReplacement = new \Doctrine\Common\Collections\ArrayCollection();
foreach ($categories as $category) {
$newProductCategory = new ProductCategory();
$newProductCategory->setProduct($this);
$newProductCategory->setCategory($category);
$productCategoryReplacement[] = $newProductCategory;
}
$this->productCategory = $productCategoryReplacement;
return $this;
}
/**
* @return \Doctrine\Common\Collections\ArrayCollection
*/
public function getCategories() {
$categories = new \Doctrine\Common\Collections\ArrayCollection();
foreach ($this->getProductCategory() as $pc) {
$categories[] = $pc->getCategory();
}
return $categories;
}
}
Category.php:
namespace TestBundle\Entity;
/**
* @ORM\Table(name="category")
* @ORM\Entity(repositoryClass="TestBundle\Repository\CategoryRepository")
*/
class Category {
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
* @ORM\OneToMany(targetEntity="ProductCategory", mappedBy="category", cascade={"persist"})
*/
private $productCategory;
}
ProductCategory.php
namespace TestBundle\Entity;
/**
* @ORM\Table(name="product_category")
* @ORM\Entity(repositoryClass="TestBundle\Repository\ProductCategoryRepository")
*/
class ProductCategory {
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Product", inversedBy="productCategory")
* @ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Category", inversedBy="productCategory")
* @ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
}
产生
我Product
形式如下:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name')
->add('categories', EntityType::class, array(
'class' => 'TestBundle:Category',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
));
}
请注意,我用的是将与Category
实体采取类别进行填充的categories
字段名。该表格返回Category
对象的数组,用于在Product.php
的setCategories()
方法中生成ProductCategory
实体。
/**
* @param \Doctrine\Common\Collections\ArrayCollection $categories
* @return \TestBundle\Entity\Product
*/
public function setCategories($categories) {
$productCategoryReplacement = new \Doctrine\Common\Collections\ArrayCollection();
foreach ($categories as $category) {
$newProductCategory = new ProductCategory();
$newProductCategory->setProduct($this);
$newProductCategory->setCategory($category);
$productCategoryReplacement[] = $newProductCategory;
}
$this->productCategory = $productCategoryReplacement;
return $this;
}
编辑1:
我没有在Product
一个categories
场,我只有一个getCategories()
和setCategories()
方法。如我的表单类型代码所示,我添加了EntityType
类Categories
的字段,该字段映射到categories
属性(实际上不存在)。通过这种方式,我可以将现有类别显示为复选框,并正确检查产品的类别。
编辑2:可能的解决方案
我结束了以下萨姆Jenses的建议。我创建了一个服务,如下所示:
文件:src/TestBundle/Service/CategoryCleaner.php
namespace TestBundle\Service;
use Doctrine\ORM\EntityManagerInterface;
use TestBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Request;
class CategoryCleaner {
/**
*
* @var EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function cleanCategories(Product $product, Request $request) {
if ($this->em == null) {
throw new Exception('Entity manager parameter must not be null');
}
if ($request == null) {
throw new Exception('Request parameter must not be null');
}
if ($request->getMethod() == 'POST') {
$categories = $this->em->getRepository('TestBundle:ProductCategory')->findByProduct($product);
foreach ($categories as $category) {
$this->em->remove($category);
}
$this->em->flush();
}
}
}
在cleanCategories
方法,它接收当前产品和请求作为参数,其对应于Product
的ProductCategory
所有条目被删除,仅在壳体的POST请求。
服务注册如下:
文件app/config/services.yml
services:
app.category_cleaner:
class: TestBundle\Service\CategoryCleaner
arguments: ['@doctrine.orm.entity_manager']
服务必须从控制器以前handleRequest($request)
被调用,也就是说,添加新类别前。如果不是,我们会得到一个重复的输入异常。从文件
编辑方法TestBundle/Controller/ProductController.php
public function editAction(Request $request, Product $product) {
$deleteForm = $this->createDeleteForm($product);
$editForm = $this->createForm('TestBundle\Form\ProductType', $product);
$this->container->get('app.category_cleaner')->cleanCategories($product, $request);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('product_edit', array('id' => $product->getId()));
}
return $this->render('product/edit.html.twig', array(
'product' => $product,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
请验证我的做法。
是否需要删除setCategories中的所有ProductCategory? –
@AlessandroMinoccheri不是每一个'ProductCategory',只有那些与'Product'有关的人 –