【发布时间】:2017-05-18 22:24:20
【问题描述】:
这是我的第一个问题!
我想要关联两个实体:Product 和 Category。一个产品可能有多个类别,一个类别可能对应多个产品。我决定将这种关系实现为 3 类关联,具有中间 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 集合;这样,只有在表单中选择的那些类别才会保存到数据库中。
我的问题是,Doctrine 在插入新记录之前不会从数据库中删除记录。当没有为产品分配类别但我在尝试更新关联时收到Integrity constraint violation: 1062 Duplicate entry '1-1' for key 'PRIMARY' 时,这很好。我在 Doctrine 部分检查了 Symfony 调试面板,在 INSERT 之前从未执行过任何 DELETE 语句。
是否可以从实体中删除相关实体?如果没有,那为什么可以添加新的?提前致谢。
我的实体如下:
产品.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;
}
}
类别.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,
));
}
请注意,我使用了 categories 字段名称,该字段名称将填充来自 Category 实体的类别。该表单返回一个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() 方法。如我的表单类型代码所示,我添加了 Categories 类的 EntityType 字段,该字段映射到 categories 属性(实际上并不存在)。通过这种方式,我可以将现有类别显示为复选框,并正确检查产品的类别。
编辑 2:可能的解决方案
我最终听从了 Sam 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 和 Request 作为参数,只有在 POST 请求的情况下,才会删除与 Product 对应的所有 ProductCategory 条目。
服务注册如下:
文件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相关的人
标签: php mysql symfony doctrine symfony-3.2