Николай Ланец
25 мая 2017 г., 21:33

Многоуровневые условия в xPDO

Материал для экспертов.

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

Если вы уже работали с нашими getdata-процессорами, понять тему публикации будет легче. Если нет, советую к прочтению: https://modxclub.ru/topics/vse-chto-vyi-xoteli-znat-o-proczessorax-no-boyalis-sprosit-1563.html

Итак, есть у нас getdata-процессор на получение товаров. При чем важно учитывать, что процессоры изначально расширяют другие процессоры на выборки, и рассчитаны на то, что их могут и далее расширять. В итоге один и тот же метод (как тот же prepareQueryBeforeCount, который нас здесь больше всего и интересует) может быть переопределен несколько раз, а конечный SQL-запрос сформироваться из нескольких дополняющих друг друга условий. Для лучшего понимания приведу небольшой пример на чистом xPDO. Сформируем простейший запрос на выборку документов.
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $q->prepare(); print $q->toSQL();
На выходе получим SQL-запрос:
SELECT modResource.* FROM `modx_site_content` AS `modResource`
Теперь возьмем и добавим в него пару условий.
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $q->where(array( "parent" => 85, "deleted" => 0, ));
Результирующий запрос:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( `modResource`.`parent` = 85 AND `modResource`.`deleted` = 0 )
Теперь мы получим документы из раздела 85 и не удаленные.

Добавим еще условие, чтобы получить плюс к этому только опубликованные документы.
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $q->where(array( "parent" => 85, "deleted" => 0, )); $q->where(array( "published" => 1, ));
Да, xPDO позволяет несколько раз вызывать методы типа where, select, sortby и т.п.
Получили более сложный запрос:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( `modResource`.`parent` = 85 AND `modResource`.`deleted` = 0 ) AND `modResource`.`published` = 1 )
Здесь, как мы видим, все условия перечислены через AND, так что скобки для нас не играют никакой роли. Но в случае, если у нас в запросе начинают фигурировать условия OR, картина сильно меняется, и очередность скобок и условий начинает играть очень важную роль. Вот давайте в запрос добавим условие "или раздел = 100". То есть у нас выборка уже идет из раздела 85, а хотим еще с теми же условиями выбрать, но включая раздел 100. Запрос расширим, следуя нашей текущей методике, то есть последовательно.
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $q->where(array( "parent" => 85, "deleted" => 0, )); $q->where(array( "published" => 1, )); $q->where(array( "or:parent:=" => 100, ));
Что мы получим на выходе?
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( `modResource`.`parent` = 85 AND `modResource`.`deleted` = 0 ) AND `modResource`.`published` = 1 or `modResource`.`parent` = 100 )
Вот тут уже запрос получился не такой, какой бы мы хотели. Здесь получилось буквально "Получить все документы (из раздела 85, не удаленные) и (опубликованные или из раздела 100)". А хотели бы "Получить все документы ((из раздела 85 или из раздела 100), не удаленные) и (опубликованные)". То есть наш желаемый запрос должен выглядеть примерно так:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( (`modResource`.`parent` = 85 or `modResource`.`parent` = 100) AND `modResource`.`deleted` = 0 ) AND `modResource`.`published` = 1 )
Да, я знаю, что для таких целей можно было бы использовать конструкцию "parent:in" => array(85, 100), но мы рассматриваем не ту задачу, когда мы за один раз формируем нужный нам запрос, а случай, когда мы работаем уже с ранее подготовленным запросом и дальше добавляем в него свои условия.

