原则2 继承映射与关联

2022-08-30 10:04:24

注意:如果我想要的是不可能的,“不可能”的答案将被接受

关于继承映射的教义2文档中,它说有两种方法:

  • 单表继承 (STI)
  • 类表继承 (CTI)

对于两者,都有警告:

如果将 STI/CTI 实体用作多对一或一对一实体,则永远不应使用继承层次结构上层的类之一作为“目标实体”,而只应使用那些没有子类的类。否则,Doctrine 无法创建此实体的代理实例,并且将始终热切地加载该实体。

那么,如何继续使用与基(抽象)类关联的继承?(并保持性能当然)


用户有许多(由 或 扩展的抽象类)。PetDogCat

我想做什么 :

class User {
    /**
     * @var array(Pet) (array of Dog or Cat)
     */
    private $pets;
}

由于教义文档中的警告,我应该这样做:

class User {
    /**
     * @var array(Dog)
     */
    private $dogs;
    /**
     * @var array(Cat)
     */
    private $cats;
}

这很烦人,因为我失去了继承的好处!

注意:我没有为映射到DB添加教义注释,但您可以理解我的意思


答案 1

我很累,但这似乎没什么大不了的。

您错过了该警告的重要部分:

如果您将 STI/CTI 实体用作多对一或一对一实体

在您的示例中,情况并非如此!如果你没有省略教义注释,你可能已经注意到了。

关联 User::p ets 是 OneToMany,而不是 [One|很多]一个。一个用户有许多宠物。

反向关联OneToOne,但它针对的是User,它没有继承权。

Robin的答案应该是一个很好的提示 - 你可以记录sql查询,看看教义对你的数据库到底做了什么!


性能不佳的场景如下所示:

abstract class Pet { ... }

class Cat extends Pet { ... } 

class Dog extends Pet { ... }

class Collar {
   /**
    * @Column(length="16")
    */

   protected $color;
   /**
    * ManyToOne(targetEntity="Pet")
    */
   protected $owner;
}

现在,如果你想迭代所有的蓝领,教义会遇到一些麻烦。它不知道$owner会是什么类,所以它不能使用代理。相反,它被迫急切地加载$owner,以找出它是猫还是狗。

对于 OneToMany 或 ManyToMany 关系来说,这不是问题,因为在这种情况下,延迟加载工作正常。而不是代理,你得到一个持久收集。PersistentCollection总是只是一个PersistentCollection。它不在乎它自己的内容,直到你真正要求它们。所以延迟加载工作正常。


答案 2

我认为你误解了,你引用的手册中标题为“性能影响”的部分,他们并没有告诉你你不能这样做,只是说如果你这样做,就会对性能产生影响。这对于延迟加载是有意义的 - 对于STI实体的异构集合,您必须转到数据库并加载实体,然后才能知道它将是什么类,因此延迟加载是不可能的/没有意义。我现在正在自己学习教义2,所以我模拟了你的例子,以下工作正常:

namespace Entities;

/**
 * @Entity
 * @Table(name="pets")
 * @InheritanceType("SINGLE_TABLE")
 * @DiscriminatorColumn(name="pet_type", type="string")
 * @DiscriminatorMap({"cat" = "Cat", "dog" = "Dog"})
 */
class Pet
{
    /** @Id @Column(type="integer") @generatedValue */
    private $id;

    /** @Column(type="string", length=300) */
    private $name;

    /** @ManyToOne(targetEntity="User", inversedBy="id") */
    private $owner;
}


/** @Entity */
class Dog extends Pet
{

    /** @Column(type="string", length=50) */
    private $kennels;
}

/** @Entity */
class Cat extends Pet
{
    /** @Column(type="string", length=50) */
    private $cattery;
}

/**
 * @Entity
 * @Table(name="users")
 */
class User
{

    /** @Id @Column(type="integer") @generatedValue */
    private $id;

    /** @Column(length=255, nullable=false) */
    private $name;


    /** @OneToMany(targetEntity="Pet", mappedBy="owner") */
    private $pets;
}

...和测试脚本....

if (false) {
    $u = new Entities\User;
    $u->setName("Robin");

    $p = new Entities\Cat($u, 'Socks');
    $p2 = new Entities\Dog($u, 'Rover');

    $em->persist($u);
    $em->persist($p);
    $em->persist($p2);
    $em->flush();
} else if (true) {
    $u = $em->find('Entities\User', 1);
    foreach ($u->getPets() as $p) {
        printf("User %s has a pet type %s called %s\n", $u->getName(), get_class($p), $p->getName());
    }
} else {
    echo "  [1]\n";
    $p = $em->find('Entities\Cat', 2);
    echo "  [2]\n";
    printf("Pet %s has an owner called %s\n", $p->getName(), $p->getOwner()->getName());
}

我所有的猫和狗都加载为正确的类型:

如果你看一下生成的SQL,你会发现当OneToMany目标实体是“pet”时,你得到的SQL是这样的:

SELECT t0.id AS id1, t0.name AS name2, t0.owner_id AS owner_id3, pet_type, 
t0.cattery AS cattery4, t0.kennels AS kennels5 FROM pets t0 
WHERE t0.owner_id = ? AND t0.pet_type IN ('cat', 'dog')

但是当它设置为 Cat 时,您会得到以下结果:

SELECT t0.id AS id1, t0.name AS name2, t0.cattery AS cattery3, t0.owner_id 
AS owner_id4, pet_type FROM pets t0 WHERE t0.owner_id = ? AND t0.pet_type IN ('cat')

呵呵。


推荐