2016-03-30 2 views
1

Учитывая две доктрину сущностей (Person и Company), связанный один-ко-многим, и репозиторий, который выглядит примерно такSymfony2/Doctrine2 получить присоединился объектами из объекта QueryBuilder

namespace TestBundle\Entity\Repository; 
use Doctrine\ORM\EntityRepository; 

class PersonRepository extends EntityRepository { 

    public function findAllByAge($age) { 
     $qb = $this->createQueryBuilder('p') 
       ->select('p','c') 
       ->leftjoin('p.company', 'c') 
       ->where("p.age = :age") 
       ->setParameter('age', $age); 

     // ... 
    } 
} 

Как я мог извлекать сущность (объект или имя) Компании, предпочтительно из объекта $ qb (или из Alias, DQL, AST, parser и т. д.)?

В идеале, я хотел бы иметь массив, содержащий все псевдонимы, используемые экземпляром QueryBuilder, или по крайней мере те, которые определены в выберите метод, вместе с их субъектами, в виде:

[ 
    'p' => 'TestBundle\Entity\Person', 
    'c' => 'TestBundle\Entity\Company', 
    // etc 
] 

В $qb->getDQLPart('join') или даже что-то более низком уровне, как $qb->getQuery()->getAST()->fromClause->identificationVariableDeclarations, есть информация о присоединениях к псевдонимам, но она содержит только корневую сущность и ее псевдоним (p = TestBundle \ Entity \ Person).

getRootEntity, getRootAliases, getAllAliases не помогите, поскольку я получаю корневую сущность и/или все псевдонимы, но не могу связать их вместе.

$em->getClassMetadata($rootentity)->associationMappings дает мне ассоциации для корневого объекта и содержит целевые сущности для объединений, но без псевдонимов. Я мог бы сопоставить имена полей с информацией от $qb->getDQLPart('join'), но это привело бы меня в область, где мне пришлось бы обходить информацию рекурсивно от каждого объекта сущности. Похоже, это может вызвать серьезные ошибки.

Как Querybuilder преобразует ассоциации в правильные объекты? Или он вообще не делает этого, просто разбирается с материалами более низкого уровня, не зная, какие объекты он использует?

Мне нужна информация, поэтому я могу обеспечить, чтобы определенные поля объектов имели на них определенные вторичные индексы. Эти вторичные индексы могут быть установлены с помощью аннотаций и сохраняются в объекте по доктрине ($em->getClassMetadata($entity)->table['indexes']).

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

ответ

1

, как вы должны сделать это довольно просто:

namespace TestBundle\Entity\Repository; 
use Doctrine\ORM\EntityRepository; 

class PersonRepository extends EntityRepository { 

    public function findAllByAge($age) { 
     $qb = $this->createQueryBuilder('p') 
       ->where("p.age = :age") 
       ->setParameter('age', $age); 

     return $qb->getQuery()->getResult(); 
    } 
} 

... потом, когда шел ответ:

$people = $personRepository->findAllByAge($age); 
$companies = array_map(function ($person) { 
    return $person->getCompany(); 
}, $people); 

Я знаю, что вы думаете: не хотели, чтобы создать ненужное Запросы? Разве невозможно получить все это в одном вызове SQL? Ну, это действительно возможно, но это не так прямолинейно, как это длительный выстрел.

Я бы не рекомендовал его, если нет большой потребности в производительности для этого запроса (например, массивный импорт/экспорт). Но поскольку Doctrine уже добавляет слой абстракции, я предполагал, что производительность здесь не проблема.

EDIT:

Ну тогда в этом случае лучшее, что вы можете сделать, это использовать native queries with custom result set mappers.Из-за того, как работают репозитории, регулярные запросы DQL, объявленные в них, всегда будут возвращать объект репозитория (в вашем случае он попытается выплеснуть экземпляр Person), поэтому я боюсь, что вокруг нет никакого реального пути ,

Это действительно к лучшему, поскольку DQL добавляет слой абстракции, который нежелателен в случае запросов с высокой интенсивностью.

