Вложенные критерии в Doctrine 2 Query Builder
В этой статье я расскажу об одном интересном способе формирования вложенных критериев в DQL запросах Doctrine 2.
Последнее время я всё больше занимаюсь разработкой под Doctrine 2. В текущем проекте ORM система вообще не использовалась (ну если не считать Zend_Db и разбросанные то тут, то там куски кода). Поэтому при выборе ORM решил использовать последнюю стабильную версию (2.1 на тот момент).
29 сентября вышла Doctrine 2.2 в которой был исправлен баг с вложенными условиями andx. Суть его в том, что нельзя добавить выражение andx() в условие andWhere(), которео формируется QueryBuilder’ом в Доктрине. Баг пофиксили, и мне пришлось обновить библиотечку. Тут выяснилось что расширение DoctrineExtension\Paginate, которое я использовал, не работает с Doctrine 2.2 потому, что авторы доктрины наконец решили вернуть сделать свой пагинатор (Doctrine\ORM\Tools\Pagination\Paginator). Как говорится, ну где же вы раньше были? 🙂 Вообще я начинаю понимать, почему говорят , что Doctrine 2 в 3 раза быстрее чем Doctrine 1. Они просто выпилили треть функций. Например, больше нет моих любимых $model->fromArray() и $model->toArray(). Авторы предлагают… писать свои реализации. Ну это всё лирика.
Задача
Сегодня будем решать такую задачу. Пусть есть критерий1(a=10) и переменное количество других критериев(criterias). Надо сформировать DQL запрос со следующей Where частью: a=10 AND (criteria[1] OR criterias[2] OR criterias[3] …). Оказывается, что для этого надо поплясать с бубном.
Варианты решения:
- Модельный поиск (findBy()) – не подходит по интерфейсу функции.
- Native SQL – не подходит, т.к. будет очень много хм.. некачественного кода.
- DQL.. вариант, но в лоб не подходит, также как и п.2
- QueryBuilder – кажется подходит.
QueryBuilder как раз позволяет легко добавлять части запроса по определённым условиям, т.е. это то, что нужно. Однако есть одна особенность в интерфейсе функции orx() (ну и andx() конечно).
|
1 |
$qb->andWhere($qb->expr()->orx($criterias[1], $criterias[2] $criterias[3])); |
Функция принимает критерии как свои аргументы, и например такой вариант не пойдёт:
|
1 |
$qb->andWhere($qb->expr()->orx($criterias)); |
Конечно есть и ложка мёда в этой бочке дёгтя. Функция принимает NULL значения и теоретически можно передавать ей аргументы, которые будут через один – NULL. НО в таком случае всё равно надо знать количество криетриев, а если их количество неизвестно – то задача таким методом решена не будет.
Решение
Тут я вспомнил про замечательную функцию PHP call_user_func_array(). Она и позволила написать элегентное решение данной задачи.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public function getDQLForSomething() { $qb = $this->getEntityManager()->createQueryBuilder(); $qb->select('g.key, c.a') .... ->where('c.a = 10'); $criterias = array(); if($something) $criterias[] = $qb->expr()->andx( $qb->expr()->gte('g.key', '5'), $qb->expr()->lte('g.key', '20') ); if($something2) $criterias[] = $qb->expr()->gt('g.key', '20'); $where = \call_user_func_array(array($qb->expr(), "orx"), $criterias); $qb->andWhere($where); $q = $qb->getQuery(); return $q; } |
Таким образом теперь возможно передавать любое количество критериев в функцию andWhere().
Ссылки
Doctrine 2 Query Builder
Can’t add instance of Andx to QueryBuilder through andWhere
andx nested in another andx