Список, список, список… Открываешь файл любого интернет-проекта,
основанного на технологии PHP+MySQL, и первое, что видишь - это куски кода, примерно
такого вида:
$q = 'SELECT id, title FROM rubrics';
$result = mysql_query ($q) or die ('Select query failed');
print '<ol>';
while ($line = mysql_fetch_array ($result, MYSQL_ASSOC)) {
print '<li><a href="?id=' . $line['id'] . '">';
print $line['title'];
print '</a></li>';
}
print '</ol>';
Подобным образом выводится список ссылок на отдельные страницы
раздела сайта, хранящиеся в базе данных, фирмы в каталоге, подписчики рассылки
в интерфейсе администрирования, статистика ресурса и многое другое.
Каждый раз, набирая подобный код в PHP-редакторе, видишь, что
снова и снова проделываешь одну и ту же работу с минимальными вариациями. Различия
только в коде SQL-запроса, типе и количестве выводимых полей и в HTML-тэгах,
т.е. в дизайне. А если делаешь одну работу два раза, то это повод не только
заскучать, но и задуматься о том, как ее себе облегчить. Да и попытки немного
изменить дизайн сайта часто требуют модификации именно этого участка кода, а
значит и его последующей отладки.
На протяжении нескольких лет работы с PHP у меня выработался
определенный стандарт написания кода для вывода списков, и в результате захотелось
обобщить этот опыт и сделать нечто стандартное, подходящее для любой ситуации,
связанной с выводом результатов SQL-запроса в виде списка средствами PHP.
В результате все простые списки были препарированы на шесть
частей (это самая простая их модель):
- Обрамление списка: это то, внутри чего список находится, есть в нем хотя
бы один элемент или нет.
- Пустой список: если наш запрос не возвращает ни одной строки, то все-таки
стоит вывести на сайт некоторое пояснение, например, „список пуст”, или „редактор
в отпуске, новостей больше не будет”, или оставить здесь пустое значение.
- Начало списка: если в списке есть хотя бы один элемент, то список можно
начать какой-нибудь фразой, например, „начало списка”, или разместить здесь
заголовок, который в случае пустого списка может и не выводиться.
- Конец списка: нужен для тех же целей, что и предыдущий пункт – фраза „конец
списка” или какие-либо итоговые данные по всем строкам, которые в случае пустого
списка могут и отсутствовать.
- Элемент списка: собственно это и есть вывод данных каждой строки запроса;
в отличие от предыдущих пунктов этот элемент применяется к каждой строке запроса,
выводя её в одном и том же дизайне внутри обрамления списка.
- Разделитель элементов списка: иногда элементы списка отделяются чем-нибудь
друг от друга – запятыми, вертикальными черточками, тире и прочим, причем в начале
и конце списка эти разделители могут отсутствовать.
Вышеуказанную структуру я называю шаблоном списка. Остается
написать код некоего класса, который, имея на входе SQL-запрос, порождает объект,
содержащий результат выполнения этого запроса. В классе также требуется метод,
который на основании разработанного выше шаблона выдаст нам требуемый HTML-код.
Можно, конечно, обойтись и функцией, которая выдаст требуемое,
получив в качестве параметров SQL-запрос и код шаблона. Но на мой взгляд с классами
и ООП в программировании работать куда вкуснее. Вдобавок я не собираюсь останавливаться
на такой простой модели списка, а дальнейшие разработки кода с классами поддерживаются
куда проще. А когда наши провайдеры сменят движки PHP с четвертой версии на
пятую, классы войдут в PHP в том же объеме, что и в C++.
Недолго думая, класс был назван ListItems; незнакомых
с английским отсылаю на multitran.ru. Пишем его
код:
class ListItems {
var $query;
var $template;
var $count = 0;
var $items = array();
var $listitems = '';
function ListItems ($query='') {
$this->query = $query;
if (!empty ($this->query)) {
$result = mysql_query ($this->query)
or die('SQL query is failed');
while ($tmp = mysql_fetch_object ($result))
$this->items[] = $tmp;
$this->count = count ($this->items);
}
}
}
Конструктор класса принимает в качестве параметра код SQL-запроса.
К моменту создания экземпляра класса у вас уже должна быть вызвана функция mysql_connect.
После этого в свойстве items объекта мы получим результат выполнения
запроса в виде нумерованного массива, каждый элемент которого является стандартным
PHP-объектом, имена полей которого соответствуют именам полей запроса, а значения,
естественно, соответствуют выбранным из базы данных значениям полей.
Теперь немного о шаблоне списка. Списки я препарировал, но ничего
не сказал об их программной реализации. Для начала назовем каждый пункт каким-нибудь
малопонятным именем на английском языке.
- list – обрамление списка
- empty – пустой список
- begin – начало списка
- end – конец списка
- item – элемент списка
- delimiter – разделитель элементов списка
Потом создадим массив, в котором каждый элемент шаблона индексируется
принятым именем. Для написания кода элементов шаблона я пользуюсь одной достаточно
распространенной идеей, вымученной PHP-программистами (да и не только ими) за многолетние
попытки отделить дизайн от кода (эта статья также относится к ним). В настоящее
время сложился определенный стандарт написания шаблонов для PHP-программ, использующий
так называемые макрокоды, или макросы.
Чаще всего применяется конструкция, использующая угловые скобки с каким-либо текстовым
или цифровым значением внутри для указания того места в шаблоне, куда надо вставлять
нужную информацию. Для той же цели применяются теги комментариев HTML <!--
--> с текстом или числом, которые идентифицируются PHP-движком и заменяются
требуемой информацией.
На мой взгляд, главный недостаток такой технологии заключается
в том, что если PHP по какой-либо причине не обработал этот элемент кода, то в браузере
вы не увидите ничего, что указало бы на эту ошибку, поскольку комментарии HTML невидимы
по определению, а конструкции с угловыми скобками выглядят для браузера как неизвестные
ему HTML-тэги, которые также не отображаются. А если совершенная ошибка невидима,
то она будет повторяться и никогда не отладится. К тому же шаблоны бывают достаточно
сложны и объемны по содержанию и разрабатывать их приходится в каком-либо WISIWIG-редакторе.
Попробуйте редактировать невидимые тэги и комментарии в нем, и сразу убедитесь,
насколько это неудобно. Применение квадратных скобок в виде [name]
не очень удачно, так как даже в обычных текстах они достаточно распространены.
В общем, для вставки в шаблоны варьируемой информации была придумана
следующая конструкция: {%%NAME%%}, где NAME –
это идентификатор того значения, которым мы хотим заменить этот макрос. Такой макрос
виден в выводе браузера, в любом WISIWIG-редакторе, более того, многие популярные
HTML- и PHP-редакторы исходного кода можно настроить на подсветку этой конструкции
цветом, отличным от остального кода, поскольку в стандарте HTML и PHP такая последовательность
символов не используется. К тому же этот макрос не требует для своего написания
специальных символов HTML.
Идентификатор NAME должен подчиняться еще одному правилу. Как
вы уже заметили, в коде класса ListItems, SQL-запрос преобразуется в массив объектов,
у которых имена полей соответствуют именам полей запроса, а значения – значениям.
Распространяя это дальше, примем, что идентификатор NAME должен соответствовать
имени того поля, значение которого заменит макрос без учета регистра символов. То
есть, если в вашей базе данных есть таблица, в которой в поле name
содержатся названия стран мира, а в поле population – численность
их населения, то для того чтобы вывести список стран в виде
НАЗВАНИЕ СТРАНЫ – НАСЕЛЕНИЕ
элемент item должен выглядеть так:
{%%NAME%%} – {%%POPULATION%%} чел.
Чтобы указать, где в обрамлении списка выводится последовательность
его элементов, а также их количество, в классе ListItems предусмотрены
дополнительные поля и соответствующие им макросы.
- listitems – в этом поле накапливается HTML-вывод по мере перебора
элементов списка, а макрос {%%LISTITEMS%%} должен обязательно
присутствовать в элементе list шаблона, поскольку он заменяется
кодом всего списка.
- count – это число элементов списка, а макрос {%%COUNT%%}
заменяется значением этого поля.
С учетом вышесказанного, код, задающий шаблон, будет выглядеть
так:
$template = array (
'list' => '{%%LISTITEMS%%}',
'empty' => '<p>"Это мир, где нет стран и людей.',
'begin' =>' <p>Население {%%COUNT%%} стран мира.<ul>',
'end' => '</ul>',
'item' => '<li>{%%NAME%%} - {%%POPULATION%%} чел.</li>',
'delimiter' => ' '
);
Теперь приведу код метода объекта ListItems, который
вставляет любой объект $o в каждый элемент шаблона $t,
закодированного по определённым выше правилам.
function InsertObjectToTemplate ($o, $t) {
foreach ($o as $n=>$v)
if (gettype ($v)!= 'object' && gettype ($v)!='array') {
$p = '\{%%' . strtoupper ($n) . '%%\}';
if (eregi ($p, $t)) $t = eregi_replace ($p, strval($v), $t);
}
return $t;
}
Изменив регулярное выражение в третьей строке кода, вы можете изменить
вид макроса в соответствии с принятым на вашем проекте. Например,
$p = '\[' . strtoupper ($n) . '\]';
позволит использовать макрос вида [name], а
$p = '\{[[:space:]]{0,}' . strtoupper ($n) . '[[:space:]]{0,}\}';
– такой же макрос с фигурными скобками и любым числом пробельных
символов между name и скобками. Я использую в своих проектах макросы
с возможностью добавления пробельных символов перед и просле name,
и эта строка выглядит так:
$p = '\{%%[[:space:]]{0,}' . strtoupper ($n) . '[[:space:]]{0,}%%\}';
И последняя функция класса ListItems выводит
массив объектов items в шаблон из шести элементов в соответствии со всем вышесказанным.
function getOutput ($arg) {
$list = empty ($arg['list']) ? '' : $arg['list'];
$begin = empty ($arg['begin']) ? '' : $arg['begin'];
$end = empty ($arg['end']) ? '' : $arg['end'];
$item = empty ($arg['item']) ? '' : $arg['item'];
$delimiter = empty ($arg['delimiter']) ? '' : $arg['delimiter'];
$empty = empty ($arg['empty']) ? '' : $arg['empty'];
foreach ($this->items as $id=>$one) {
$this->listitems .= $this->insertObjectToTemplate ($one, $item) . $delimiter;
}
$this->listitems = $this->count == 0 ?
$empty :
$begin . substr ($this->listitems, 0, -strlen ($delimiter)) . $end;
$res = $this->insertObjectToTemplate ($this, $list);
unset ($this->listitems);
return $this->insertObjectToTemplate ($this, $res);
}
Первые шесть строк кода нужны, чтобы предотвратить вывод сообщений
об ошибках в случае несоответствия шаблона $arg введенным нами
соглашениям. Далее по циклу перебираются все элементы массива items
и вставляются в элемент item шаблона с добавлением разделителя
delimiter. По окончании цикла, в случае, если количество элементов
в items больше нуля, то listitems заменяется
empty, если нет, то к нему добавляются begin и
end с отбрасыванием последнего delimiter. Далее
экземпляр нашего класса вставляется в list и еще раз в полученный
результат но уже без поля listitems. Это необходимо для того, чтобы
дать возможность разместить поле count в begin,
end, item или delimiter, что
иногда бывает необходимо.
В общем-то, уже почти все сказано. Но как полагается в любой статье
по программированию, приведу один готовый к тестированию пример, чтобы мой читатель,
который, как и всякий программист, достаточно ленив, мог оценить сей труд на своем
сервере.
Скачайте файл listitems.rar
и распакуйте его в один из каталогов вашего сервера. Код класса ListItems
находится в файле cls_lstitems.inc, тестовый пример – в файле test.php,
SQL-код для создания тестовой таблицы в вашей базе данных – в файле test.sql.
Во второй-пятой строках файла test.php укажитете ваши данные для
подключения к MySQL, создайте тестовую таблицу из файла test.sql
и запускайте скрипт test.php. Результатом его работы будет список
стран мира с их населением по последним данным из CIA World FactBook.
Раскомментируйте 20-ю строку скрипта. Теперь элемент item
отличается от первоначального тэгами b и, соответственно, население страны
выделяется жирным шрифтом. Раскомментируйте 22-ю и 23-ю строки, и список становится
нумерованным. Если в 53-ей строке заменить параметр функции $template
на $template1, то вы получите список в табличном виде. Шаблон $template2
позволяет вывести только названия всех стран мира через запятую с пробелом и завершающей
точкой. Если 51-ую строку скрипта заменить на
$list = new ListItems ('');
то можно посмотреть на то, как класс ListItems
обрабатывает пустые SQL-запросы.
Конечно, код класса ListItems намного сложнее
приведенного в начале статьи цикла. Но, вводя такой класс, мы сделали одну очень
большую вещь. В этом примере программный код (класс) полностью отделен от дизайна
(шаблон) и содержащейся в выводимом списке информации (SQL-запрос). Добавьте к этому
возможность хранить любое количество шаблонов в базе данных за счет их однотипной
структуры, выбирая их по имени или номеру (идентификатору) и редактируя через какой-нибудь
интерфейс администрирования без необходимости правки программного кода. Теперь не
надо запускать дизайнеров в программный код, достаточно лишь поправить шаблон. Нет
необходимости править код и в случае добавления новых данных: если вы хотите добавить
в ваш список площадь территории каждой страны, то нужно лишь добавить в таблицу
MySQL новое поле square, а в поле item шаблона
— макрос {%%SQUARE%%}. Все это очень сильно облегчает жизнь разработчику
движка сайта и CMS (Content Management System).
В этой статье мною приведена самая простая модель шаблона списка.
На данный момент код класса ListItems, применяемый мной, куда сложнее.
На сайте http://asiadata.ru/, который я поддерживаю
как вебмастер, этот класс выводит все списки, позволяя группировать их по любому
количеству элементов, выделять один или несколько из них, сортировать по одному
или нескольким полям, как в запросе, так и в самом массиве items,
а также расставлять ссылки и многое другое. Если читателей статьи заинтересует написанное,
пишите на mailto:direqtor@mail.ru, и я продолжу
и разовью затронутую здесь тему.