Практически каждый разработчик сталкивался с необходимостью постраничного вывода информации. Каким бы ни был проект, в нем всегда есть "что-то", что не помещается на одну страницу: авторские стихи в маленькой домашней страничке или архив новостей в информационном портале. И каждый раз, когда мы создаем механизм поиска "Первой страницы", "Следующей страницы", "Предыдущей страницы", мы занимаемся рутинной работой. Каждый раз, когда мы в очередной раз переделываем готовую функцию постраничного вывода под новый интерфейс, мы напрасно теряем время. Однако, как правило, попытки создать какую-то единую универсальную функцию постраничного вывода наталкивались на то, что постраничный вывод тесно связан с контекстом. Эту связь можно разделить по степени важности на два уровня:
Проблема взаимозависимости контекста и постраничного вывода (основная проблема). Каждый раз, когда мы переходим от одной страницы к другой, мы должны "тащить" за собой огромное количество дополнительных данных, полученных из контекста: например, если мы листаем результаты поиска, то неплохо было бы сохранить параметры поиска при переходах от страницы к странице. Но эта зависимость обоюдная, ведь контекст, в свою очередь, зависит от параметров постраничного вывода: очевидно, что когда мы выбираем просмотр архива новостей за июль, мы будем удивлены, если встретим там новогодние поздравления.
Проблема, связанная с организацией внешнего вида (второстепенная проблема). В каждом проекте свой метод вывода информации. Это касается не только внешнего вида постраничных переходов (это может быть кнопка, которая переходит от страницы к странице с помощью метода POST; это может быть ссылка, которая формирует GET-овую строку запроса; и др.), но и способа хранения данных (кнопка или ссылка может формироваться динамически, "на лету"; это может быть шаблон, хранящийся в БД, и др.)
Невозможно создать универсальную систему постраничного вывода (читай: независимую от контекста), если невозможно разделить постраничный вывод и контекст. Причем не заниматься этим "разделением" в каждом проекте, а сделать это один раз, создав универсальную систему, и затем просто использовать ее. Всё это приводит нас к следующему разделу:
Целью этой статьи является рассказать об общем методе решения основной проблемы. Это решение неполно, но оно оставлено таким намеренно, чтобы не ограничивать свободы творчества тех разработчиков, которые решат воспользоваться им. Второстепенная проблема практически не затронута, но если идея, реализованная в этой статье, вызовет интерес, то, естественно, мы еще вернемся к этому вопросу.
Однако, прежде, чем начинать обсуждение чего-либо, хотелось бы "сверить часы": объяснить значение терминов, используемых в этой статье, создать своеобразный терминологический словарь:
Термины в словаре введены в логическом порядке: если термин содержит другие термины, то эти термины были определены раньше.
Постраничный просмотр - способ вывода запрошенной пользователем информации по частям. Каждый постраничный просмотр имеет собственные параметры деления информации на части (например, можно выводить информацию о каждой неделе или о каждом дне текущего месяца). При постраничном просмотре может быть видима информация только одной выбранной части. Эти части называются "страницами".
Страница - часть информации, выводимая на при постраничном просмотре, в соответствии с заданными параметрами деления информации на страницы.
Навигационная система или система постраничного вывода - это набор кнопок или ссылок, позволяющих пользователю осуществить постраничный просмотр (см. пример N1).
Первая страница | Предыдущая страница | 4 | 5 | 6 | 7 | 8 | 9 | Следующая страница | Последняя страница |
---|
Активная страница - страница, содержимое которой в данный момент просматривает пользователь. В примере N1 активной является седьмая (7) страница.
Навигационная ссылка - кнопка или ссылка (в зависимости от реализации), с помощью которой пользователь может "пролистать" на ту страницу, которая в данный момент не является активной. В примере N1 страницы 4-6, 8-9, "Первая страница", "Предыдущая страница", "Следующая страница", "Последняя страница" являются навигационными ссылками.
Строка в формате md5 - строка, содержащая параметры системы постраничного вывода, однако сформированная таким образом, чтобы внешне походить на md5-строки, генерируемые функцией md5(). См. пример N2.
Основная идея, изложенная в этой статье, проста: если очень сложно (практически невозможно) отделить контекст от параметров постраничного вывода, то нужно попробовать использовать другой внешний вид этих параметров при передаче их в навигационных ссылках. Как правило, все системы постраничного вывода обладают несколькими основными параметрами, а именно:
Тому есть 2 причины: во-первых, наши рассуждения могут быть легко распространены на два других параметра; во-вторых, это не будет отвлекать нас в процессе рассуждений.
Теперь изложим нашу идею более подробно для системы постраничного вывода с двумя основными параметрами: поскольку оба параметра - количество элементов и номер активной страницы - являются целыми неотрицательными числами, наиболее оптимальные решением было бы передавать их в виде специально сформированной строки, например, abcd2as15ks2sdm3 (См. пример N2)
abcd | 2 | as | 15 | ks | 2sdm3 |
↑ | ↑ | ↑ | ↑ | ↑ | ↑ |
"Маскирующая" часть 1 | Номер активной страницы | "Разделяющая" часть 2 | Общее число элементов | "Разделяющая" часть 3 | "Маскирующая" часть 4 |
↑ | ↑ | ↑ | ↑ | ↑ | ↑ |
Необязательная часть. Мы сами определим, как она должна выглядеть. | Целое число ≥ 0 | Разделитель-"не цифра" (например, буквы латинского алфавита) | Целое число ≥ 0 | Разделитель-"не цифра" (например, буквы латинского алфавита) | Необязательная часть. Мы сами определим, как она должна выглядеть. |
Итак, давайте подробней обсудим, как будет формироваться наша cтрока в формате md5. Конечно, можно использовать любой другой алгоритм ее формирования, и необязательна эта строка должна быть похожа на md5-строку. И, конечно же, наш способ приведен исключительно в качестве примера. Мы предлагаем формировать строку в формате md5 с учетом следующих условий:
Будем использовать "маскирующую" часть 1 фиксированной длины в 5 символов; для этого сгенерируем с помощью функции md5() строку и будем использовать ее первые 5 символов:
define('PART1_LENGTH', 5);
//...
srand(time());
$part_1 = substr(md5(uniqid(mt_rand())), 0, PART1_LENGTH);
Будем использовать "разделяющую" часть 2 фиксированной длины в 3 символа, который будем случайным образом выбирать из заранее определенной строки $t_str
:
define('PART2_LENGTH', 3);
//...
srand(time());
$t_str = eregi_replace('[0-9]', '', md5(uniqid(mt_rand())));
$part_2 = substr($t_str, 0, PART2_LENGTH);
Аналогично "разделяющей" части 2, найдем "разделяющую" часть 3:
define('PART3_LENGTH', 2);
//...
srand(time());
$t_str = eregi_replace('[0-9]', '', md5(uniqid(mt_rand())));
$part_3 = substr($t_str, 0, PART3_LENGTH);
Теперь совместим все части вместе. Получим результирующую строку вида:
$res = $part_1.$curindex.$part_2.$total.$part_3;
где
Длина нашей строки $res
, полученной на шаге 4, может быть меньше, чем 32 символа, и поэтому, поскольку мы создаем строку в формате md5, сгенерируем с помощью функции md5() еще одну, "дополняющую" строку и будем использовать ее для "дополнения" до 32-х символов нашей строки $res
. Для этого воспользуемся функцией str_pad():
define('GENERAL_LENGTH', 32);
//...
srand(time());
$res = str_pad($res, GENERAL_LENGTH, md5(uniqid(mt_rand())), STR_PAD_RIGHT);
где функция str_pad() принимает следующие параметры (более подробно - см. описание функции на сайте http://www.php.net):
$res
; если длина строки $res
меньше, чем GENERAL_LENGTH, то наша строка $res
будет дополнена в соответствии с последующими двумя параметрами до длины GENERAL_LENGTH;$res
до длины GENERAL_LENGTH;$res
справа.Оформим формирование строки в формате md5 в виде отдельной функции:
define('PART1_LENGTH', 5);
define('PART2_LENGTH', 3);
define('PART3_LENGTH', 2);
define('GENERAL_LENGTH', 32);
//...
function makeSecureParam($curindex, $total){
srand(time());
$part1 = substr(md5(uniqid(mt_rand())), 0, PART1_LENGTH);
srand(time());
$t_str = eregi_replace('[0-9]', '', md5(uniqid(mt_rand())));
$part2 = substr($t_str, 0, PART2_LENGTH);
srand(time());
$t_str = eregi_replace('[0-9]', '', md5(uniqid(mt_rand())));
$part3 = substr($t_str, 0, PART3_LENGTH);
srand(time());
return str_pad($part1.$curindex.$part2.$total.$part3,
GENERAL_LENGTH,
md5(uniqid(mt_rand())),
STR_PAD_RIGHT );
}
Итак, основная идея нами реализована : мы создали функцию, позволяющую "прятать" параметры системы постраничного вывода внутрь одной, специального вида строки. Какие преимущества нам это дает? Поскольку теперь наша система постраничного вывода характеризуется всего одним параметром, то это даст нам возможность встраивать систему постраничного вывода в контекст, а не наоборот, как это было раньше. Осталось только реализовать класс, который позволил бы принимать эту специального вида строку и преобразовывать ее в соответствующие параметры навигационной системы.
Мы создадим базовый класс, в котором присутствуют всего 2 функции: конструктор и инициализация основных параметров системы постраничного вывода по заранее данной строке специального формата.
Мы используем именно класс, хоть это и несколько замедлит выполнение скрипта, для того чтобы иметь возможность использовать несколько систем постраничного вывода одновременно (например, постраничный вывод новостей еженедельно и постраничный вывод новостей ежедневно внутри каждой выбранной недели) без каких-либо особых осложнений. Описание класса приведено ниже:
Класс содержит две переменных: общее число элементов и номер активной страницы.
class navigation{
var $curpage; /* active page number */
var $total; /* total items */
//...
}
Конструктор класса инициализирует "пустой" набор элементов (это предоставит возможность инициализировать экземпляр класса несколько раз):
class navigation{
//...
function navigation(){
$this->curpage = $this->total = 0;
}
}
Прежде, чем анализировать заданную строку в формате md5, исключим из нее "маскирующую" часть 1:
$str = substr($str, PART1_LENGTH);
Проанализируем заданную строку в формате md5 с помощью функции preg_split() (более подробно - см. описание функции на сайте http://www.php.net):
preg_split('/[^0-9]/si', $str, 3, PREG_SPLIT_NO_EMPTY)
где
$str
те ее части, которые состоят только из цифр; например, если
$str = 'cf16b2bcf12fd72dc05154e87749d940'
то результатом выполнения функции
preg_split('/[^0-9]/si', $str)
будет массив значений:
$str - заданная строка в формате md5;
$str
;например, если
$str = 'cf16b2bcf12fd72dc05154e87749d940'
то результатом выполнения функции
preg_split('/[^0-9]/si', $str, 3)
будет массив значений:
PREG_SPLIT_NO_EMPTY - специальный параметр, который позволяет выводить только непустые строки, которые соответствуют заданному шаблону поиска.
Поскольку функция preg_split() возвращает в качестве результата массив значений, то наиболее удобно использовать функцию list() обработки результатов - запишем первое полученное число во временную переменную $curpage
, а второе - во временную переменную $total
(этот порядок определяется способом формирования строки в формате md5 с помощью функции "makeSecureParam
"):
@list($curpage, $total) = preg_split('/[^0-9]/si', $str, 3, PREG_SPLIT_NO_EMPTY);
Чтобы исключить всевозможные "неожиданности", преобразуем полученные результаты к целым неотрицательным числам, а результат запишем в соответствующие переменные нашего класса:
$this->curpage = abs(intval($curpage));
$this->total = abs(intval($total));
Оформим анализ строки в формате md5 в виде функции init
, которая позволит выделить из данной строки необходимые параметры навигационной системы: общее число элементов и номер активной страницы.
define('PART1_LENGTH', 5);
//...
class navigation{
//...
function init($str = ''){
$str = substr($str, PART1_LENGTH);
if (!trim($str)){
$this->curpage = $this->total = 0;
return;
}
@list($curpage, $total) = preg_split('/[^0-9]/si',
$str, 3, PREG_SPLIT_NO_EMPTY);
$this->curpage = abs(intval($curpage));
$this->total = abs(intval($total));
}
}
Статья может быть полезной любому человеку, знакомому с синтаксисом РНР и владеющему по крайней мере начальным опытом программирования на РНР. В этой статье нет готовых рецептов. В ней изложен метод решения, которым каждый может воспользоваться. Главной идеей является "сокрытие" всех параметров навигационной системой внутри одной строки специального формата (в приведенном примере используется формат "а-ля md5" :) ). Представлена функция, формирующая строку в формате md5, и базовый класс, позволяющий инициализировать параметры навигационной системы по заданной строке в формате md5. Преимущества данного метода:
Полностью работающий текст класса и функции, описанных в этой статье, можно скачать здесь: code.zip
Copyright © 2002 by Felenka.