Сортировка и гибкая выборка по комментариям


После моих неудачных попыток приджойнить на лету нужное мне количество комментариев и сортировать по ним, пришлось идти другим путём.

Минусы джойна не давали нормально работать:
  1. Невозможность выбрать только тикеты, у которых комментариев >= 10
  2. Невозможность подсчитать только комментарии первого уровня (в моём случае это количество вопросов без ответов) и сортировать по ним
  3. При загрузке страницы и при пролистывани через пагинацию подгружались разные тикеты и сортировались по разному.
  4. Приходилось выбирать все тикеты, и в чанке через условие прятать те, у которых комментариев < 10
  5. Отсюда приходилось и пагинацию делать на костылях
В итоге перешел к варианту «Б»:
  1. Создаём расширенное поле в БД (можно и тв-шку использовать, но так оптимальнее)
  2. Создаём плагин для нового поля (или дописываем существующий, если расширенные поля уже имеются).
  3. Создаём плагин для копирования количества комментариев в наше созданное поле с учётом нужных нам условий выборки.
  4. Импортируем все существующие уже комментарии
  5. Исправляем вызовы сниппетов и чанков

Итак, всё по порядку:

1. Создаём расширенное поле в БД
Создаём в таблице modx_site_content новое поле ext_comments:
  • Тип: INT
  • Длина: 10
  • По умолчанию: 0
  • Атрибуты: UNSIGNED


2. Создаём плагин для нового поля

Приведу сразу мой сокращённый плагин с примером нескольких расширенных полей:
<?php
switch ($modx->event->name) {
	case 'OnMODXInit':

		// Загружаем поля в модель ресурса
		$modx->map['modResource']['fields']['ext_hot'] = 
		$modx->map['modResource']['fields']['ext_vip'] = 
	    $modx->map['modResource']['fields']['ext_comments'] = 
	    0;

		$modx->map['modResource']['fieldMeta']['ext_hot'] =
		$modx->map['modResource']['fieldMeta']['ext_vip'] =
		array(
			'dbtype' => 'tinyint',
			'precision' => 1,
			'attributes' => 'unsigned',
			'phptype' => 'integer',
			'default' => 0,
		);

        // Наше поле
		$modx->map['modResource']['fieldMeta']['ext_comments'] = 
		array(
			'dbtype' => 'int',
			'precision' => 10,
			'attributes' => 'unsigned',
			'phptype' => 'integer',
			'default' => 0,
		);
		
		break;
	case 'OnDocFormSave':
		// Сохраняем ТВ в поле таблицы ресурса
		$resource->ext_hot = $resource->getTVValue('hot');
		$resource->ext_vip = $resource->getTVValue('vip');
		$resource->save();
	    break;
}
Я привёл пример с 3-мя полями и синхронизацией с ТВ. Наше поле ext_comments не нуждается в тв-шке, оно автономно

3. Создаём плагин для копирования количества комментариев в наше поле

Я создал плагин, реагирующий на любые действия с комментариями (события OnCommentDelete, OnCommentPublish, OnCommentRemove, OnCommentSave, OnCommentUndelete, OnCommentUnpublish):

<?php
$obj = $object->toArray();
$thread = $obj['thread'];

$result = $modx->query("SELECT resource, comments FROM modx_tickets_threads WHERE id = '{$thread}'");
$row = $result->fetch(PDO::FETCH_ASSOC);
$resource = $row['resource'];
$comments = $row['comments'];

$result2 = $modx->query("SELECT parent FROM modx_site_content WHERE id = '{$resource}'");
$row2 = $result2->fetch(PDO::FETCH_ASSOC);
$parent = $row2['parent'];

// Посчёт только ответов в нужном нам разделе (комментарии 1-го уровня)
if($parent == 364375) {
    $q = $modx->prepare("SELECT id FROM modx_tickets_comments WHERE thread = '{$thread}' AND parent = 0 AND published = 1");
    $q->execute();
    $r = $q->fetchAll(PDO::FETCH_ASSOC);
    $comments = count($r);
}

if($resource&&$comments) {
    $q_upd = $modx->prepare("UPDATE modx_site_content SET ext_comments = '{$comments}' where id ='{$resource}'");
    $q_upd->execute();
}
// $modx->log( xPDO::LOG_LEVEL_ERROR, "RES - COM - PAR: ".$resource.", ".$comments.", ".$parent );


4. Импортируем все существующие уже комментарии

Открываем компонент Console и вбиваем туда код для импорта количества комментариев в наше поле ext_comments:

<?php
$q = $modx->prepare("SELECT id, resource, comments FROM modx_tickets_threads WHERE comments != '0'");
$q->execute();
$r = $q->fetchAll(PDO::FETCH_ASSOC);
print "<b>Всего: ".count($r)."</b>
";

