Doctrine class table 继承映射:为什么外键指向父 table?

Doctrine class table inheritance mapping: why a foreign key to parent table?

简短的问题:我可以避免为父 classes 的继承 classes 生成外键吗?是否可以在关系所有者而不是父对象中设置用于继承映射的鉴别器列class?

解释:

我正在设计一个发票模型,其中发票主题可以是 3 种类型中的一种:

看完Doctrine inheritance mapping I think Class table inheritance最符合我的需要

Mapped superclass 如果我能画出从 InvoiceInvoiceSubject 的关系,则可以更好地满足我的需要,但我想我不能:

A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment.

此外,使用接口可能是一种解决方案,但关系中的接口只能映射到一个实体

所以,这是我的模型:

/**
 * @ORM\Entity
 */
class Invoice
{
    /**
     * @ORM\ManyToOne(targetEntity="InvoiceSubject")
     * @var InvoiceSubject
     */
    protected $subject;
}

/**
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="invoicesubject_type", type="string")
 * @ORM\DiscriminatorMap({"invoice-subject" = "InvoiceSubject", "contract" = "Contract", "provider" = "Provider", "client" = "Client"})
 */
class InvoiceSubject
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    protected $id;
}

/**
 * @ORM\Entity
 */
class Contract extends InvoiceSubject{}

/**
 * @ORM\Entity
 */
class Provider extends InvoiceSubject implements ProviderInterface{}

/**
 * @ORM\Entity
 */
class Client extends InvoiceSubject{}

但是,当我尝试生成模型 (bin/console doctrine:schema:update --dump-sql) 时,我看到它试图创建从子 class 到父(table 已经存在并且有数据):

ALTER TABLE contract ADD CONSTRAINT FK_E9CCE71ABF396750 FOREIGN KEY (id) REFERENCES invoice_subject (id) ON DELETE CASCADE;
ALTER TABLE provider ADD CONSTRAINT FK_B2F1AF1BBF396750 FOREIGN KEY (id) REFERENCES invoice_subject (id) ON DELETE CASCADE;
ALTER TABLE client ADD CONSTRAINT FK_B2F1AF1BBF396750 FOREIGN KEY (id) REFERENCES invoice_subject (id) ON DELETE CASCADE;

这意味着来自不同 table 的 id 之间将存在 冲突,否则我将需要在每个 table 中使用不同的值,none 的解决方案是好的。所以,问题是:

  • 有没有办法避免这个外键并在关系所有者中设置鉴别器 class Invoice?在我的例子中,InvoiceSubject 实际上不需要作为 table 存在,我被迫将其创建为 Class table 继承 强迫我这样做。
  • 或者,这个建模是否完全错误,我应该使用另一种方法?

好的,我可能误读了你的部分问题:

是的,这三个子实体在父实体 (table) 中有一个指向同一 id 字段的外键,这是有意为之的。这个想法是,core 实体是发票主题。该实体具有 id。子实体继承该 id 并通过扩展父实体获得更多属性(在子 table 中)。这就是继承的意义。你基本上有一个核心实体,它有不同的子类型和额外的属性。

(注意:您也可以手动执行此操作,将关联映射添加到潜在的 "extra data of the contract/client/provider variety" 到某些 invoicesubject 实体)

这也意味着,您实际上不必处理冲突,因为父 table 条目总是首先由学说创建。当您创建任何子类型的新 InvoiceSubject 时,您实际上创建了一个 InvoiceSubject(具有 id)并扩展了它。因此,您的 contract/client/provider 实体将不会具有相同的 ID(除非您使用 SQL 手动设置它)。

旧答案

这是一个非常固执己见的答案。我的意思是......从技术上讲,这是一个品味问题。如果可以合理地避免并且没有充分的理由这样做,我总是更喜欢 而不是 进行继承映射。

问题是:您是否有 一个 表格来输入它们?您(已经)是否拥有包含任何这些实体的单个字段?这些实体是否提供相同的语义?是懒惰驱使您只想处理一个 "type" 实体吗?有那么多地方想要对它是什么样的主题完全不可知并且不能通过 well-defined 界面解决吗?他们什么时候真正受到同等对待?

就您个人而言,查看您的用例,我可能会保留三个实体,以及具有三个字段的发票,每个实体一个。它很简单,速度很快,鉴别器列很烂(恕我直言,语义上,不是技术上)。

一样为您的发票添加一个功能
function getSubject() {
    return $this->contract ?? $this->provider ?? $this->client;
}

setting tad 更难......但是如果你不想要三个不同的 setter(老实说我怀疑你创建了一个 Client 并且在设置主题时,你忘了它是一个客户,想把它当作一个 InvoiceSubject)

function setSubject(InvoiceSubject $subject) {
    if($subject instanceof Client) {
         $this->client = $subject;
    } elseif (...) {} elseif (...) {}
    //... technically you should unset the others, if it can only ever be one
}

几乎所有您可能想通过继承映射使用的概念都可以在代码中解决,几乎没有开销,但它会大大简化许多其他事情。大多数时候,您可能可以在代码中使用该界面。

继承映射恕我直言,麻烦多于它的价值。除非你有非常充分的理由真正需要它:否则不要这样做。处理独特的不同实体比处理一些抽象实体要容易得多,在抽象实体中你总是必须检查它是哪一种并注意......这真的很烦人。另一方面,你什么时候完全一样对待三个实体?我敢打赌,其中每一个都有一些独特的东西在发生,而且总有 switch-cases 无论如何。如果不是:接口。

保持简单。