Пример хранения списка в базе данных
Допустим, мы хотим сделать отображение на сайте простого списка данных − например, каталога книг. При этом все данные должны храниться в базе данных, в нашем случае в таблице books. Чтобы задание не выглядело совсем уж примитивным, добавим несколько дополнительных условий:
у каждой книги, кроме названия и автора, есть картинка-превьюшка, а также год издания;
есть возможность сортировки списка по названию, по автору, а также по году издания;
список выводится постранично, не более 5 книг на одной странице;
наконец, все ссылки должны быть в "красивом", человеко-понятном формате.
Приступим.
Шаг 1. Создаем таблицу и добавляем в нее пробные данные
Таблица будет называться books и будет иметь следующие поля:
id: уникальный идентификатор записи (число + счетчик);
title: название книги (строка);
author: автор книги в формате "Фамилия, Имя" (строка);
image: путь к картинке-превьюшке (строка);
year: год издания (число).
Создать таблицу можно самостоятельно, при помощи стандартного плагина "Управление БД", либо выполнив уже готовый SQL-запрос:
CREATE TABLE books (
id INT NOT NULL AUTO_INCREMENT,
title TINYTEXT NOT NULL,
author TINYTEXT NOT NULL,
image TINYTEXT NOT NULL,
year INT NOT NULL,
PRIMARY KEY (id));
Теперь добавим в таблицу тестовые данные. Вы можете воспользоваться следующим SQL-запросом:
INSERT INTO books VALUES (1,'Ковчег на острове','Даррелл, Джеральд','/images/books/ark.jpg',1976);
INSERT INTO books VALUES (2,'Зоопарк в моем багаже','Даррелл, Джеральд','/images/books/zoo.jpg',1960);
INSERT INTO books VALUES (3,'Конец Вечности','Азимов, Айзек','/images/books/eternity.jpg',1955);
INSERT INTO books VALUES (4,'Полдень, XXII век','Стругацкие, Аркадий и Борис','/images/books/century.jpg',1965);
INSERT INTO books VALUES (5,'Трудно быть богом','Стругацкие, Аркадий и Борис','/images/books/god.jpg',1974);
INSERT INTO books VALUES (6,'Пикник на обочине','Стругацкие, Аркадий и Борис','/images/books/picnic.jpg',1971);
INSERT INTO books VALUES (7,'Неукротимая Планета','Гаррисон, Гарри','/images/books/pirr.jpg',1960);
INSERT INTO books VALUES (8,'Конные Варвары','Гаррисон, Гарри','/images/books/varvars.jpg',1968);
Обратите внимание, как выглядят ссылки на изображения: в виде абсолютного пути к файлу, от корневой папки сайта, и каждая ссылка начинается со слеша (/). Такие ссылки удобны для использования в шаблонах.
Скачать набор изображений для этого примера можно здесь.
Шаг 2. Пробное чтение и вывод данных
По всем правилам архитектуры ACMS, для отображения списка книг у нас будет служить страница books, к которой будет привязан одноименный пакет − для подготовки данных, и одноименный шаблон − для их отображения. Начнем с создания страницы. Добавьте ее в панели управления (Страницы сайта − Создать):
Остальные поля формы оставьте как есть. Сохраните страницу.
Теперь нужно создать пакет, который будет выбирать данные о книгах из БД, и подготавливать их для отображения. Для начала создадим очень простой пакет, который не будет учитывать ни порядка сортировки, ни номера страницы. Все, что он будет делать (пока) − лишь выбирать все записи из таблицы books и сохранять их в переменной. Создайте новый пакет в панели управления (Пакеты − Создать):
// Отображение списка книг
$books=$database->getLines("books");
$booksInfo=compact("books");Обратите внимание, что мы сохраняем данные о книгах в ключе "books" массива $booksInfo, вместо того, чтобы просто сохранить список книг в какой-нибудь переменной. Это считается хорошим стилем программирования на ACMS. Декорированное имя переменной (booksInfo вместо books) гарантирует нам, что эта переменная не будет затерта каким-нибудь другим пакетом, а использование ассоциативного массива позволит нам в той же самой переменной передавать и другую информацию − например, список страниц.
Теперь самое время создать шаблон, который будет отображать эти данные на экране. Для красоты и для удобства дальнейшей работы мы создадим не один шаблон, а три − для отображения всей страницы, для отображения декорированного списка книг (без содержимого) и для отображения информации об одной книге.
Шаблон books (этому шаблону, поскольку он отображает всю страницу, нужно указать предка general):
<?-- Отображение списка книг --?>
<area:content>
<insert:htmlBooks>
<logic:iterator property=booksInfo:books item="book">
<insert:htmlBook book=book/>
</logic:iterator>
</insert:htmlBooks>
</area:content>
Текст этого шаблона нуждается в пояснении. Мы используем вспомогательный шаблон htmlBooks, чтобы обернуть часть шаблона, начиная со спецтега <insert:htmlBooks> и заканчивая спецтегом </insert:htmlBooks>. Это называется "враппингом", или "оборачиванием" − шаблон как бы охватывает собой часть другого шаблона и позволяет красиво оформить эту часть. Внутри же обернутой области мы видим итератор. Он пробегает по всем элементам массива $booksInfo["books"], и для каждого элемента вызывает другой вспомогательный шаблон, htmlBook. При этом итератор передает ему данные о текущей книге, параметром book.
Таким образом, схему работы этого шаблона можно пересказать так:
1. открыть шаблон htmlBooks (начать отображение списка книг);
2. пробежаться в цикле по массиву $booksInfo["books"];
3. для каждого элемента вызвать шаблон htmlBook;
4. закрыть шаблон htmlBooks (закончить отображение списка книг).
Шаблон htmlBooks:
<?-- Оформленный список книг --?>
<?-- Содержимое списка передается параметром content --?>
<table border="1">
<tr>
<td align="center"><b>Обложка</b></td>
<td align="center"><b>Название</b></td>
<td align="center"><b>Автор</b></td>
<td align="center"><b>Год</b></td>
</tr>
<var:content>
</table>
Шаблон htmlBook:
<?-- Оформленные данные о книге --?>
<?-- Данные о книге передаются параметром book --?>
<tr>
<td><img src="<var:book:image>"></td>
<td><var:book:title></td>
<td><var:book:author></td>
<td><var:book:year></td>
</tr>
Пора посмотреть, что у нас получилось. Откройте страницу books на вашем сайте. Если вы все сделали правильно, на экране должна появиться таблица, приблизительно такого вида:

