Doctrine Collection Eager Loading

Redjik

Джедай-мастер
Хочется Eager Load коллекции, но не через join, а как в Йиишечке второй, через дополнительный запрос с IN.

Долго гуглил и ковырял код, походу не умеет доктрина такого.

Как быть? Писать кастомные хинты к DQL?
Может кто-нибудь уже накостылял?

Пагинация же делает отдаленно подобное...
 

hell0w0rd

Продвинутый новичок
Redjik, она должна уметь это делать, если прописана EAGER связь, но она этого не делает.
С знаниями доктрины об sql можно было бы написать не в тупую where id in (...), а в in подставить запрос, но видимо это никому не нужно.
 

Redjik

Джедай-мастер
ну дак а какие варианты? самому пилить? Т_Т
хочется так то - хорошая штука =)
 

stalxed

Новичок
Судя по тикету http://www.doctrine-project.org/jira/browse/DDC-1149 в версии 2.5 для OneToMany and ManyToMany должно тоже работать.

И вообще, разве бизнес логика в ORM не должна быть главнее всяких там ещё баз данных...
Если мыслить бизнес логикой, то я обычно разделяю подобные методы.

Например:
Правильно с точки зрения реляционной БД:
Код:
$clients = $clientRepository->findClientsAndPhonesByBalance($from, $to);
Правильно с точки зрения ООП архитектуры(каждый репозиторий берёт ответственность только за себя):
Код:
$clients = $clientRepository->findClientsByBalance($from, $to);
$phones = $phoneRepository->findPhonesByClients($clients);
Второй плюс - метод findPhonesByClients можно использовать для разных выборок клиентов повторно.

Может быть данный сложный запрос говорит вам изменить архитектуру, а не сам запрос?
 
Последнее редактирование:

Redjik

Джедай-мастер
Правильно с точки зрения ООП архитектуры(каждый репозиторий берёт ответственность только за себя):
Второй плюс - метод findPhonesByClients можно использовать для разных выборок клиентов повторно.
Взаимоисключающие параграфы.

Конкретно по примеру... Предположим нам надо вывести юзеров со всеми телефонами (коих может быть штук по 10 у каждого).
Как это сделать с $clients и $phones, итерировать оба массива и проверять совпадения id? бред же...

Предположим, что
PHP:
$phoneRepository->findPhonesByClients($clients);
вставляет в клиентов телефоны
=> внешнее изменение внутреннего состояния - что еще хуже =)
 

Redjik

Джедай-мастер
Пагинация например(и да, я посмотрел как Доктрина умеет пагинацию - не круто)
Кейсы разные бывают, у меня нет привязки к конкретной задаче пока что.
 

Redjik

Джедай-мастер
По сабжу, у меня 2.4, взял стабильную и пробовал many-many, видимо надо до 2.5 обновится, если зарабоатет EAGER loading, то просто добавлю абстрактный репо с методом with
PHP:
$clients = $clientRepository->with(['phones'])->findClientsByBalance($from, $to);
костыльно, но лучше вариантов пока не вижу
(опять yii головного мозга?)
 

stalxed

Новичок
Предположим, что
PHP:
$phoneRepository->findPhonesByClients($clients);
вставляет в клиентов телефоны
=> внешнее изменение внутреннего состояния - что еще хуже =)
А я не предлагал загружать в клиентов телефоны.
Я дальше их на проспам рекламного предложения использовал)

Но суть в том, что загружать коллекцию объектов, где в каждом объекте есть другая коллекция объектов - по мне само по себе не хорошо.
Например, вы загрузили 100 объектов в главной коллекции, и каждый объект имеет ещё по 100 дочерних объектов.
Это уже 10000 объектов в ОЗУ.

Один раз мне дали оптимизировать кусок функционала SugarCRM, делать нечего - профайлер и разбираться.
Тормозил только datagrid. Начал разбираться. Именно такая ситуация там и возникла.
Там самописная ORM. Коллекция из 200 объектов, каждый из которых имел дочерние объекты. Дольше всего по времени выполнялись не SQL запросы. А построение самих объектов.
Притом не было 1 операции про которую можно было бы сказать, что именно она тормозила, тормозила сумма.
Переписал запросы используя их же ORM чтобы возвращался не граф объектов, а обычный array с необходимыми полями. Получил ускорение в 50-100 раз.

Вообще подобное решение - коллекция объектов, где каждый имеет коллекцию объектов в загруженном состояние - нужно разве что при импорте и в datagrid`ах.

С вышеуказанной php doctrine нравится элегантность этого решения для datagrid: https://github.com/orocrm/crm/blob/master/src/OroCRM/Bundle/AccountBundle/Resources/config/datagrid.yml
Лучшее что я видел.
Формируется обычный DQL, возвращающий НЕ ОБЪЕКТЫ, а обычный массив. В красивейшей оболочке.

Где необходимость при работе с ORM - всегда получать коллекции объектов?
Часто это вижу. А потом жалобы "тормоза эти ваши ORM".
 

stalxed

Новичок
PHP:
$clients = $clientRepository->with(['phones'])->findClientsByBalance($from, $to);
костыльно, но лучше вариантов пока не вижу
(опять yii головного мозга?)
Почему Вы хотите написать универсальный репозиторий, который очень умный?
Репозиторий должен быть тупым и выполнять лишь те функции которые вам необходимы здесь и сейчас.
 

Вурдалак

Продвинутый новичок
stalxed, с точки зрения интерфейса репозитория, EAGER/LAZY значения не имеет: мы на этом уровне не знаем про такие вещи.
 

Вурдалак

Продвинутый новичок
(опять yii головного мозга?)
Не без этого.

Как-то так можно было бы:
PHP:
interface ClientRepository
{
    /**
     * @return Client[]
     */
    public function findByBalance($from, $to);
}

class DoctrineClientRepository implements ClientRepository
{
    public function __construct(EntityManager $em, $forcedFetchMode = null)
    {
        // ...
    }

    public function findByBalance($from, $to)
    {
        // ...
  
        if ($this->forcedFetchMode !== null) {
            $query->setFetchMode($this->forcedFetchMode);
        }

        // ...
    }
}
И инжектить в нужные места нужный репозиторий. Можно вместо $forcedStrategy передавать массив — на каждый метод свой элемент. Но таким образом ты по крайней мере не будешь кишочки типа EAGER/LAZY вываливать туда, куда не нужно. Потому что кто сказал, что у тебя именно Doctrine, а не Redis или ещё хрен знает что.
 
Последнее редактирование:

Redjik

Джедай-мастер
Тока стратегию сеттером инжектить лучше (или и сеттером и конструктором) и объект типа FetchStrategy, подумаю короче, норм варик
 

hell0w0rd

Продвинутый новичок
Интересные вы, с вопроса "как сделать eager loading в два запроса, вместо N*M" вы скатились к ответственности объектов. Да насрать на это всем, когда без дополнительных костылей доктрина делает 3000 запросов там, где можно было сделать 2.
 

stalxed

Новичок
А есть выбор?
Какая ORM в php построена ещё по паттерну UnitOfWork?
 
Сверху