Как сделать фильтрацию элементов по двум независимым признакам?

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

Прежде всего необходимо решить, какой из двух независимых признаков будет первичным, а какой вторичным. В качестве первичного будет уместно выбрать тот признак, по которому фильтрация может выполняться автономно. Если фильтрация может выполняться автономно по каждому из признаков, в качестве первичного следует выбрать тот признак, который представляет наибольшую значимость для сайта, например содержит основные разделы сайта. Если и тут наблюдается равнозначность признаков, в качестве первичного можно выбрать любой из признаков, главное не допускать при этом вариативность адресов, т.е. возможность использовать на равных адреса форматов /property1/property2 и /property2/property1.

Для отбора по двум независимым признакам используется сложное условие с логический связкой И (AND) между первым и вторым простыми условиями:

`field1`=значение1 AND `field2`=значение2

Для сокрытия реальных полей и значений, задействованных в этом условии, а также для автоматической проверки существования поступающих состояний фильтра можно использовать возможности G-Drive. Чтобы не хранить состояния в виде всевозможных комбинаций значений для первого и второго простых условий, можно размещать значения для первого условия в таблице категорий, а для второго – в таблице объектов. Чтобы категории ссылались на одну и ту же таблицу объектов, можно использовать представления.

Реализуем фильтрацию элементов по разделам и типам. Код создания таблицы разделов (site_categories) и примерных записей этой таблицы можно взять из предыдущей статьи, заменив имя модуля на filterp2. Создание таблицы типов и примерных записей этой таблицы (тут же можно создать упоминавшиеся выше представления):

CREATE TABLE `site_types` (
  `type` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
  `id` varchar(16) NOT NULL,
  `name` tinytext NOT NULL,
  PRIMARY KEY (`type`),
  UNIQUE (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3;

INSERT INTO `site_types` (`type`, `id`, `name`) VALUES
(1, 'type-1', 'Тип 1'),
(2, 'type-2', 'Тип 2');

CREATE ALGORITHM=MERGE VIEW `site_div_1` AS SELECT * FROM `site_types`;
CREATE ALGORITHM=MERGE VIEW `site_div_2` AS SELECT * FROM `site_types`;

Для одной из категорий можно не создавать представление, а назвать таблицу объектов в точном соответствии с предполагаемым именем этого представления, например site_div_1 вместо site_types.

Создание таблицы фильтруемых элементов и примерных записей этой таблицы:

CREATE TABLE `site_items` (
  `item` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `id` varchar(16) NOT NULL,
  `name` tinytext NOT NULL,
  `div` tinyint(3) unsigned NOT NULL,
  `type` tinyint(3) unsigned NOT NULL,
  PRIMARY KEY (`item`),
  UNIQUE (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5;

INSERT INTO `site_items` (`item`, `id`, `name`, `div`, `type`) VALUES
(1, 'item-1', 'Элемент 1', 1, 1),
(2, 'item-2', 'Элемент 2', 1, 2),
(3, 'item-3', 'Элемент 3', 2, 1),
(4, 'item-4', 'Элемент 4', 2, 2);

Основной код модуля (filterp2.d.php):

<?php

$pp=10;
$fields='';
$tables='';
$condition='';

if (strlen($p0))
{
  $fields.=',"'.$r0['id'].'" `div_id`,"'.$r0['name'].'" `div_name`';
  $condition.=' WHERE `div`='.$r0['div'];
}
else
{
  $fields.=',`categories`.`id` `div_id`,`categories`.`name` `div_name`';
  $tables.=' LEFT JOIN `categories` USING(`div`)';
}

if (strlen($p1))
{
  $fields.=',"'.$r1['id'].'" `type_id`,"'.$r1['name'].'" `type_name`';
  $condition.=' AND `type`='.$r1['type'];
}
else
{
  $fields.=',`types`.`id` `type_id`,`types`.`name` `type_name`';
  $tables.=' LEFT JOIN `types` USING(`type`)';
}

if ($result=mysqli_query($link,'SELECT FLOOR((COUNT(*)+'.($pp-1).')/'.$pp.') FROM `items`'.$condition))
{
  list($pc)=mysqli_fetch_row($result);
  mysqli_free_result($result);

  require(PATH.'include/rotate01.php');
  if ($pn==0||$pn>$pc) error(404);

  elseif ($result=mysqli_query($link,'SELECT `items`.*'.$fields.' FROM `items`'.$tables.$condition.' ORDER BY `item` LIMIT '.(($pn-1)*$pp).','.$pp)) require(PATH.'include/getrow.php');
  else error(503);
}
else error(503);

В приведенном коде опущен табличный префикс (site_). Код ротатора номеров страниц 0 и 1 (include/rotate01.php) описан в заключительной части статьи Как сделать пагинацию? и в первом комментарии к ней. Содержимое включаемого файла include/getrow.php (оно будет использовано в шаблоне страниц фильтра) представлено в статье Как сделать вывод списка статей?

Простейший шаблон страниц фильтра (filterp2.php):

<h1><?= $page['name'] ?> (стр. <?= $pn ?> из <?= $pc ?>)</h1>

<?php while ($row=getrow()): ?>
<h2><?= $row['name'] ?></h2>
<p>
  <a href="/<?= $row['div_id'] ?>"><?= $row['div_name'] ?></a>
/
  <a href="/<?= $row['div_id'] ?>/<?= $row['type_id'] ?>"><?= $row['type_name'] ?></a>
</p>
<?php endwhile; ?>

Для автономной фильтрации по типам можно создать специальный раздел div-all (или types, чтобы не создавать соответствующее представление) с нулевым идентификатором и изменить условие первого ветвления на $r0['div'] или усложнить его до strlen($p0)&&$r0['div']. При этом, чтобы не разрушалось условие запроса, нужно изменить исходное значение переменной $condition на 'WHERE 1' и заменить в коде ветвления слово WHERE на слово AND. Чтобы сделать недействительным адрес /div-all (или /types), в котором не указан тип, можно установить для категории режим разрешений 3 вместо 2 (значение поля bits 95 вместо 94). Также можно совместить данный раздел со специальным разделом с отключенной фильтрацией, о создании которого рассказывалось в предыдущей статье, как это показано ниже.

Демонстрация: /filter-p2 и с автономной фильтрацией по типам /filter-p2/type-1 и /filter-p2/type-2.

Комментарии: 0

Отправить комментарий

Ваш адрес E-mail не будет опубликован.