Шаг 3. Добавляем сортировку данных
Следующая задача − добавить в наш книжный каталог поддержку сортировки (по полям "Название", "Автор", "Год"), а также поддержку разбивки списка на страницы. Это уже нетривиальная задача, поэтому договоримся о соглашениях:
сортировка первична, разбивка на страницы вторична. Сперва сортируем, потом определяем диапазон выборки;
при изменении порядка сортировки происходит автоматический возврат на первую страницу каталога;
порядок сортировки передается строковым параметром order (значения "title", "author" или "year");
номер страницы передается целочисленным параметром page, начиная с единицы;
оба параметра являются опциональными (необязательными);
если параметр order не указан или имеет неверное значение, список сортируется по названию;
если параметр page не указан или имеет неверное значение, отображаем первую страницу каталога.
Для начала определимся со значениями по умолчанию. Сотрем полностью старый текст пакета и добавим вместо него следующий код:
// Количество книг на странице и порядок сортировки по умолчанию
$pageSize=5;
$defaultOrder="title";
Если в будущем нам потребуется изменить порядок сортировки по умолчанию или размер страницы, достаточно будет изменить эти значения.
Теперь определим набор способов сортировки данных. Удобнее всего задать их в виде ассоциативного массива, в котором ключи − это названия сортировки (значение параметра order), а значения − дополнительные параметры сортировки:
// Определяем набор правил сортировки
$ordering=array(
"title"=>array("page"=>"books","params"=>array("order"=>"title"),"fields"=>"title"),
"author"=>array("page"=>"books","params"=>array("order"=>"author"),"fields"=>"author,title"),
"year"=>array("page"=>"books","params"=>array("order"=>"year"),"fields"=>"year"));Как видите, набор параметров включает в себя следующие поля:
page: название страницы с каталогом, для генерирования ссылки;
params: набор параметров для генерирования ссылки;
fields: набор полей, по которым в том или ином случае будут отсортированы данные.
Полями page и params мы воспользуемся в шаблоне, в специальных тегах <write:link> и <write:anchor>. Они удобны для создания ссылок. По мере надобности мы будем добавлять к ним еще один параметр − номер страницы.
Также обратите внимание на небольшой нюанс: для сортировки по автору (author) значение параметра fields состоит не из одного поля, а из двух (author, title). Это связано с тем, что в нашем каталоге авторы могут повторяться, и в пределах одного и того же автора удобнее дополнительно сортировать записи еще и по названию произведения.
На очереди определение текущего способа сортировки. Добавим в пакет следующий код:
// Определяем текущий порядок сортировки
$order=acceptAlphaParameter("order");
if(!isset($ordering[$order])) $order=$defaultOrder;
$ordering[$order]["current"]=true;
$orderFields=$ordering[$order]["fields"];Переменная $orderFields − и есть SQL-порядок сортировки данных. Он понадобится нам чуть позже.
Шаг 4. Добавляем разбивку на страницы и список страниц
Как было упомянуто выше, номер страницы передается необязательным параметром page, который может принимать значения от 1 до N (максимальное количество страниц). Для того, чтобы корректно принять этот параметр, нужно знать его максимальное значение, то есть общее количество страниц, необходимых для отображения всего каталога. Для этого добавим в пакет следующий код:
// Определяем общее количество страниц и номер текущей страницы
$booksCount=$database->getLinesCount("books");
$pagesCount=ceil($booksCount/$pageSize);
$page=acceptIntParameter("page",1,$pagesCount);Логика работы этого фрагмента проста: сначала определяем, сколько всего записей в таблице books (переменная $booksCount), затем, зная размер одной страницы (переменная $pageSize), определяем количество страниц (переменная $pagesCount), наконец, используя это значение, принимаем параметр page и сохраняем номер текущей страницы в переменной $page. Обратите внимание, что переменная $page всегда будет иметь значение как минимум единицы, даже если параметр page не был указан, даже если в таблице нет ни одной записи.
Итак, подготовительные действия выполнены, все параметры учтены. Пора сделать главное − выборку списка книг:
// Определяем смещение и выбираем список книг,
// согласно порядку сортировки и номеру страницы
$offset=($page-1)*$pageSize;
$books=$database->getOrderedLinesRange("books",$orderFields,$offset,$pageSize);Заодно подготовим и полный список страниц, который будет использован для отображения навигации:
// Если нужно − создаем навигатор (список ссылок на страницы)
if($pagesCount>1) {
$navigation=createSimpleNavigation(1,$pagesCount,$page);
$navigation["ordering"]=$ordering[$order];
}
else unset($navigation);Переменная $navigation создается только когда страниц две или более. В противном случае навигатор нам ни к чему. Для создания навигации используется стандартная функция createSimpleNavigation. Кроме того, в готовый навигатор вручную добавляется еще один ключ, ordering, который хранит информацию о текущем способе сортировки. Все это пригодится нам в шаблоне.
Заключительный шаг в создании полноценного пакета − это сборка переменной $booksInfo:
// Наконец, создаем переменную, которая хранит в себе все нужные данные
// и которую мы будем использовать в шаблоне
$booksInfo=compact("books","ordering","navigation");Шаг 5. Модифицируем шаблоны
Если сейчас открыть страницу books сайта, мы увидим, что почти ничего не изменилось. Единственные отличия от того, что было раньше − книги начали отображаться в алфавитном порядке, а не как попало, и начали отображаться только первые пять книг, то есть первая страница каталога. Но нет ни возможности изменить порядок сортировки, ни перейти на другую страницу каталога. Исправим это упущение. Для начала, внесем исправления в шаблон htmlBooks:
<?-- Оформленный список книг --?>
<?-- Содержимое списка передается параметром content --?>
<?-- Способы сортировки передаются параметром ordering --?>
<table border="1">
<tr>
<td align="center"><b>Обложка</b></td>
<td align="center"><b><write:anchor property=ordering:title:page params=ordering:title:params content="Название"></b></td>
<td align="center"><b><write:anchor property=ordering:author:page params=ordering:author:params content="Автор"></b></td>
<td align="center"><b><write:anchor property=ordering:year:page params=ordering:year:params content="Год"></b></td>
</tr>
<var:content>
</table>
Как видите, шаблон несколько усложнился. Вместо простых текстов в шапке таблицы теперь там выводятся ссылки, которые генерируются спецтегом <write:anchor>, и которые позволят посетителю сортировать список по тому или иному полю. Каждому спецтегу передается набор параметров, позволяющий сформировать ссылку правильно. Эти параметры берутся из переменной $ordering, которую нужно будет указать при вызове шаблона.
Заодно создадим новый шаблон, htmlBooksNavigation:
<?-- Навигация для списка книг --?>
<?-- Данные навигатора передаются параметром navigation --?>
Страницы: <logic:iterator property=navigation:range item="item">
<?-- Текущая страница отображается в скобках --?>
<logic:present property=item:current>
[<var:item:label>]
</logic:present>
<?-- Остальные страницы отображаются как ссылки --?>
<logic:notPresent property=item:current>
<write:anchor property=navigation:ordering:page
params=navigation:ordering:params page=item:value content=item:label>
</logic:notPresent>
</logic:iterator><br>
И, наконец, внесем исправления в главный шаблон books, чтобы учесть все сделанные нами дополнения и изменения:
<?-- Отображение списка книг --?>
<area:content>
<?-- Собственно каталог --?>
<insert:htmlBooks ordering=booksInfo:ordering>
<logic:iterator property=booksInfo:books item="book">
<insert:htmlBook book=book/>
</logic:iterator>
</insert:htmlBooks>
<?-- Постраничная навигация (если есть) --?>
<logic:present property=booksInfo:navigation>
<insert:htmlBooksNavigation navigation=booksInfo:navigation/>
</logic:present>
</area:content>
Работа почти завершена. Если теперь открыть страницу каталога на сайте, должно отобразиться следующее:

