如何在Symfony中设置Doctrine的类继承?

4
我的问题是,我在理解Doctrine的类继承中的DiscriminatorColumn和DiscriminatorMap方面遇到了困难。
我有一个产品实体,被认为是父类/表。
有几个子实体继承了产品实体。(models, parts, and options)
我觉得我应该能够使用主键来链接两个表...但是如何在DiscriminatorColumn中实现呢?
这里是我想要发生的事情的一般想法...
从数据库中获取所有模型对象,同时继承产品父实体。
SELECT object 
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id

或者...在继承产品父实体的同时从数据库中获取所有部件对象
SELECT object 
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id

理想情况下,我希望使用Doctrine而不是一些自定义SQL完成此操作。

我是否需要为父表设置一个“类型”列,以便每行定义它是零件、模型还是选项?

Doctrine继承文档


我认为你可能误解了继承。对我来说,无论是部件、模型还是选项都不像是产品。看一下关系。我猜一个产品由一个或多个部分组成,并且可能有不同的型号和特定的选项。 - Cerad
模型、零件和选项都是可以单独销售的产品。它们共享超过二十个属性,如尺寸、成本等。 - payling
在这种情况下,请遵循文档。你不需要担心查询。ORM会处理这些,并根据你的映射提供适当的实体类型。如果你对Doctrine还不熟悉,那么就从简单的东西开始,直到你熟悉它的设计为止。 - Cerad
2个回答

4

如果您想在Doctrine中使用DiscriminatorMap,那么应该使用Doctrine而不是SQL。

基本设置如下:

/**
 * @ORM\Table(name="product")
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ProductRepository")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="productType", type="string")
 * @ORM\DiscriminatorMap({
 *   "Product" = "Product",
 *   "Model" = "Model",
 *   "Part" = "Part",
 *   "Option" = "Option",
 * })
 */
class Product
{
...
}

MyApp\ProductBundle\Entity\Model

/**
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ModelRepository")
 */
class Model extends Product 
{
}

MyApp\ProductBundle\Entity\Part

/**
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\PartRepository")
 */
class Part extends Product 
{
}

我的应用程序\ProductBundle\Entity\选项

/**
 * @ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\OptionRepository")
 */
class Option extends Product 
{
}

如果您需要在控制器中获取所有产品,则可以使用以下方法:
 $em = $this->getDoctrine()->getManager(); 
 $repo = $em->getRepository("MyAppProductBundle:Product");
 $products = $repo->findAll();

如果您需要选择所有模型,只需设置正确的存储库。
 $repo = $em->getRepository("MyAppProductBundle:Model");
 $models = $repo->findAll();

4
好的,我会尽可能简单地解释。
我们从DiscriminatorColumn开始。
Discriminator column,如其名,是您数据库中的一列。它用于存储一个关键字,以帮助识别您当前查询的对象类型,基于您的DiscriminatorMap配置。
DiscriminatorMap是映射每个关键字到实体的方式。 例如,您有以下内容:
- Product [parent] - Model [parent的子类] - Part [parent的子类] - Option [parent的子类]
那么,您的discriminator map应该像这样:
@DiscriminatorMap({
    "model" = "AppBundle\Entity\Model", 
    "Part" = "AppBundle\Entity\Part",
    "Option" = "AppBundle\Entity\Option"
})

始终注意您映射中的最后一个定义。最后一行必须没有逗号!
至于InheritanceType,我建议您使用@InheritanceType(“JOINED”),因为这将让您为每个子类拥有单独的表。
每个子类都必须扩展您的Product实体类,这显然是父类。由于继承映射,每个子类都不得定义$id属性。
然后按特定类型查询记录需要使用以下查询:
"SELECT product FROM AppBundle\Entity\Product product WHERE product INSTANCE OF AppBundle\Entity\Part"

查询将仅搜索映射到此实体的记录。
如果您有任何问题,请不要犹豫问。
编辑为新评论
-----------------------
稍微解释一下。您不需要在实体映射中创建任何额外的属性/列。一旦添加了这个注释@DiscriminatorColumn(name =“discr”,type =“string”),Doctrine就会自动为您创建该列。此示例中的列将被命名为discr,类型为VARCHAR。
引用:
我仍然不明白用于连接表的内容。Doctrine如何知道链接产品和模型之间的id?
关于这部分。如果使用@InheritanceType(“JOINED”),这意味着您的GeneratedValue ID将设置在主实体Product中。然后,扩展Product的每个子实体都会自动获得相同的ID,这就是为什么您不需要在子实体中指定$id属性的原因。
最后,您如何检查您当前正在查看哪种实体类型。例如,考虑以下场景,您的每个子实体都扩展Product,并且我们将执行虚拟搜索以获取记录:
$product = $entityManager->find('AppBundle:Product', 1); // example

现在,如果你实际上执行var_dump($product),你会发现一些有趣的事情。该对象将是ModelPartOption的一个实例,因为这些实体都在你的鉴别器映射中定义,并且Doctrine会根据此自动映射你的记录。 后来,在像这样的情况下这可能会派上用场:
if( $product instanceof \AppBundle\Entity\Part ) {
    // do something only if that record belongs to part.
}

DiscriminatorColumn是关键,这种情况下它将是model、part或option的值,对吗?因此,我需要在产品表中创建一个包含这3个值之一的列。我仍然不明白用什么来连接这些表。如果我删除$id属性,Doctrine如何知道链接产品和模型实体之间的id呢?难道我还需要做类似于where product.product_id = model.product_id这样的事情吗? - payling
@payling 添加了更多的信息。 - Artamiel
这个答案帮了我很多,所以谢谢。只是想确认一下。如果我通过ID获取一个产品(父实体),Doctrine会自动找出该行属于哪个子实体并返回正确的实体类吗?所以如果ID为10的产品是Part类型,并且我从ProductRepo中获取该产品,Doctrine会自动返回一个Part实体实例给我吗?我正在尝试创建一个具有多个用户类型的系统,但希望使用共享登录的共享用户实体。 - Lee Overy
@LeeOvery 是的,它会。如果您仔细查看我的答案的最后部分,您可以看到我为这种情况提供了一个示例。 - Artamiel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接