Doctrine-коллекция гидратов в классе сущностей
у меня проблема с двунаправленным OneToMany <-> ManyToOne
отношения между субъектами Device
и Event
. Вот как выглядит отображение:
// Device entity
/**
* @ORMOneToMany(targetEntity="AppBundleEntityEvent", mappedBy="device")
*/
protected $events;
// Event entity
/**
* @ORMManyToOne(targetEntity="AppBundleEntityDevice", inversedBy="events")
*/
protected $device;
проблема возникает потому, что Device
это Наследование Одной Таблицы сущности
* @ORMInheritanceType("SINGLE_TABLE")
* @ORMDiscriminatorColumn(name="device_class_type", type="string")
и каждый раз, когда я получаю и повторяю некоторые Event
лица, затем $device
всегда с нетерпением. Это происходит потому, что это объект STI, как сообщается в относительное документация
общее рассмотрение представления с одиночной таблицей Наследование: если целевой объект много-к-одному или один-к-одному ассоциация является сущностью STI, предпочтительнее по соображениям производительности что это листовая сущность в иерархии наследования (т. е. нет подклассы). В противном случае Doctrine не сможет создать прокси-экземпляры этого entity и всегда будет загружать сущность с нетерпением.
теперь есть еще один сущность называется Gateway
, который имеет отношения с Device
и Event
:
/**
* @ORMOneToMany(targetEntity="AppBundleEntityDevice", mappedBy="gateway")
*/
protected $devices;
/**
* @ORMOneToMany(targetEntity="targetEntity="AppBundleEntityEvent", mappedBy="gateway")
*/
protected $events;
public function getEvents(): Collection
{
return $this->events;
}
и, конечно, каждый раз, когда я перебрать $gateway->getEvents()
все устройства относительных событий извлекаются с нетерпением. Это происходит, даже если я не получаю $device
info-пустой foreach
достаточно, чтобы доктрина выполняла 1 запрос для каждого объекта, чтобы получить связанный $device
foreach ($gateway->getEvents() as $event) {}
теперь я знаю, что мог бы использовать QueryBuilder
чтобы установить другой режим гидратации, избегая $device
fetching
return $this->getEntityManager()->createQueryBuilder()
->select('e')
->from('AppBundle:Event', 'e')
->where('e.gateway = :gateway')
->setParameter('gateway', $gateway)
->getQuery()->getResult(Query::HYDRATE_SIMPLEOBJECT);
но я хотел бы сделать это как-то напрямую в Gateway
сущности.
так это возможно гидрат Gateway->events
непосредственно в Gateway
класс сущностей?
2 ответов
вам потребуется написать свой собственный метод гидратации
у вас есть циклическая ссылка, где один из этих узлов (устройств) будет силу FETCH EAGER
. Что еще хуже, один из этих узлов (шлюз) действует как ManyToMany join table между двумя другими, в результате чего FETCH EAGER
загрузка всего в почти бесконечный цикл (или, по крайней мере, больших блоков связанных данных).
+──< OneToMany
>──+ ManyToOne
>──< ManyToMany
+──+ OneToOne
┌──────< Gateway >──────┐
│ │
+ +
Event +──────────────< Device*
как вы можете видеть, когда устройство делает нетерпеливый fetch, он будет собирать много Gateways
, таким образом, многие Events
, таким образом, многие Devices
, таким образом, много больше Gateways
, etc. Fetch EAGER
будет продолжать идти, пока все ссылки не будут заполнены.
предотвратите" нетерпеливую " гидратацию, построив свой собственный гидратор.
создание собственного гидратора потребует некоторых тщательных манипуляций с данными, но, вероятно, будет несколько простым для вашего случая использования. Не забудьте зарегистрировать свой гидратор с помощью Doctrine и передать его в качестве аргумента $query->execute([], 'GatewayHydrator');
class GatewayHydrator extends DefaultEntityHydrator
{
public function hydrateResultSet($stmt)
{
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$class = $this->em->getClassMetadata(Gateway::class);
$gateway = $class->newInstance();
$gateway->setName($data[0]['gateway_name']); // example only
return $gateway;
}
}
кроме того, удалите сопоставленное поле с устройства на шлюз
удаление $gateway => Gateway
отображение из Device
и mappedBy="gateway"
С Gateway->device
назначение, устройство будет листок с точки зрения доктрины. Это позволит избежать этого ссылочного цикла с одним недостатком: свойство Device - >gateway должно быть установлено вручную (возможно, в Gateway и Event setDevice
методов).
Я бы предложил вам несколько вариантов для рассмотрения здесь.
1) по состоянию на документация доктрины можно использовать fetch="EAGER"
чтобы намекнуть доктрине, что вы хотите, чтобы отношение охотно извлекалось всякий раз, когда сущность загружается:
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway", fetch="EAGER")
*/
protected $devices;
при использовании тщательно это может спасти вас от запуска дополнительных запросов на итерации, но имеет свои недостатки.
Если вы начинаете активно использовать жадную загрузку вы можете оказаться в ситуации где загрузка сущности для чтения простого атрибута из нее приведет к загрузке десятков и даже сотен отношений. Это может выглядеть не так плохо с точки зрения SQL (возможно, один запрос), но помните, что все результаты будут гидратированы как объекты и прикреплены к единице работы для мониторинга их изменений.
2) Если вы используете это для целей отчетности (например, отображение всех событий для устройства), то лучше вообще не использовать сущности, а запрашивать гидратацию массива из учения. В этом случае вы сможете контролировать, что попадает в результат, явно присоединяясь к отношению (или нет). В качестве дополнительного преимущества вы пропустите дорогостоящую гидратацию и мониторинг UoM, поскольку в таком случае маловероятно изменение сущностей. Это также считается "наилучшей практикой" при использовании доктрины отчетности.