ret1984 28 октября 2013 1 8
Привет, Николай!

Подскажи, для поиска по tv нужно переопределить prepareQueryBeforeCount?
8 комментариев
Fi1osof1
Fi1osof 28 октября 2013г в 13:53 #
Вот это очень хороший и важный вопрос! Правда ответ будет довольно объемным, но просто дело очень важное, и понимание этого будет в дальнейшем очень важным плюсом.

Здесь корень тянется из самого принципа построения постраничности: для постраничности требуется знать сколько всего результатов есть, удовлетворяющих условию, и какой 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-шники искомых записей.
Fi1osof1
Fi1osof 28 октября 2013г в 14:00 #
А если добавлять джоин таблицы в PrepareQueryBeforeCount(), то таблица будет и в конечном условии, а в getdata-процессоре она итак джоинится.
github.com/Fi1osof/shopModx/blob/54322b2f9c095003223636b191ea058d939c50b4/core/components/shopmodx/processors/web/getdata.class.php#L35
Получится, что два раза таблица джоинится. Не комильфо будет.
Fi1osof1
Fi1osof 28 октября 2013г в 14:03 #
Но весь этот механизм конечно же мне не видится еще оптимальным. Есть полно задач, когда он соответствует, но все-таки не все устраивает. Плюс тут в итоге вообще 3 запроса получается, а не два. Так что уже повод еще раз очень внимательно подумать.
Еще и планирую добавить опцию, чтобы не выполнять подсчет при желании. Если мы просто получаем общие данные или типа того, когда нас не интересует общее кол-во записей, то и нет смысла в запросе на подсчет.
В общем, все пока так, как есть, и долго еще так будет, но потом механизм будет доработан.
r
ret1984 28 октября 2013г в 22:46 #
В продолжение темы…
Переопределил 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]]


но не тут то было
333806.tehotdel.web.hosting-test.net/models/audi.html
Fi1osof1
Fi1osof 28 октября 2013г в 22:56 #
Что-то у тебя там жуткое :-)

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}'");
        }
Fi1osof1
Fi1osof 29 октября 2013г в 00:02 #
Все, вопрос решенный.

Правильный код:

Процессор.
<?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. И не забывайте в таких случаях проверку на наличие передаваемого значения и обязательно с конвертацией типа данных, а то будет передано строковое значение (то есть переменная есть), а поиск будет не корректный.
Tramp13571
Tramp1357 06 ноября 2013г в 21:21 #
А если переменных несколько? например, цвет и время года? Я не могу сообразить. ведь они храняться в одной таблице, но в разных записях. Первое, что приходит на ум — сделать запрос сначала по переменным и затем использовать выборку как критерий отбора товаров. или это будет неэффективно?
Fi1osof1
Fi1osof 06 ноября 2013г в 22:21 #
Саша, по поводу нескольких записей в БД — не парься. Там же процессор уже написан с учетом этого, и в итоге он все равно вернет только уникальные данные.

А несколько джоинов — это все равно по одной записи к одной, так что кол-во итоговых записей не меняется (если четко указано условие по 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}'");
}
Tramp13571
Tramp1357 06 ноября 2013г в 22:31 #
Понял, спасибо. Буду делать.
Fi1osof1
Fi1osof 06 ноября 2013г в 22:32 #
Пожалуйста.
a
andrei.balkin 01 мая 2015г в 13:48 #
Николай подскажите, а как расширить данный процессор, чтобы можно было делать сортировку, если в tv содержится массив из id вида 12,25,45? Т.е. один и тот же товар может принадлежать нескольким категориям. Интересует именно такая реализация. натолкните на мысль хотя бы. Спасибо
Fi1osof1
Fi1osof 01 мая 2015г в 16:28 #
Сушите весла… Сами вряд ли сделаете. Надо добавлять в селект запрос вида 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)");
a
andrei.balkin 01 мая 2015г в 17:13 #
Сделал последним вариантом

Все работает, есkи тип tv по умолчанию — вписываю 191,195 к примеру — работает.Но не пойму почему, когда я создаю тип tv множественный список ресурсов, добавляю тип вывода с разделителем через, — не работает. Проверял тв выводит аналогично 191,195. в чем может быть косяк
Tramp13571
Tramp1357 01 мая 2015г в 17:22 #
в таблице список хранится в виде
key1||key2||key3 или key1==val1||key2==val2||key3==val3
и тип вывода никак на это не влияет.
a
andrei.balkin 01 мая 2015г в 17:25 #
Александр что посоветуете?
Tramp13571
Tramp1357 01 мая 2015г в 17:31 #
в тонкостях mysql не так силен, как Николай :)
find_in_set ищет подстроку с разделителями "," — и "||" тут не проходит.
как вариант — завести еще одну tv, паписать плагин, который при сохранении документа просто переконвертирует список в строку с разделителем "," — и затем по этому полю уже использовать find_in_set

www.codenet.ru/db/mysql/mystring4.php#find_in_set
a
andrei.balkin 01 мая 2015г в 17:39 #
пробовал вот так прописать

$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 )  
Tramp13571
Tramp1357 01 мая 2015г в 17:47 #
все верно. str_replace — это не функция mysql — отсюда и ошибка.Фукция innerJoin генерирует SQL-код. А туда нет доступа php. Я вижу пока только тот вариант, что описал — нужно наличие поля, в котором значения разделены запятой.
Где-то тут на сайте я видел статью о плагине, который перехватывает сохранение документа и делает необходимые преобразования. Его можно взять за основу.
a
andrei.balkin 01 мая 2015г в 17:48 #
о да точно, понамешал. спасибо — подумаю еще
a
andrei.balkin 01 мая 2015г в 17:50 #
Все проще есть же 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, '||', ',') )");
        }
Fi1osof1
Fi1osof 02 мая 2015г в 12:02 #
Да, так правильно. На большом каталоге будет конечно сильно тормозить такой поиск, но на нескольких сотнях товаров переживать не за что будет.
Fi1osof1
Fi1osof 02 мая 2015г в 12:05 #
Я вижу пока только тот вариант, что описал — нужно наличие поля, в котором значения разделены запятой.
Для этого лучше всего подходит эта технология: habrahabr.ru/post/253737/
a
andrei.balkin 03 мая 2015г в 09:07 #
Статья полезная, буду думать, Пока на данном этапе каталог будет в пределах 300 товаров. Я думаю проблем не возникнет. Спасибо за помощь в очередной раз выручаете
Fi1osof1
Fi1osof 03 мая 2015г в 09:11 #
Пожалуйста.
a
andrei.balkin 01 мая 2015г в 17:25 #
в моем случае да, key1||key2||key3
a
andrei.balkin 01 мая 2015г в 16:31 #
Спасибо, попробую
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.