Все ленты — последние статьи

Битва со списками или применение классов для вывода списков

Список, список, список… Открываешь файл любого интернет-проекта, основанного на технологии 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:Этот адрес электронной почты защищен от спам-ботов. У вас должен быть включен JavaScript для просмотра., и я продолжу и разовью затронутую здесь тему.