[EVO] Переползаем с Ditto на Doclister, часть 2

В прошлой статье Grinyaha рассказал о простых примерах использования DocLister — по сути, как вместо Ditto вызвать DocLister. Я покажу пример тоже для новичков, но чуть сложнее — для понимания необходимо минимальное знание PHP.

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

Очевидное решение — в чанке товара написать:

[[DocInfo? &docid=`[+parent+]` &field=`pagetitle`]]


Написали и написали, ничего страшного. Давайте задачу усложним: пусть будет [+longtitle+], если есть, а если нет — [+pagetitle+]:

[[if? &is=`[[DocInfo? &docid=`[+parent+]` &field=`longtitle`]]:isempty` &then=`[[DocInfo? &docid=`[+parent+]` &field=`pagetitle`]]` &else=`[[DocInfo? &docid=`[+parent+]` &field=`longtitle`]]`]]


Получилось какое-то месиво из скобочек, но можно же не писать в одну строку и тоже будет нормально.

Теперь посмотрим с другой стороны. Я взял сайт с каталогом — в каталоге 168 товаров и вывел их все.

Без DocInfo на вывод понадобилось: Запросов: 8. Время: 0,2117 s.

C DocInfo — первый вариант: Запросов: 176. Время: 0,3517 s.

Второй вариант: Запросов: 680. Время: 1,3393 s.

Понятно, что 168 товаров на странице это, скорее всего, необычная ситуация, а еще есть кэш, но все же…

Как можно решить проблему?

Вариантов тоже несколько, например создать TV-параметр и вписывать в него название категории (на каком-то сайте я такое видел) руками. Или написать плагин, чтобы не руками. Это решит проблему, но создаст некоторые неудобства. Можно написать сниппет, который будет получать сразу pagetitle и longtitle и возвращать нужное — так вернемся к 176 запросам.

Но раз появились варианты с «написать», то самое время рассказать о том, как решить такую задачу с помощью DocLister.

В DocLister вся работа по выводу выполняется контроллерами, которые содержат набор необходимых для этого функций: для документов из дерева это контроллер site_content, для внешних таблиц — onetable, для каталога shopkeeper — тоже свой контроллер. Нужный контроллер выбирается с помощью параметра &controller — по умолчанию это site_content.

Можно указать и свой контроллер — если создать по определенным правилам (это есть в документации) соответствующий файл в папке DocLister/core/controller/. Другими словами, можно скопировать файл site_content.php, переименовать, поправить и указать при вызове сниппета. Но можно поступить еще хитрее.

Создадим в указанной папке файл test_site_content.php:

<?php
if (!defined('MODX_BASE_PATH')) die('HACK???');

include_once('site_content.php');
class test_site_contentDocLister extends site_contentDocLister
{
    
}

