原则2:带有关联的继承映射


Doctrine 2 Inheritance Mapping with Association

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

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

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

对于两者,都有警告:

如果您使用STI/CTI实体作为多对一或一对一的实体,您永远不应该使用继承层次的上层中的一个类作为"targetEntity",只能使用那些没有子类的类。否则Doctrine将无法创建该实体的代理实例,并将始终主动加载该实体。

那么,我如何继续使用与基类(抽象)关联的继承呢?(当然还要保证性能)

一个用户有多个Pet(由DogCat扩展的抽象类)。

我想做什么:

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

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

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

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

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

我很累,但这似乎是无事生非。

你错过了那个警告的重要部分:

如果使用STI/CTI实体作为多对一或一对一实体

在你的例子中不是这样的!如果你没有省略教义注释,你可能已经注意到了。

关联User::pets是OneToMany,而不是[One|Many]ToOne。一个用户有很多宠物。

反向关联 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;
}

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

这不是OneToMany或manymany关系的问题,因为在这种情况下,延迟加载工作得很好。您得到的不是代理,而是PersistentCollection。一个PersistentCollection总是只是一个PersistentCollection。它不关心自己的内容,直到你真正请求它们。

我想你误解了,你引用的手册中题为"性能影响"的部分,他们并没有告诉你不能这样做,只是说如果你这样做会对性能产生影响。这对于延迟加载是有意义的——对于STI实体的异构集合,您必须在知道它将是什么类之前访问数据库并加载实体,因此延迟加载是不可能的/没有意义的。我现在正在学习原则2,所以我模拟了你的例子,下面的工作OK更多:

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 targetEntity为"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')

HTH .