Вложенные критерии в 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
Спасибо, удобно. Работаю с Doctrine не так давно, в своем блоге я выкладываю переводы глав документации по Doctrine 2. Возможно это поможет кому-нибудь:
http://odiszapc.ru/doctrine/
Спасибо! Очень ценный материал! А то я было начал разочаровываться в Doctrine.
Ну у них во второй версии просто “концепция изменилась” 🙂
Вот анекдот в тему, кстати: http://smile.inazov.com/index.php?option=com_content&view=article&id=194&catid=34:2010-02-12-15-01-10&Itemid=55
Интересный материал, спасибо. Никогда не использовал expr(), но сейчас обязательно попробую.
P.S. Ссылка на анекдот не работает
О, редирект отвалился. Спасибо, что заметили, Алексей. Поправил!)
Чётко, как раз аналогичная задача возникла
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$orConditions = $queryBuilder->expr()->orX();
$andConditions = $queryBuilder->expr()->andX();
$orConditions->add(‘g.key = :value1’);
$orConditions->add(‘g.key = :value2’);
$andConditions->add($orConditions);
$queryBuilder->andWhere($andConditions);
$queryBuilder->setParameter(‘value1’, 5);
$queryBuilder->setParameter(‘value2’, 20);
Похоже, это по фэншую!