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, поскольку в таком случае маловероятно изменение сущностей. Это также считается "наилучшей практикой" при использовании доктрины отчетности.