Доктрина 2 Наследование с ассоциацией

ПРИМЕЧАНИЕ: если то, что я хочу, невозможно, будет принят «непонятный» ответ

В документации Doctrine 2 о сопоставлении наследования говорится, что существует 2 способа:

  • Наследование одиночной таблицы (STI)
  • Наследование таблицы классов (CTI)

Для обоих есть предупреждение:

Если вы используете объект STI / CTI как объект «много-к-одному» или «один-к-одному», вы никогда не должны использовать один из классов на верхних уровнях иерархии наследования как «targetEntity», только те, у которых нет подклассов. В противном случае Doctrine НЕ МОЖЕТ создавать экземпляры прокси этого объекта и ВСЕГДА будет загружать объект с нетерпением.

Итак, как я могу перейти к использованию наследования с ассоциацией с базовым (абстрактным) классом ? (и, конечно же, сохранить производительность)


пример

У пользователя много Pet (абстрактный класс, расширенный Dog или Cat ).

Что я хочу сделать :

 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; } 

Это раздражает, потому что я теряю преимущества наследования!

Примечание. Я не добавлял аннотации Doctrine для сопоставления DB, но вы можете понять, что я имею в виду

Я устал, но это ничего не значит.

Вы пропустили важный бит этого предупреждения:

Если вы используете объект STI / CTI как объект «много-к-одному» или «один-к-одному»

Это не так в вашем примере! Если бы вы не пропустили аннотации доктрины, вы могли бы заметить.

Ассоциация User :: pets – это OneToMany, а не [One | Many] ToOne. У одного пользователя есть много домашних животных.

Обратная связь OneToOne, но она нацелена на пользователя, у которого нет наследования.

Ответ Робин должен был быть хорошим советом – вы можете регистрировать 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 всегда просто PersistentCollection. Он не заботится о своем собственном содержании, пока вы на самом деле не попросите их. Такая ленивая загрузка работает нормально.

Я думаю, что вы неправильно поняли, раздел руководства, который вы цитировали, озаглавлен «Эффективное воздействие», они не говорят вам, что вы не можете этого сделать, только если есть последствия для производительности. Это имеет смысл для ленивой загрузки – для гетерогенных коллекций объектов 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 является «домашним животным», вы получаете 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') 

НТН.