На многих сайтах используются каталоги с фильтрацией элементов по уровню иерархии. Как сделать подобный каталог на G-Drive, можно прочитать в данной статье.
Каталог будет двухуровневым с отражением иерархии в адресах. В G-Drive такая иерархия поддерживается автоматически. Кроме того, в каталог включена поддержка вывода списка всех элементов на определенной странице сайта. Для распознавания этой страницы в коде используется условие, противоположное $r0['div']
, что соответствует странице со значением 0 или NULL в $r0['div'].
Связь элементов с иерархической структурой осуществляется по одному полю вне зависимости от поддерживаемого уровня вложенности узловых элементов. Для определенности назовем узловые элементы первого уровня разделами, а второго – категориями (не путать с категориями в терминологии G-Drive, где они занимают первый уровень иерархии). Фильтруемые элементы связываются с категориями, а категории – с разделами. Прямая связь между фильтруемыми элементами и разделами не поддерживается, т.е. элементы не могут находиться непосредственно в разделах. Если возникает затруднение с выбором категории для отдельных элементов раздела, для них можно создать, например, категорию «Другое» в этом разделе. Для элементов, категорий и разделов используются три отдельные таблицы. Кроме того, таблицу категорий можно разделить на отдельные таблицы по одной для каждого раздела (в качестве альтернативы использованию в запросе условия для выбора раздела), однако в коде отдано предпочтение использованию единой таблицы категорий, а для обеспечения автоматического роутинга G-Drive задействованы представления, разделяющие таблицу категорий по разделам.
Создание таблицы элементов и примерных записей этой таблицы:
CREATE TABLE `site_items` ( `item` int(10) unsigned NOT NULL AUTO_INCREMENT, `cat` tinyint(3) unsigned NOT NULL, `id` varchar(16) NOT NULL, `name` tinytext NOT NULL, PRIMARY KEY (`item`), KEY (`cat`), UNIQUE (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5; INSERT INTO `site_items` (`item`, `cat`, `id`, `name`) VALUES (1, 1, 'item-1', 'Элемент 1'), (2, 2, 'item-2', 'Элемент 2'), (3, 3, 'item-3', 'Элемент 3'), (4, 4, 'item-4', 'Элемент 4');
Создание таблицы категорий и примерных записей этой таблицы (тут же можно создать представления, разделяющие таблицу категорий по разделам):
CREATE TABLE `site_category` ( `cat` tinyint(3) unsigned NOT NULL AUTO_INCREMENT, `div` tinyint(3) unsigned NOT NULL, `id` varchar(16) NOT NULL, `name` tinytext NOT NULL, PRIMARY KEY (`cat`), UNIQUE (`div`, `id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5; INSERT INTO `site_category` (`cat`, `div`, `id`, `name`) VALUES (1, 1, 'cat-1', 'Категория 1 раздела 1'), (2, 1, 'cat-2', 'Категория 2 раздела 1'), (3, 2, 'cat-1', 'Категория 1 раздела 2'), (4, 2, 'cat-2', 'Категория 2 раздела 2'); CREATE ALGORITHM=MERGE VIEW `site_div_1` AS SELECT `cat`, `id`, `name` FROM `site_category` WHERE `div`=1; CREATE ALGORITHM=MERGE VIEW `site_div_2` AS SELECT `cat`, `id`, `name` FROM `site_category` WHERE `div`=2;
Создание таблицы разделов и примерных записей этой таблицы (таблица должна быть совмещена с таблицей категорий в терминологии G-Drive):
CREATE TABLE `site_categories` ( `div` tinyint(3) unsigned NOT NULL, `id` varchar(16) NOT NULL, `name` tinytext NOT NULL, `module` varchar(16) NOT NULL, `bits` tinyint(3) unsigned NOT NULL, PRIMARY KEY (`id`), KEY (`div`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `site_categories` (`div`, `id`, `name`, `module`, `bits`) VALUES (0, 'filter-l2', 'Каталог', 'filterl2', 52), (1, 'div-1', 'Раздел 1', 'filterl2', 62), (2, 'div-2', 'Раздел 2', 'filterl2', 62);
Поле div объявлено обычным ключом, чтобы можно было идентифицировать не относящиеся к разделам каталога записи одним и тем же значением этого поля, например нулевым. Если использовать какой-то другой признак, отделяющий относящиеся к разделам каталога записи от всех прочих, данное поле можно сделать и первичным ключом, и автоинкрементальным. Также можно объявить поле div уникальным ключом, убрав из его определения «NOT NULL».
Основной код модуля (filterl2.c.php):
<?php require INCLUDE_PATH.'query.php'; require INCLUDE_PATH.'getrow.php'; require INCLUDE_PATH.'pagelink.php'; $pp = 10; $fields = []; $tables = []; $where = ''; if ($r0['div']) { array_unshift($fields, ",'{$r0['id']}' `div_id`,'{$link->escape_string($r0['name'])}' `div_name`"); $where = " AND `div`={$r0['div']}"; } else { array_unshift($fields, ',`categories`.`id` `div_id`,`categories`.`name` `div_name`'); array_unshift($tables, ' LEFT JOIN `categories` USING(`div`)'); } if (isset($r1)) { array_unshift($fields, ",'{$r1['id']}' `cat_id`,'{$link->escape_string($r1['name'])}' `cat_name`"); $where = " AND `cat`={$r1['cat']}"; } else { array_unshift($fields, ',`category`.`id` `cat_id`,`category`.`name` `cat_name`'); array_unshift($tables, ' LEFT JOIN `category` USING(`cat`)'); } $fields = '`items`.*'.implode($fields); $tables = '`items`'.implode($tables); $where = '1'.$where; $result = query("SELECT FLOOR((COUNT(*)+".($pp - 1).")/$pp) FROM $tables WHERE $where"); list($pc) = $result->fetch_row(); $result->free(); rotate01(); if ($pn == 0 || $pn > $pc) { error(404); return; } $result = query("SELECT $fields FROM $tables WHERE $where ORDER BY `item` LIMIT ".(($pn - 1) * $pp).",$pp");
В приведенном коде опущен табличный префикс (site_). Код ротатора номеров страниц 0 и 1 (функции rotate01) описан в заключительной части статьи Как сделать пагинацию? и в первом комментарии к ней. Содержимое включаемого файла getrow.php (оно будет использовано в шаблоне страниц) представлено в статье Как сделать вывод списка статей?
Простейший шаблон страниц каталога (filterl2.php):
<h1><?= $page['name'] ?> (стр. <?= $pn ?> из <?= $pc ?>)</h1> <?php while ($row = getrow($result)): ?> <h2><?= $row['name'] ?></h2> <p> <a href="/<?= $row['div_id'] ?>"><?= $row['div_name'] ?></a> / <a href="/<?= $row['div_id'] ?>/<?= $row['cat_id'] ?>"><?= $row['cat_name'] ?></a> </p> <?php endwhile; ?>
Демонстрация: /filter-l2 (для наглядности в шаблон был добавлен вывод текста основного запроса к базе данных для конкретной страницы, т.к. формированию подобных запросов в статье было уделено не слишком много внимания).
Для страницы со списком всех элементов (т.е. с отключенной фильтрацией) в первом запросе (с
COUNT(*)
) нет необходимости использовать LEFT JOIN. А вот для страниц разделов присоединение таблицы категорий необходимо, т.к. элементы не связаны напрямую с разделами. Без этого присоединения будет недоступно поле div, которое используется в условии отбора.При определенных условиях использовать представления не совсем удобно. Чтобы G-Drive не пытался выполнять запросы к таблице site_category через представления site_div_1, site_div_2 и т.п., нужно изменить режим разрешений с 2 на 1 в записях разделов (младшие два бита поля bits) и взять на себя часть работы, выполняемой G-Drive, а именно добавить код для выборки записи запрошенной категории при появлении двухкомпонентного пути:
Обновил код, сделав конструкцию WHERE обязательной для любого запроса, в том числе выбирающего все элементы, т.к. в реальности практически всегда присутствует какое-то дополнительное условие, например постоянно ограничивающее видимость определенных элементов. Так вот это условие можно указать в качестве начального вместо '1'. Обратите внимание, что если в этом условии содержится операция OR, то может потребоваться заключить его в скобки.