Что такое процессоры в Modx

Почему стоит обратить внимание на процессоры в shopModx

10 мая 2017

Статья для тех, кто пишет на php и использует самописные сниппеты.

Тот, кто использует сниппеты для выборки ресурсов и возврата конечного результата, наверняка может разбить процесс на такие этапы:

1. Формирование общего запроса на выборку.

То есть, к примеру, нам надо сделать выборку всех документов, отвечающих определенным условиям:

  • Не удален
  • Опубликован
  • Не скрыт в меню

2. Перехват дополнительных параметров поиска.

Здесь уже может быть что угодно. Можно указать определенные разделы, где искать, максимальные/минимальные значения и т.п.

3. Подготовка данных для вывода.

Это может быть преобразование формата данных (даты, валюты и т.п.), добавочные данные и т.п.

То есть на выходе мы по ситуации формируем конечный запрос, получаем данные, подготавливаем их и скармливаем чанку или другому шаблонизатору. 

В чем здесь сложности?

Сложность в том, что как правило вся логика находится в одном и том же файле/сниппете, и постепенно он может разрастись до довольно больших размеров. Особенно когда возникает необходимость в зависимости от типа объекта сделать различные преобразования данных и т.п. К тому же иногда даже возникает сложность определить что является обязательным условием для поиска, а что не обязательным. К примеру, условия «опубликован», «не удален» и «не скрыт в меню» могут быть обязательными и они никогда не должны меняться для этого запроса, а остальные условия поиска опциональные. Но здесь они все в куче… К тому же какие-то условия поиска могут требовать наличия в запросе дополнительных объектов (TV-параметры и т.п.). В общем кто работал с каталогами типа недвижимости и т.п., меня понимает.

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

В общем, я хочу показать на паре примеров, как процессоры могут облегчить решение подобных задач.

Пример решения подобной задачи на процессорах

Сразу скажу, что за основу взят один из процессоров из shopModx-а —ShopmodxWebGetDataProcessor. Все эти процессоры я называю list-процессоры, так как их основная задача — выборки, а каждый из них в отдельности сокращенно — getlist, getdata и getcollection процессоры.
И да, эти процессоры подходят не только для выборки товаров, но и для любых других ресурсов.


Итак, вот основной процессор, который делает выборку всех товаров:

<?php

if($this instanceofModxsite){
    $modxsite
=& $this;
}
else{
    $modxsite
=& $this->modxsite;
}
$modxsite
->loadProcessor('web.resourceproduct.getdata','shopmodx');

class modWebShopResourceproductGetdataProcessor extendsmodWebResourceproductGetDataProcessor{
   
protectedfunction prepareCountQuery(xPDOQuery &$query){
        $query
= parent::prepareCountQuery($query);
        $query
->where(array(
           
'deleted'   =>0,
           
'hidemenu'   =>0,
           
'published'   =>1,
       
));
        $query
->innerJoin('modTemplateVarResource','tv_image',"{$this->classKey}.id=tv_image.contentid AND tv_image.tmplvarid=18");
        $query
->where(array(
           
'tv_image.value:!='=>'',
       
));
       
return $query;
   
}
   
   
   
   
publicfunction afterIteration(array $data){
        $data
= parent::afterIteration($data);
       
       
// Получаем УРЛ источника файлов
        $source_url
= $this->modx->runSnippet('getSourcePath');
       
foreach($data as& $doc){
           
// Заменяем ковычки
            $doc
'pagetitle'= str_replace('"','"', $doc'pagetitle');
           
           
// Формируем полный УРЛ на картинку
           
if(!empty($doc'tvs''img')&&!empty($doc'tvs''img''value')){
                $doc
'tvs''img''value'= $source_url.$doc'tvs''img''value';
           
}
           
           
// Формируем массив размеров
           
if(!empty($doc'tvs''size')&&!empty($doc'tvs''size''value')){
                $size
= array();
                $size_array
= explode("||", $doc'tvs''size''value');
               
foreach($size_array as $s){
                    $a
= explode("==", $s);
                    $sizetrim
($a0)=trim($a1);
               
}
                $doc
'tvs''size''value'= $size;
           
}
       
}
       
       
return $data;
   
}
}

return'modWebShopResourceproductGetdataProcessor';


Он расширяет другой shopModx-процессор, который уже добавляет связку с классом товаров, потому в нашем процессоре уже нет нужды добавлять эту связку.