Итак, прежде чем выдать окончательный вариант, давайте чуть перепишем наш запрос другим позволительным способом:
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $where = array(); $where[] = array( "parent" => 85, "deleted" => 0, ); $where[] = array( "published" => 1, ); $where[] = array( "or:parent:=" => 100, ); $q->where($where);
Это все то же самое, и SQL-запрос конечный тот же самый, но все условия мы не стали по частям отправлять в xPDO, а сначала подготовили массив условий. И здесь вот что интересно: xPDO вполне прилично справляется с многоуровневыми массивами запросов. К примеру, все то же самое можно запихнуть вот в такой массив:
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $where = array( array(), ); $where[0][] = array( "parent" => 85, "deleted" => 0, ); $where[0][] = array( "published" => 1, ); $where[0][] = array( "or:parent:=" => 100, ); $q->where($where);
На выходе сейчас мы получим ровно такой же SQL-запрос, что и ранее, то есть ничего не поменялось. Но теперь мы сформируем конечный правильный запрос и посмотрим что это нам дает.
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $where = array( array(), array(), ); $where[1][] = array( "parent" => 85, ); $where[0][] = array( "deleted" => 0, "published" => 1, ); $where[1][] = array( "or:parent:=" => 100, ); $where[0][] = array( "hidemenu" => 0, "template:in" => array(1,2), ); $q->where($where);
Я специально добавил здесь больше условий, чтобы показать, что используя такой метод, можно добавлять свои условия в различные части запроса, не особо беспокоясь об очередности. Вот такой запрос у нас получился:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( ( `modResource`.`deleted` = 0 AND `modResource`.`published` = 1 ) AND ( `modResource`.`hidemenu` = 0 AND `modResource`.`template` in (1,2) ) ) AND ( `modResource`.`parent` = 85 or `modResource`.`parent` = 100 ) )
То есть, буквально, у нас получился запрос "Получить все документы (не удаленные и опубликованные) и (не скрытые в меню и у которых шаблон 1 или 2) и (раздел 85 или раздел 100)".
Что для нас важно здесь, это то, что условия, добавленные в первый элемент массива условий, будут распространяться на все условия второго элемента массива. То есть если мы хотим добавить еще несколько разделов, и при этом добавить условие поиска по заголовку, и еще добавить разделы, это можно сделать в любой последовательности. К примеру
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $where = array( array(), array(), ); $where[1][] = array( "parent" => 85, ); $where[0][] = array( "deleted" => 0, "published" => 1, ); $where[1][] = array( "or:parent:=" => 100, ); $where[0][] = array( "hidemenu" => 0, "template:in" => array(1,2), ); $where[1][] = array( "or:parent:in" => array(120,130), ); $where[0][] = array( "pagetitle:like" => "%test%", ); $q->where($where);
Запрос:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( ( `modResource`.`deleted` = 0 AND `modResource`.`published` = 1 ) AND ( `modResource`.`hidemenu` = 0 AND `modResource`.`template` in (1,2) ) AND `modResource`.`pagetitle` like '%test%' ) AND ( `modResource`.`parent` = 85 or `modResource`.`parent` = 100 or `modResource`.`parent` in (120,130) ) )
На самом деле редки случаи построения таких хитрых запросов на лету, но тем не менее.
Так же уточню, что эти же условия-массивы можно отправлять и в pdoTools, и они должны работать с большой долей вероятности. Небольшой пример такого вызова: https://modx.pro/development/7236-pdofetch-search-in-tv-fields-with-the-delimiter/

UPD: Василий Наумкин подсказывает, что есть еще нативный способ подобное реализовывать через передачу группы запроса в orCondition()/andCondition().
Пример:
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $q->where(array( "parent" => 85, ), null, 0); $q->orCondition(array( "deleted" => 0, "published" => 1, ), "OR", 1); $q->orCondition(array( "parent:=" => 100, ), null, 2); $q->prepare(); print $q->toSQL();
Но как мы совместно выяснили, это не дает полный контроль над формированием запроса. К примеру, этот код сформирует такой запрос:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( `modResource`.`parent` = 85 AND ( `modResource`.`deleted` = 0 OR `modResource`.`published` = 1 ) AND `modResource`.`parent` = 100 )
То есть как я ни старался, он не сбил блоки условием OR, а только AND. А вот с использованием массивов получилось вот такой запрос сформировать:
$q = $modx->newQuery("modResource"); $alias = $q->getAlias(); $q->select(array( "{$alias}.*", )); $where = array( array( "parent" => 85, "or:parent:=" => 100, array( "or:hidemenu:=" => 0, "template:in" => array(1,2), ) ), array( ), ); $where[0][0][] = array( "published" => 1, );
получив вот такой запрос:
SELECT modResource.* FROM `modx_site_content` AS `modResource` WHERE ( ( `modResource`.`parent` = 85 or `modResource`.`parent` = 100 or ( `modResource`.`hidemenu` = 0 AND `modResource`.`template` in (1,2) AND `modResource`.`published` = 1 ) ) )




Добавить комментарий