foreach($r as $row){
    $thread = $row['id'];
    $resource = $row['resource'];
    $comments = $row['comments']; // количество комментариев уже есть в ветке

    $result2 = $modx->query("SELECT parent FROM modx_site_content WHERE id = '{$resource}'");
    $row2 = $result2->fetch(PDO::FETCH_ASSOC);
    $parent = $row2['parent'];
    
    // Посчёт только ответов (комментарии 1-го уровня)
    if($parent == 364375) {
        $q = $modx->prepare("SELECT id FROM modx_tickets_comments WHERE thread = '{$thread}' AND parent = 0 AND published = 1");
        $q->execute();
        $r = $q->fetchAll(PDO::FETCH_ASSOC);
        $comments = count($r);
    }
    
    if($resource&&$comments) {
        $q_upd = $modx->prepare("UPDATE modx_site_content SET ext_comments = '{$comments}' where id ='{$resource}'");
        $q_upd->execute();
    }
    print $resource." - ".$comments." - ".$parent."
";

}


$memory = round(memory_get_usage(true)/1024/1024, 4).' Mb';
print "

***********************************************
<div>Memory: {$memory}</div>";
$totalTime= (microtime(true) - $modx->startTime);
$queryTime= $modx->queryTime;
$queryTime= sprintf("%2.4f s", $queryTime);
$queries= isset ($modx->executedQueries) ? $modx->executedQueries : 0;
$totalTime= sprintf("%2.4f s", $totalTime);
$phpTime= $totalTime - $queryTime;
$phpTime= sprintf("%2.4f s", $phpTime);
print "<div>TotalTime: {$totalTime}</div>";

5. Исправляем вызовы сниппетов и чанков

Теперь уже можно указать новое поле в вызова сниппетов для ограничение выборки по ним и сортировки.
Вот сокращённый пример сниппета (феном):
{set $results = $_modx->runSnippet('!pdoPage', [
    'element' => 'getTickets',
    'tpl' => 'tpl.consultant.card',
    'sortdir' => 'DESC',
    'sortby' => 'ext_comments',
    'includeContent' => 1,
    'showHidden' => 1,
    'limit' => 10,
    'parents' => 364375,
    'totalVar' => 'totalVar',
    'where' => '{ "ext_comments:>=":"10" }',
    
    'tplPageWrapper' => '@INLINE <div class="pagination uk-margin-remove"><ul class="pagination uk-margin-remove">{$first}{$prev}{$pages}{$next}{$last}</ul></div>',
    
	'loadModels' => 'ms2gallery',
    'leftJoin' => '{
		"95x125": { "class":"msResourceFile","alias":"95x125", "on": "95x125.resource_id = Ticket.id AND 95x125.path LIKE \'%/95x125/\' AND 95x125.rank=0" }
	}',
    'select' => '{
		"Ticket":"*",
		"95x125":"95x125.url as 95x125"
	}',
])}
Тут я выбираю только лучшие тикеты (те у которых комментариев >= 10). Также я выкинул из джойна мои предыдущие опыты с подсчётом комментариев на лету и остались только подключение фотки из ms2Gallery.

В чанках тоже везде используем плейсхолдер ext_comments (например {$ext_comments} для фенома)

===================================================
Ура! Так и хочется похлопать в ладоши — у меня наконец-то всё работает именно так как мне нужно и без глюков!
████▐▐▐██████████
███▐▐▐▐███▌▌▌████
███───▐▌██▌▌▌▌███
███▄──▄▄█▐▌───███
█████████▄▄──▄███

Теперь можно и чайку попить...
───))░░░((
──((░-:¦:-░))
───))░░((
─█▀▀▀▀▀█▄░
─█░░░░░█─█
─▀▄▄▄▄▄▀▀░

Меня результат устроил, хотя может быть можно всё и более элегантно сделать… =)
22 july 2016, 08:46    Василий Столейков   G+  
4    438 +10

Comments (7)

  1. Яркий Слава 22 july 2016, 10:01 # 0
    Мощно
    1. Василий Столейков 22 july 2016, 10:10 # 0
      Спасибо, решил: зачем труду пропадать — другим пригодится, да и себе чтобы сохранилось. ;)
    2. Павел Левин 28 july 2016, 00:50 # 0
      Я ничего не понял, а где глянуть рабочий пример для понимания дела?
      1. Василий Столейков 28 july 2016, 06:40 # 0
        И как ты себе это представляешь?
        Я не могу тебе дать доступ к сайту клиента, над которым я работаю. А если покажу сайт внешне, там можно будет увидеть только то, что описано в этом топике: сортировку по количеству комментариев + выборку тех тикетов, у которых комментариев больше чем 10.
        Это же пошаговая инструкция! Может быть скажешь что именно не понимаешь? Попробую объяснить…
        1. Павел Левин 28 july 2016, 16:57 # 0
          т.е. вся суть дела это сортировка комментариев?
          1. Василий Столейков 28 july 2016, 17:29 # +1
            Нет, не комментариев, а сортировка и выборка ТИКЕТОВ в зависимости от количества комментариев.
            Также в статье приводится пример сортировки только учитывая количество комментариев первого уровня не считая дочерних и наоборот.
            1. Павел Левин 28 july 2016, 17:49 # 0
              Ага, понял. Спасибо =)
              Я просто пытался найти суть/цель/назначение в начале статьи.
      You need to login to create comments.