2017-05-18 91 views
0

这是我第一个问题!使用Doctrine删除与Symfony 3的3实体(一对多对一)关联

我有两个我想要关联的实体:ProductCategory。一个产品可能有多个类别,而一个类别可能与许多产品相对应。我已决定将这种关系作为一个三级关联实现,具有中间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.phpsetCategories()方法中生成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()方法。如我的表单类型代码所示,我添加了EntityTypeCategories的字段,该字段映射到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方法,它接收当前产品和请求作为参数,其对应于ProductProductCategory所有条目被删除,仅在壳体的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(), 
    )); 

请验证我的做法。

+0

是否需要删除setCategories中的所有ProductCategory? –

+0

@AlessandroMinoccheri不是每一个'ProductCategory',只有那些与'Product'有关的人 –

回答

0

创建中间的服务,您也可以使用学说删除现有的实体

+0

我遵循你的方法,它的工作! –

+0

我编辑了我的问题以显示我如何实施服务 –

0

我想,你有你的实体内部的一些方法,如:

addCategory 
removeCategory 
getCategory 

public function __construct() 
{ 
    $this->categories = new \Doctrine\Common\Collections\ArrayCollection(); 
} 

所以在你的功能,你可以做:

public function setCategories($categories) { 
    $productCategoryReplacement = new \Doctrine\Common\Collections\ArrayCollection(); 

    foreach ($this->categories as $category) { 
     $this->removeCategory($category); 
    } 

    foreach ($categories as $category) { 
     $newProductCategory = new ProductCategory(); 
     $newProductCategory->setProduct($this); 
     $newProductCategory->setCategory($category); 
     $productCategoryReplacement[] = $newProductCategory; 
    } 
    $this->productCategory = $productCategoryReplacement; 

    return $this; 
} 
+0

我无法删除类别,但Doctrine不会将其保留。 –

+0

我编辑了我的问题来澄清 –

相关问题