Как видите, в этом классе всего два метода:
1. prepareCountQuery. Здесь мы как раз и добавляем наши обязательные условия поиска (не удален, опубликован и не скрыт в меню, а так же обязательное наличие картинки для товара). Эти условия нам понадобятся в абсолютно всех вариантах поиска товаров.
2. afterIterationGetData-процессор — необычный лист-процессор. он не просто получает все документы, но еще и набивает в массив все его TV-шки. Об этом я подробно и с картинками писал здесь.
Но это сырые данные. И мне необходимо эти данные специально подготовить под мои индивидуальные потребности. И как раз в методе afterIteration я имею возможность еще раз пробежаться по конечному массиву данных и выполнить необходимые манипуляции. К примеру, как это сделано здесь, разбил строку с размерами и сформировал из нее массив значений, а так же сформировал абсолютный УРЛ до картинки.

Все, в общих чертах мой list-процессор готов. Он формирует минимально необходимый запрос, получает данные, готовит их в нужном виде и возвращает конечный массив. Дальше я могу все эти данные прогнать через чанки или Smarty-шаблоны.

Новинки, популярные и т.п.

И вот как раз здесь у нас возникает потребность в формировании более точечных запросов. К примеру, «новинки» или «популярные». Конечно, если нам надо всего-лишь указать id раздела, из которого нам нужно сделать выборку товаров, то можно было бы просто в параметрах передать 'where' => array('parent' => $id) и все. Но те же новинки и т.п. часто делают на основе TV-шек. Соответственно нам надо к нашему запросу надо добавить таблицу TV-шек и добавить условие поиска по ней определенного TV со значением. Делаем просто: создаем еще один процессор и расширяем имеющийся:

<?php
/*
 * Получаем вообще все новинки
 */

require_once dirname
(dirname(__FILE__)).'/getdata.class.php';

class modWebShopResourceproductSlingsGetdataProcessor extendsmodWebShopResourceproductGetdataProcessor{
   
protectedfunction prepareCountQuery(xPDOQuery &$query){
        $query
= parent::prepareCountQuery($query);
        $query
->innerJoin('modTemplateVarResource','tv_new',"{$this->classKey}.id = tv_new.contentid AND tv_new.tmplvarid=20");
        $query
->where(array(
           
'tv_new.value'=>1,
       
));
       
return $query;
   
}  
}
return'modWebShopResourceproductSlingsGetdataProcessor';


Здесь нам нет нужды опять прописывать формирование основного запроса и обработку конечных данных. Мы всего-лишь добавили условие по TV-полю. Все остальное у нас уже имеется. То есть этот процессор выполнит все тоже самое, что и его предок, только получит одни только новинки.

А вот популярные:

<?php
/*
 * Получаем вообще все популярные
 */

require_once dirname
(dirname(__FILE__)).'/getdata.class.php';

class modWebShopResourceproductPopularGetdataProcessor extendsmodWebShopResourceproductGetdataProcessor{
   
protectedfunction prepareCountQuery(xPDOQuery &$query){
        $query
= parent::prepareCountQuery($query);
        $query
->innerJoin('modTemplateVarResource','tv_new',"{$this->classKey}.id = tv_new.contentid AND tv_new.tmplvarid=21");
        $query
->where(array(
           
'tv_new.value'=>1,
       
));
       
return $query;
   
}  
}
return'modWebShopResourceproductPopularGetdataProcessor';


То же самое, только TV другой. Конечно это можно было бы загнать все в один процессор, но я не хочу, чтобы в вызов процессора приходилось передавать какие-то параметры. Вызов процессора может выполняться в нескольких местах, и я не хочу бегать по всему сайту и искать где параметры менять. А так в одном месте поменял и все. Но даже это можно оптимизировать, вызывая один определенный процессор на уровне статического метода getInstance процессора, но это не суть сейчас.

Самое хорошее во всем этом то, что общий запрос и обработка всех данных у нас в одном месте, в одном процессоре, и если нам что-то надо поменять, то мы делаем это в одном месте. Но на уровне конкретных процессоров мы так же можем перегрузить или расширить методы родительских классов, а значит и сформировать более гибкие запросы или определенным способом подготовить конечные данные.

Так же в лист-процессорах надо отметить одну важную особенность: там формируется два объекта запросов. Один формируется с учетом всех дополнительных параметров поиска и т.п, и его основная цель — посчитать сколько всего имеется элементов, удовлетворяющих условию, и найти ID-шники конкретных элементов с учетом этих условий, лимитов, оффсетов и т.п. Второй запрос (самый главный), который получает все конечные колонки из запрошенных таблиц, как правило в условии уже имеет только ID-шники запрошенных объектов. Все остальные условия из них вычищаются. Нет смысла в каких-то дополнительных условиях, если уже поиск элементов выполнен и нам известны их ID-шники. Вся эта система может оказаться неочевидной и запутанной (и не исключено, что она еще будет скорректирована), но сейчас она дает все необходимое. И главное — для поиска конечных элементов мы можем выполнить внутри процессора сразу несколько запросов. К примеру, вот такой процессор:

