Doctrine 2继承映射与关联

时间:2011-04-19 11:40:12

标签: php inheritance orm doctrine doctrine-orm

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

Doctrine 2 documentation about inheritance mapping中,它说有两种方式:

  • 单表继承(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注释,但你可以理解我的意思

2 个答案:

答案 0 :(得分:47)

我很累,但这似乎很无聊。

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

  

如果您将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或ManyToMany关系,这不是问题,因为在这种情况下,延迟加载工作正常。您将获得PersistentCollection而不是代理。而PersistentCollection总是只是一个PersistentCollection。在你真正要求它们之前,它并不关心它自己的内容。所以延迟加载工作正常。

答案 1 :(得分:46)

我认为你误解了,你引用的手册部分名为“性能影响”,他们并没有告诉你不能这样做,只有有如果你这样做,性能影响。这对于延迟加载是有意义的 - 对于STI实体的异构集合,您必须先去数据库并在知道它将成为什么类之前加载实体,因此延迟加载是不可能的/没有意义。我现在正在学习Doctrine 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 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。