Для длинных запросов я также настоятельно рекомендую использовать ->iterate(), что позволит разделить запрос на более мелкие куски. В конце концов, ваш метод должен выглядеть следующим образом:

namespace TestBundle\Entity\Repository; 
use Doctrine\ORM\EntityRepository; 

class PersonRepository extends EntityRepository 
{ 
    public function getAllByAgeIterator($age) 
    { 
     $rsm = new ResultSetMappingBuilder($this->_em); 
     // RSM configuration here 

     $qb = $this->_em->createNativeQuery(" 
      // Your SQL query here 
     ", $rsm)->setParameter('age', $age); 

     return $qb->getQuery()->iterate(); 
    } 
} 

Я не подробно конфигурация ResultSetMappingBuilder, но я думаю, что вы найдете только штраф.

+0

Спасибо за ответ. Мне действительно нужно получить меньшее подмножество на уровне базы данных, поскольку оно включает таблицы с миллионами записей, часто со сложным соединением и упорядочением. Поэтому перед выполнением запроса мне нужно получить дополнительную информацию об индексах из доктрины. Мне нужна эта информация, чтобы иметь возможность применить andWhere() к querybuilder, после чего она дополнительно ограничена с помощью уставки doctrine. – Fx32

+0

Ах, красивое редактирование. Вместе с несколькими часами чтения кода Doctrine на Github я пришел к выводу, что этот подход может не сработать или не без грубого злоупотребления Doctrine. Проблема в том, что у нас есть куча отдельных проектов Symfony с сотнями сущностей. Все они имеют один (частный) комплект поставщиков, который имеет методы, принимающие QB в качестве аргумента, применяя некоторые пользовательские функции (используя классы, которые расширяют «FunctionNode») и возвращают QB. Поэтому я не могу создать собственный запрос, просто возьмите конструктор запросов и верните построитель запросов. – Fx32

+0

И хотя было бы неплохо обнаружить вторичные индексы, определенные в аннотации @Index сущности автоматически (для обеспечения безопасности), я думаю, мне просто нужно заставить конечных пользователей предоставить массив имен полей/псевдонимов, когда они называют преобразование/фильтр. – Fx32

1

Если я правильно понимаю, вы можете вернуть коллекцию Company объектов непосредственно из запроса, даже из PersonRepository:

namespace TestBundle\Entity\Repository; 
use Doctrine\ORM\EntityRepository; 

class PersonRepository extends EntityRepository { 
    public function findAllByAge($age) { 
     $qb = $this->getEntityManager() 
      ->createQueryBuilder() 
      ->from('YourBundle:Person', 'p') 
      ->select('c') 
      ->distinct() 
      ->leftjoin('p.company', 'c') 
      ->where("p.age = :age") 
      ->setParameter('age', $age); 

     // ... 
    } 
} 

Я не уверен, что при возврате Company экземпляров из Person хранилища хорошо, но это в основном конвенции вопрос. Конечно, вы всегда можете создать CompanyRepository::findAllCompaniesWithEployeesAtAge($age) и обратное соединение вроде:

namespace TestBundle\Entity\Repository; 
use Doctrine\ORM\EntityRepository; 

class CompanyRepository extends EntityRepository { 
    public function findAllCompaniesWithEployeesAtAge($age) { 
     $qb = $this->createQueryBuilder('c') 
      ->select('c') 
      ->distinct() 
      ->leftjoin('c.person', 'p') // or whatever inverse side is called 
      ->where("p.age = :age") 
      ->setParameter('age', $age); 

     // ... 
    } 
} 
+0

Спасибо! Для обоих вариантов мне требуется выполнить запрос. Я получаю querybuilder, определяю, какое из выбранных полей имеет вторичный индекс, добавьте andWhere() в конструктор запросов, используя этот индекс, и должен возвратить построитель запросов * без * его выполнения (поскольку он подается в встроенный paginator и гидратированный определенным образом). Во всех случаях я могу получить только корневой класс сущности, а не все объединенные объекты. – Fx32

+0

Так что у меня не проблема ... Чего вы хотите достичь? – Wirone