Щелкая по ссылкам в шапке таблицы, можно управлять порядком сортировки, а щелкая по навигатору внизу, можно переходить с одной страницы каталога на другую.
Шаг 6. Делаем красивые ссылки
Если вы пригляделись к ссылкам, которые появляются в шапке таблицы, а также в постраничном навигаторе внизу, то заметили, что они имеют очень простой (и не самый красивый) формат: books?order=title, books?order=author, books?order=year, books?page=2&order=year и так далее. Хотелось бы увидеть более изящные ссылки, которые хранят значения параметров прямо в пути к странице сайта. Поскольку для вывода ссылок в шаблонах мы пользовались только спецтегами write:link и write:anchor, сделать это будет очень просто.
Найдите в панели управления закладку "Представления ссылок" и добавьте два новых правила:
| Страница | Паттерн | Умолчания | Порядок |
|---|
| books | books/sort/$order/page$page | | 1 |
| books | books/sort/$order | | 2 |
Хотите − верьте, хотите − нет, но этого вполне достаточно. Все ссылки на сайте изменят свой вид на более простой и удобочитаемый, и по-прежнему будут правильно восприниматься нашим пакетом, который принимает значения параметров. Удобное управление внутренними ссылками − одна из полезных возможностей, предоставляемых ACMS.
Приложение. Полный текст пакета books
Далее приведен полный текст пакета books, который должен у вас получиться в конечном итоге.
// Отображение списка книг
// Количество книг на странице и порядок сортировки по умолчанию
$pageSize=5;
$defaultOrder="title";
// Определяем набор правил сортировки
$ordering=array(
"title"=>array("page"=>"books","params"=>array("order"=>"title"),"fields"=>"title"),
"author"=>array("page"=>"books","params"=>array("order"=>"author"),"fields"=>"author,title"),
"year"=>array("page"=>"books","params"=>array("order"=>"year"),"fields"=>"year"));
// Определяем текущий порядок сортировки
$order=acceptAlphaParameter("order");
if(!isset($ordering[$order])) $order=$defaultOrder;
$ordering[$order]["current"]=true;
$orderFields=$ordering[$order]["fields"];
// Определяем общее количество страниц и номер текущей страницы
$booksCount=$database->getLinesCount("books");
$pagesCount=ceil($booksCount/$pageSize);
$page=acceptIntParameter("page",1,$pagesCount);
// Определяем смещение и выбираем список книг,
// согласно порядку сортировки и номеру страницы
$offset=($page-1)*$pageSize;
$books=$database->getOrderedLinesRange("books",$orderFields,$offset,$pageSize);
// Если нужно − создаем навигатор (список ссылок на страницы)
if($pagesCount>1) {
$navigation=createSimpleNavigation(1,$pagesCount,$page);
$navigation["ordering"]=$ordering[$order];
}
else unset($navigation);
// Наконец, создаем переменную, которая хранит в себе все нужные данные
// и которую мы будем использовать в шаблоне
$booksInfo=compact("books","ordering","navigation");