Если указать теперь при вызове &controller=`test_site_content` — то ничего не случится (: Наш контроллер полностью дублирует контроллер site_content.

А в контроллере site_content есть функция (точнее метод) getDocs, эта функция получает список документов в соответствие с параметрами сниппета. Результатом работы функции будет массив: 'id' => 'pagetitle,longtitle, все поля в общем'.

Что это дает — во-первых, мы можем получить список значений parent (то есть id категории), используя который получим, в свою очередь, значения pagetitle,longtitle категории. Во-вторых, можем добавить дополнительное поле (название категории) в массив документов, чтобы DocLister использовал это поле при выводе чанка.

И при этом нет нужды переписывать код функции целиком:


    public function getDocs($tvlist = '') {
    	$docs = parent::getDocs($tvlist);
    	//поскольку мы меняем функцию getDocs, то обращаться к оригинальной функции нужно вот таким вот образом

    	$parents = array();
    	foreach ($docs as $doc) $parents[] = $doc['parent'];
    	//теперь у нас есть массив c id категорий
    	
    	$parents = array_unique($parents);
    	//нам нужны только те, которые не повторяются

    	$parents = implode(',',$parents);
    	//сделали список
    	
    	$table = $this->modx->getFullTableName('site_content');
    	//вместо $modx здесь нужно использовать $this->modx
    	
    	$sql = "SELECT id,pagetitle,longtitle FROM $table WHERE id IN ($parents)";
    	$result = $this->modx->db->query($sql);
    	$result = $this->modx->db->makeArray($result);
    	//получили массив с id категорий и их названиями

    	$categories = array();
    	foreach ($result as $category) {
    		$categories[$category['id']] = empty($category['longtitle']) ? $category['pagetitle'] : $category['longtitle'];
    	}
    	//теперь перестроили этот массив, чтобы у нас было id => название, попутно выбираем что будет названием - pagetitle или longtitle

    	
    	foreach ($docs as &$doc) {
    		$doc['category'] = $categories[$doc['parent']];
    	}
    	//добавили дополнительное поле category, с названием категории; что в массиве документов parent, то в массиве категорий id
    	
    	$this->_docs = $docs;
    	//контроллер хранит массив документов в $this->_docs
        
        return $docs;
    	//готово!
    }

Вместо DocInfo пишем в чанке [+category+] и проверяем — Запросов: 9. Время: 0,2432 s.

В таких вот вещах и раскрывается вся cила DocLister (:

Хотел привести еще пример с SimpleGallery, но там немного сложнее задача, поэтому в другой раз, заодно можно рассмотреть и prepare.
А можно уже и не рассматривать: modx.im/blog/docs/2759.html#comment24208 (:

10 комментариев

avatar
Спасибо! (:
avatar
Спасибо, что прочитали (:
avatar
Если уж изменять контроллеры, то лучше пользоваться методами DocLister'a. Так, например, метод $this->modx->db->query($sql); лучше заменить на $this->dbQuery($sql); Это позволит в режиме отладки увидеть новые запросы. А метод $this->modx->getFullTableName('site_content'); на $this->getTable('site_content');

Для получения всех parent'ов через запятую можно воспользоваться методом implode(",", $this->getOneField('parent', true)); либо $_DL->sanitarIn($_DL->getOneField('parent', true));

Т.е. с учетом вышесказанного, итоговый код приобретает вид
public function getDocs($tvlist = '') {
        $docs = parent::getDocs($tvlist);
        //поскольку мы меняем функцию getDocs, то обращаться к оригинальной функции нужно вот таким вот образом

        $parents = array();
        $parents = $_DL->getOneField('parent', true);   //теперь у нас есть массив c уникальными id категорий
        $parents = $_DL->sanitarIn($parents); //сделали список
        
        $table = $_DL->getTable('site_content'); //тоже самое, что и $modx->getFullTableName('site_content');
        
        $sql = "SELECT id,pagetitle,longtitle FROM {$table} WHERE id IN ({$parents})";
        $result = $_DL->dbQuery($sql); //Тоже самое, что и $modx->db->query($sql);
        $result = $this->modx->db->makeArray($result);
        //получили массив с id категорий и их названиями

        $categories = array();
        foreach ($result as $category) {
                $categories[$category['id']] = empty($category['longtitle']) ? $category['pagetitle'] : $category['longtitle'];
        }
        //теперь перестроили этот массив, чтобы у нас было id => название, попутно выбираем что будет названием - pagetitle или longtitle

        
        foreach ($docs as &$doc) {
                $doc['category'] = $categories[$doc['parent']];
        }
        //добавили дополнительное поле category, с названием категории; что в массиве документов parent, то в массиве категорий id
        
        $this->_docs = $docs;
        //контроллер хранит массив документов в $this->_docs
        
        return $docs;
        //готово!
}


Ну и наконец для того, чтобы решить поставленную задачу не обязательно создавать новый контроллер. Можно воспользоваться prepare параметром. Вот пример prepare-сниппета который делает тоже самое. Боллее подробный разбор этого кода я приводил тут
<?php
if(($categories=$_extDocLister->getStore('docParents')) === null){
        $parents = $_DL->getOneField('parent', true);   //теперь у нас есть массив c уникальными id категорий
        $parents = $_DL->sanitarIn($parents); //сделали список
        $table = $_DL->getTable('site_content'); //тоже самое, что и $modx->getFullTableName('site_content');
        
        $sql = "SELECT id,pagetitle,longtitle FROM {$table} WHERE id IN ({$parents})";
        $result = $_DL->dbQuery($sql); //Тоже самое, что и $modx->db->query($sql);
        $result = $this->modx->db->makeArray($result);
        
        $categories = array();
    foreach ($result as $category) { //теперь перестроили массив, чтобы у нас было id => название, попутно выбираем что будет названием - pagetitle или longtitle
        $categories[$category['id']] = empty($category['longtitle']) ? $category['pagetitle'] : $category['longtitle'];
    }
        $_extDocLister->setStore('docParents', $data['category']);
}

//добавили дополнительное поле category, с названием категории; что в массиве документов parent, то в массиве категорий id
$data['category'] = isset($categories[$data['parent']]) ? $categories[$data['parent']] : '';

return $data;

P.S. Спасибо за статью. Довольно доступно и от простого к сложному.
Комментарий отредактирован 2014-10-31 13:38:34 пользователем Agel_Nash
avatar
Про prepare я и думал с самого начала, но рассчитывал на доступ к $this->_docs, а оказалось — private. Поэтому расширил контроллер.
Методы да, упустил. Если dbQuery и sanitarIn еще вспоминаются, то getOneField первый раз вижу. Сейчас пытаюсь перейти на PHPStorm, может тогда будет проще разбираться.
avatar
Обязательно переходи на Шторм!
avatar
Ваня, ты когда-нибудь спишь?)
avatar
вообще после часа сплю обычно)
avatar
Добрый день, гуру, подскажите (не нашел, не хватило квалификации, не предусмотрено — нужное подчеркнуть).

Суть вопроса.
На сайте имеется личный кабинет.
В личном кабинете есть форма, к форме подключен текстовый редактор tinymce.
Из формы идет запись в свою таблицу через htmlspecialchars

'".$modx->db->escape(htmlspecialchars($fields['message']))."'

В результате теги преобразовываются и записывается, например вместо

<p> Тест </p>

имеем (подчеркивание поставил для наглядности, чтобы не терялся смысл)

&_lt; p&_gt; Тест &_lt;/p&_gt;

И сам вопрос — где и как правильно и корректно прописать в DocLister (контроллере onetable)обратную функцию htmlspecialchars_decode, чтобы то что записано в базе выводилось и воспринималось не как стрелки, а как теги

<p> Тест </p>

Спасибо
  • paic
  • +1
avatar
попробуйте через prepare
создайте сниппет, например, dl.prepare
$data['message'] = htmlspecialchars_decode($data['message']);
return $data;

и в доклистере вызывается &prepare=`dl.prepare`
avatar
Спасибо!
работает
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.