<?php
if($this instanceof modxSite){
    $modxsite
=& $this;
}
else{
    $modxsite
=& $this->modxsite;
}

$modxsite
->loadProcessor('web/getdata','shopmodx');

class modWebNedvigimostGetdataProcessor extendsShopmodxWebGetDataProcessor{
   
protected $sectionsIDs = array();   // Разделы
   
   
publicfunction initialize(){
        $this
->setDefaultProperties(array(
           
'start'=>0,
           
'limit'=>30,
           
'sort'=> $this->defaultSortField,
           
'dir'=> $this->defaultSortDirection,
           
'combo'=>false,
           
'query'=>'',
       
));
       
returntrue;
   
}


   
publicfunction beforeQuery(){
        $can
= parent::beforeQuery();
       
if($can !==true){
           
return $can;
       
}
       
        $this
->getSectionsIDs($this->getSectionsCondition());
       
       
if(!$this->sectionsIDs){
           
return"Не были получены разделы";
       
}
       
returntrue;
   
}

   
protectedfunction getSectionsCondition(){
       
return array(
           
'parent'=>7,
       
);
   
}
   
   
// Получаем ID-шники разделов
   
protectedfunction getSectionsIDs($where){
       
if(!$where){
           
return;
       
}
        $query
= $this->modx->newQuery('modResource');
        $query
->select(array(
           
"DISTINCT {$this->classKey}.id",    
       
));
        $query
->where(array(
           
'deleted'   =>0,
           
'published'=>1,
           
'isfolder'  =>1,
           
'hidemenu'  =>0,
       
));
        $query
->where($where);
       
       
if($query->prepare()&& $query->stmt->execute()&& $rows = $query->stmt->fetchAll(PDO::FETCH_ASSOC)){
            $result
= array();
           
foreach($rows as $row){
                $result
= $row'id';
           
}
            $this
->sectionsIDs = array_unique(array_merge($this->sectionsIDs,$result));
           
return $this->getSectionsIDs(array(
               
"parent:IN"=> $result,
           
));
       
}
       
return;
   
}

   
publicfunction prepareCountQuery(xPDOQuery &$query){
        $query
->innerJoin('modTemplateVarResource','TemplateVarResources');
        $query
->where(array(
           
"{$this->classKey}.parent:IN"=> $this->sectionsIDs,
           
"deleted"   =>0,
           
"published"=>1,
           
"hidemenu"  =>0,
           
"isfolder"  =>0,
           
//  "TemplateVarResources.value:LIKE" => "%||%",
           
// "TemplateVarResources.tmplvarid"    => 11,
       
));
       
return parent::prepareCountQuery($query);
   
}
   
   
publicfunction afterIteration(array $list){
        $list
= parent::afterIteration($list);
       
foreach($list as& $object){
           
// Формируем районы
            $object
'raions'= array();
           
if(isset($object'tvs''tv_raions')&&!empty($object'tvs''tv_raions''value')){
                $regions
= explode("||", $object'tvs''tv_raions''value');
               
foreach($regions as $region){
                    $object
'raions'= $region;
               
}
           
}
       
}
       
return $list;
   
}
}

return'modWebNedvigimostGetdataProcessor';


здесь в методе beforeQuery еще до выполнения основного запроса, выполняем поиск разделов по условию (метод getSectionsIDs). Если разделы не будут найдены, то и конечного запроса не будет. Но уровней вложенности разделов может быть несколько, потому этот метод работает рекурсивно. Когда он отработает и найдет все разделы по условию, их ID-шники будут добавлены в условие запроса конечных элементов (методprepareCountQuery). И все, это все в одном процессор.

Далее у нас процессор, расширяющий этот процессор, но который ищет только в текущем и дочерних разделах.

<?php
require_once dirname
(dirname(__FILE__)).'/getdata.class.php';

class modWebNedvigimostSectionGetdataProcessor extendsmodWebNedvigimostGetdataProcessor{
   
protectedfunction getSectionsCondition(){
       
return array(
           
'id'    => $modx->resource->get('id'),
       
);
   
}
}
return'modWebNedvigimostSectionGetdataProcessor';


Этот процессор уже будет справедлив для всех документов разделов.

Если статья понравилась, то поделитесь ей в социальных сетях:

Понравилась статья? Подпишись и будь в курсе всех новостей!

Нажимая на кнопку, вы даете согласие на обработку своих персональных данных Политика конфиденциальности

Вам также будет интересно:

Комментарии (0)


    Vkontakte