28 окт. 2013 г., 9:19

поиск по tv

Привет, Николай!
Подскажи, для поиска по tv нужно переопределить prepareQueryBeforeCount?
Вот это очень хороший и важный вопрос! Правда ответ будет довольно объемным, но просто дело очень важное, и понимание этого будет в дальнейшем очень важным плюсом.
Здесь корень тянется из самого принципа построения постраничности: для постраничности требуется знать сколько всего результатов есть, удовлетворяющих условию, и какой limit устанавливается. К примеру, всего записей 100, лимит 10 — итого у нас 10 страниц. К чему я это? К тому, что каждый раз при выполнении процессора на получение данных, выполняется два запроса. Первый считает общее кол-во записей, удовлетворяющих условию, а второй уже делает конечную выборку. Так вот, при разработке getlist-процессора для shopModx-а, этот момент учитывался, и было использовано клонирование объекта запроса. github.com/Fi1osof/shopModx/blob/02152b36f2143902a4fb2099378b54f1699b1e63/core/components/shopmodx/processors/web/getlist.class.php#L41
Для чего это было сделано? Дело в том. что при поиске могут быть использованы различные сложные условия, плюс джоины различных таблиц и прочее. При этом это может быть нужно только при первичном поиске записей. А при окончательной выборке данных (со всеми колонками), это может и не понадобиться. В итоге, если вопрос стоит именно в поиске по TV, но не надо будет специально подставлять значения этого TV в конечный вывод, то лучше всего это делать на уровне метода prepareCountQuery(). github.com/Fi1osof/shopModx/blob/02152b36f2143902a4fb2099378b54f1699b1e63/core/components/shopmodx/processors/web/getlist.class.php#L80
Переопределяем его, добавляем innerJoin() таблицы с условием и все. На выходе процессор выполнит поиск всех удовлетворяющих условию записей, и подставит их ID-шники в конечный запрос выборки. github.com/Fi1osof/shopModx/blob/02152b36f2143902a4fb2099378b54f1699b1e63/core/components/shopmodx/processors/web/getlist.class.php#L70
При этом в окончательном запросе джоина этой доптаблицы не будет, так как в ней нет необходимости, у нас уже есть ID-шники искомых записей.
А если добавлять джоин таблицы в PrepareQueryBeforeCount(), то таблица будет и в конечном условии, а в getdata-процессоре она итак джоинится. github.com/Fi1osof/shopModx/blob/54322b2f9c095003223636b191ea058d939c50b4/core/components/shopmodx/processors/web/getdata.class.php#L35 Получится, что два раза таблица джоинится. Не комильфо будет.
Но весь этот механизм конечно же мне не видится еще оптимальным. Есть полно задач, когда он соответствует, но все-таки не все устраивает. Плюс тут в итоге вообще 3 запроса получается, а не два. Так что уже повод еще раз очень внимательно подумать. Еще и планирую добавить опцию, чтобы не выполнять подсчет при желании. Если мы просто получаем общие данные или типа того, когда нас не интересует общее кол-во записей, то и нет смысла в запросе на подсчет. В общем, все пока так, как есть, и долго еще так будет, но потом механизм будет доработан.
В продолжение темы… Переопределил modWebCatalogProductsGetdataProcessor
<?php require_once dirname(dirname(__FILE__)).'/getdata.class.php'; class modWebCatalogProductsModelGetdataProcessor extends modWebCatalogProductsGetdataProcessor{ public function initialize(){ $this->setDefaultProperties(array( 'model' => false, )); return parent::initialize(); } public function prepareQueryBeforeCount(xPDOQuery $c) { $c = parent::prepareQueryBeforeCount($c); $c->innerJoin('ShopmodxProduct', 'Product'); if($this->getProperty('model')){ $c->innerJoin('modTemplateVarResource', 'model', "model.contentid = {$this->classKey}.id AND model.tmplvarid = 10 AND model.value='$this->getProperty('model')'"); } return $c; } } return 'modWebCatalogProductsModelGetdataProcessor';
и вывод
{assign var=params value=[ "model" => $modx->resource->pagetitle ]} {processor action="web/catalog/products/model/getdata" ns="modxsite" params=$params assign=result} <div style="overflow:hidden;"> {if $result.success && count($result.object)} {foreach $result.object as $object} {assign var=image value=$object.image|default:$object.imageDefault} <div class="goodItem left"> <img src='{snippet name="phpthumbon" params="input=`{$image}`&options=`w=238&h=170&zc=1`"}' width="238" height="170"> <h2>{$object.pagetitle}</h2> <p>{$object.introtext}</p> <span class="block">{$object.sm_price|number_format:0:" ,":" "} грн.</span> <a class="block" href="{$object.uri}">Подробнее</a> </div> {/foreach} {else} <h2 class="notGoods">Категория пуста</h2> {/if} </div> [[+page.nav]]
Что-то у тебя там жуткое :-)
1. У тебя в TV содержится текстовый заголовок цели? Не лучше ли id документа содержать? Да и скорее всего он и содержится, и надо именно id передавать. (шли доступы в личку, старый пароль не проходит). 2. Два рада $this->getProperty('model') использовать — не комильфо. Лучше так:
if($model = $this->getProperty('model')){ $c->innerJoin('modTemplateVarResource', 'model', "model.contentid = {$this->classKey}.id AND model.tmplvarid = 10 AND model.value='{model}'"); }
Все, вопрос решенный.
Правильный код:
Процессор.
<?php /* Получаем новинки */ require_once dirname(dirname(__FILE__)).'/getdata.class.php'; class modWebCatalogProductsModelGetdataProcessor extends modWebCatalogProductsGetdataProcessor{ public function initialize(){ if(!(int)$this->getProperty('model')){ return 'Не была указана марка'; } return parent::initialize(); } public function prepareCountQuery(xPDOQuery & $query){ $query = parent::prepareCountQuery($query); if($model = (int)$this->getProperty('model')){ $query->innerJoin('modTemplateVarResource', 'model', "model.contentid = {$this->classKey}.id AND model.tmplvarid = 10 AND model.value='{$model}'"); } return $query; } } return 'modWebCatalogProductsModelGetdataProcessor';
Шаблон:
{assign var=params value=[ "model" => $modx->resource->id, "limit" => 9, "getPage" => true, 'cache' => true ]} {processor action="web/catalog/products/model/getdata" ns="modxsite" params=$params assign=result} <div style="overflow:hidden;"> {if $result.success && count($result.object)} {foreach $result.object as $object} {assign var=image value=$object.image|default:$object.imageDefault} <div class="goodItem left"> <img src='{snippet name="phpthumbon" params="input=`{$image}`&options=`w=238&h=170&zc=1`"}' width="238" height="170"> <h2>{$object.pagetitle}</h2> <p>{$object.introtext}</p> <span class="block">{$object.sm_price|number_format:0:",":" "} грн.</span> <a class="block" href="{$object.uri}">Подробнее</a> </div> {/foreach} {else} <h2 class="notGoods">Категория пуста</h2> {/if} </div> [[+page.nav]]
Передавать следовало именно ID. И не забывайте в таких случаях проверку на наличие передаваемого значения и обязательно с конвертацией типа данных, а то будет передано строковое значение (то есть переменная есть), а поиск будет не корректный.
А если переменных несколько? например, цвет и время года? Я не могу сообразить. ведь они храняться в одной таблице, но в разных записях. Первое, что приходит на ум — сделать запрос сначала по переменным и затем использовать выборку как критерий отбора товаров. или это будет неэффективно?
Саша, по поводу нескольких записей в БД — не парься. Там же процессор уже написан с учетом этого, и в итоге он все равно вернет только уникальные данные.
А несколько джоинов — это все равно по одной записи к одной, так что кол-во итоговых записей не меняется (если четко указано условие по id-шникам). К тому же prepareCountQuery() — это метод с копией объекта запроса. Его цель — только получение id-шников конечных объектов. Так что там за запрос не переживай вообще. Делай просто несколько джоинов по своим условиям и все. Перечисляй несколько своих TV-условий и все.
if($this->getProperty('model')){ $c->innerJoin('modTemplateVarResource', 'model', "model.contentid = {$this->classKey}.id AND model.tmplvarid = 10 AND model.value='$this->getProperty('model')'"); } if($color = $this->getProperty('color')){ $c->innerJoin('modTemplateVarResource', 'color_tv', "color_tv.contentid = {$this->classKey}.id AND color_tv.tmplvarid = 11 AND color_tv.value='{$color}'"); }
Понял, спасибо. Буду делать.
Николай подскажите, а как расширить данный процессор, чтобы можно было делать сортировку, если в tv содержится массив из id вида 12,25,45? Т.е. один и тот же товар может принадлежать нескольким категориям. Интересует именно такая реализация. натолкните на мысль хотя бы. Спасибо
Сушите весла… Сами вряд ли сделаете. Надо добавлять в селект запрос вида if(find_in_set('{$value}', column) 1, 0) as `exists` и order by `exists` DESC, но просто так не сделать этого (скажем так, это небольшая недоработка getdata-процессора). Хотя попробуйте так: 1. В prepareQueryBeforeCount() добавляете left join своей ТВшки, в которой содержатся эти значения. 2. В initialize() добавляете
$this->setDefaultProperties(array( "sort" => "if(find_in_set('{$value}', column) 1, 0)", "dir" => "DESC", ));
Если надо не порядок отсортировать, а именно получить только те товары, которые есть в указанной категории, то тут проще: пишем в prepareQueryBeforeCount() так:
$c->innerJoin('modTemplateVarResource', "tv_categories", "tv_categories.contentid = {$this->classKey}.id AND tv_categories.tmplvarid = {$tv_id} AND find_in_set('{$value}', tv_categories.value)");
Сделал последним вариантом
Все работает, есkи тип tv по умолчанию — вписываю 191,195 к примеру — работает.Но не пойму почему, когда я создаю тип tv множественный список ресурсов, добавляю тип вывода с разделителем через, — не работает. Проверял тв выводит аналогично 191,195. в чем может быть косяк
в таблице список хранится в виде key1||key2||key3 или key1==val1||key2==val2||key3==val3 и тип вывода никак на это не влияет.
Александр что посоветуете?
в моем случае да, key1||key2||key3
в тонкостях mysql не так силен, как Николай :) find_in_set ищет подстроку с разделителями "," — и "||" тут не проходит. как вариант — завести еще одну tv, паписать плагин, который при сохранении документа просто переконвертирует список в строку с разделителем "," — и затем по этому полю уже использовать find_in_set
пробовал вот так прописать
$c->innerJoin('modTemplateVarResource', "tematic", "tematic.contentid = {$this->classKey}.id AND tematic.tmplvarid = 14 AND find_in_set('{$tematic}', '{str_replace('||', ',', tematic.value)}' )");
На выходе получаю
2015-05-01 17:37:37] (ERROR @ /index.php) modSiteWebGetlistProcessor [2015-05-01 17:37:37] (ERROR @ /index.php) Array ( [0] => 42000 [1] => 1064 [2] => You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE ( `modResource`.`deleted` = 0 AND `modResource`.`hidemenu` = 0 AND `modRe' at line 1 ) [2015-05-01 17:37:37] (ERROR @ /index.php) SELECT COUNT(DISTINCT `modResource`.`id`) FROM `modx_site_content` AS `modResource` JOIN `modx_shopmodx_products` `Product` ON `modResource`.`id` = `Product`.`resource_id` JOIN `modx_site_tmplvar_contentvalues` `tematic` ON WHERE ( `modResource`.`deleted` = 0 AND `modResource`.`hidemenu` = 0 AND `modResource`.`published` = 1 )
все верно. str_replace — это не функция mysql — отсюда и ошибка.Фукция innerJoin генерирует SQL-код. А туда нет доступа php. Я вижу пока только тот вариант, что описал — нужно наличие поля, в котором значения разделены запятой. Где-то тут на сайте я видел статью о плагине, который перехватывает сохранение документа и делает необходимые преобразования. Его можно взять за основу.
о да точно, понамешал. спасибо — подумаю еще
Все проще есть же replace()
if($tematic = (int)$this->getProperty('tematic')){ $query->innerJoin('modTemplateVarResource', 'tematic', "tematic.contentid = {$this->classKey}.id AND tematic.tmplvarid = 14 AND find_in_set('{$tematic}', replace(tematic.value, '||', ',') )"); }
Да, так правильно. На большом каталоге будет конечно сильно тормозить такой поиск, но на нескольких сотнях товаров переживать не за что будет.
Я вижу пока только тот вариант, что описал — нужно наличие поля, в котором значения разделены запятой.
Для этого лучше всего подходит эта технология: habrahabr.ru/post/253737/
Статья полезная, буду думать, Пока на данном этапе каталог будет в пределах 300 товаров. Я думаю проблем не возникнет. Спасибо за помощь в очередной раз выручаете

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