Шаг 1
Подготовительные работы
Очень часто объем информации, который хочется явить взору любопытного юзера, настолько велик, что не только прочитать его одним махом, но и загрузить на одну страницу становится практически невозможным. Требуется как то информацию упорядочить. Самый цивилизованный способ - разбить на несколько страниц и снабдить навигацией.
Если это сделать в автоматическом режиме, получится знакомый нам всем пагинатор. Или по русски - листалка. Или постраничная навигация. Как больше нравится.
Вот сейчас мы и изготовим такую вещь, чтобы наш сайт был удобочитаем.
Для начала нужно изготовить полигон. То есть площадку, на которой мы будем разворачивать боевые действия.
Первое - создадим таблицу в бд, которую и будем листать:
--
-- Структура таблицы `table`
--
CREATE TABLE `table` (
`id` int(10) NOT NULL auto_increment,
`text` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=101 DEFAULT CHARSET=utf8 AUTO_INCREMENT=101 ;
--
-- Дамп данных таблицы `table`
--
INSERT INTO `table` VALUES (1, 'текст 1');
INSERT INTO `table` VALUES (2, 'текст 2');
INSERT INTO `table` VALUES (3, 'текст 3');
INSERT INTO `table` VALUES (4, 'текст 4');
INSERT INTO `table` VALUES (5, 'текст 5');
INSERT INTO `table` VALUES (6, 'текст 6');
INSERT INTO `table` VALUES (7, 'текст 7');
INSERT INTO `table` VALUES (8, 'текст 8');
INSERT INTO `table` VALUES (9, 'текст 9');
INSERT INTO `table` VALUES (10, 'текст 10');
INSERT INTO `table` VALUES (11, 'текст 11');
INSERT INTO `table` VALUES (12, 'текст 12');
INSERT INTO `table` VALUES (13, 'текст 13');
INSERT INTO `table` VALUES (14, 'текст 14');
INSERT INTO `table` VALUES (15, 'текст 15');
INSERT INTO `table` VALUES (16, 'текст 16');
INSERT INTO `table` VALUES (17, 'текст 17');
INSERT INTO `table` VALUES (18, 'текст 18');
INSERT INTO `table` VALUES (19, 'текст 19');
INSERT INTO `table` VALUES (20, 'текст 20');
INSERT INTO `table` VALUES (21, 'текст 21');
INSERT INTO `table` VALUES (22, 'текст 22');
INSERT INTO `table` VALUES (23, 'текст 23');
INSERT INTO `table` VALUES (24, 'текст 24');
INSERT INTO `table` VALUES (25, 'текст 25');
INSERT INTO `table` VALUES (26, 'текст 26');
INSERT INTO `table` VALUES (27, 'текст 27');
INSERT INTO `table` VALUES (28, 'текст 28');
INSERT INTO `table` VALUES (29, 'текст 29');
INSERT INTO `table` VALUES (30, 'текст 30');
INSERT INTO `table` VALUES (31, 'текст 31');
INSERT INTO `table` VALUES (32, 'текст 32');
INSERT INTO `table` VALUES (33, 'текст 33');
INSERT INTO `table` VALUES (34, 'текст 34');
INSERT INTO `table` VALUES (35, 'текст 35');
INSERT INTO `table` VALUES (36, 'текст 36');
INSERT INTO `table` VALUES (37, 'текст 37');
INSERT INTO `table` VALUES (38, 'текст 38');
INSERT INTO `table` VALUES (39, 'текст 39');
INSERT INTO `table` VALUES (40, 'текст 40');
INSERT INTO `table` VALUES (41, 'текст 41');
INSERT INTO `table` VALUES (42, 'текст 42');
INSERT INTO `table` VALUES (43, 'текст 43');
INSERT INTO `table` VALUES (44, 'текст 44');
INSERT INTO `table` VALUES (45, 'текст 45');
INSERT INTO `table` VALUES (46, 'текст 46');
INSERT INTO `table` VALUES (47, 'текст 47');
INSERT INTO `table` VALUES (48, 'текст 48');
INSERT INTO `table` VALUES (49, 'текст 49');
INSERT INTO `table` VALUES (50, 'текст 50');
INSERT INTO `table` VALUES (51, 'текст 51');
INSERT INTO `table` VALUES (52, 'текст 52');
INSERT INTO `table` VALUES (53, 'текст 53');
INSERT INTO `table` VALUES (54, 'текст 54');
INSERT INTO `table` VALUES (55, 'текст 55');
INSERT INTO `table` VALUES (56, 'текст 56');
INSERT INTO `table` VALUES (57, 'текст 57');
INSERT INTO `table` VALUES (58, 'текст 58');
INSERT INTO `table` VALUES (59, 'текст 59');
INSERT INTO `table` VALUES (60, 'текст 60');
INSERT INTO `table` VALUES (61, 'текст 61');
INSERT INTO `table` VALUES (62, 'текст 62');
INSERT INTO `table` VALUES (63, 'текст 63');
INSERT INTO `table` VALUES (64, 'текст 64');
INSERT INTO `table` VALUES (65, 'текст 65');
INSERT INTO `table` VALUES (66, 'текст 66');
INSERT INTO `table` VALUES (67, 'текст 67');
INSERT INTO `table` VALUES (68, 'текст 68');
INSERT INTO `table` VALUES (69, 'текст 69');
INSERT INTO `table` VALUES (70, 'текст 70');
INSERT INTO `table` VALUES (71, 'текст 71');
INSERT INTO `table` VALUES (72, 'текст 72');
INSERT INTO `table` VALUES (73, 'текст 73');
INSERT INTO `table` VALUES (74, 'текст 74');
INSERT INTO `table` VALUES (75, 'текст 75');
INSERT INTO `table` VALUES (76, 'текст 76');
INSERT INTO `table` VALUES (77, 'текст 77');
INSERT INTO `table` VALUES (78, 'текст 78');
INSERT INTO `table` VALUES (79, 'текст 79');
INSERT INTO `table` VALUES (80, 'текст 80');
INSERT INTO `table` VALUES (81, 'текст 81');
INSERT INTO `table` VALUES (82, 'текст 82');
INSERT INTO `table` VALUES (83, 'текст 83');
INSERT INTO `table` VALUES (84, 'текст 84');
INSERT INTO `table` VALUES (85, 'текст 85');
INSERT INTO `table` VALUES (86, 'текст 86');
INSERT INTO `table` VALUES (87, 'текст 87');
INSERT INTO `table` VALUES (88, 'текст 88');
INSERT INTO `table` VALUES (89, 'текст 89');
INSERT INTO `table` VALUES (90, 'текст 90');
INSERT INTO `table` VALUES (91, 'текст 91');
INSERT INTO `table` VALUES (92, 'текст 92');
INSERT INTO `table` VALUES (93, 'текст 93');
INSERT INTO `table` VALUES (94, 'текст 94');
INSERT INTO `table` VALUES (95, 'текст 95');
INSERT INTO `table` VALUES (96, 'текст 96');
INSERT INTO `table` VALUES (97, 'текст 97');
INSERT INTO `table` VALUES (98, 'текст 98');
INSERT INTO `table` VALUES (99, 'текст 99');
INSERT INTO `table` VALUES (100, 'текст 100');
Теперь возьмем установочные данные из конфига и сделаем отдельный файл коннекта.
mysql.php
<?php
/**
* Database server.
* Сервер БД.
*/
define('IRB_DBSERVER', 'localhost');
/**
* Database user.
* Пользователь БД
*/
define('IRB_DBUSER', 'root');
/**
* Database password.
* Пароль БД
*/
define('IRB_DBPASSWORD', '');
/**
* Database name.
* Название базы
*/
define('IRB_DATABASE', 'basa');
/**
* Connection and installation of chaeset of connection
* Подключение и установка кодировок соединения
*/
mysql_connect( IRB_DBSERVER, IRB_DBUSER, IRB_DBPASSWORD );
mysql_select_db( IRB_DATABASE);
mysql_query('SET NAMES utf8');
mysql_query('SET CHARACTER SET utf8');
mysql_query('SET COLLATION_CONNECTION="utf8_general_ci"');
Ну и сам индексный файл, где будем сочинять скрипт. Пока поместим туда просто выборку из таблицы:
index.php
<?php
include './mysql.php';
$res = mysql_query("SELECT * FROM `table` ");
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
Всё, можно запускать. Вот мы видим, что на странице у нас 100 строк текста. А нам допустим надо 10 страниц по 10 строк на кждой.
Проследуем в следующий раздел.
Шаг 2
Основной принцип
Чтобы разбить данные на порции, в SQL есть специальный оператор - LIMIT. Он позволяет выбрать часть таблицы. Нужно только указать стартовую позицию и необходимое количество:
<?php
$res = mysql_query("SELECT * FROM `table` LIMIT 0, 10");
А это значит, что количество строк на странице мы можем задать в конфигурационном файле, а стартовую позицию изменять динамически, поместив в GET-параметр ссылки:
index.php
<?php
include './mysql.php';
// Количество строк на странице
$num = 10;
// Принимаем номер требуемой страницы
$page = !empty($_GET['page']) ? (int)$_GET['page'] : 1;
// Формируем вывод
$res = mysql_query("SELECT * FROM `table` LIMIT ". (($page - 1) * $num) .", ". $num);
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
?>
<a href="?page=1">1</a>
<a href="?page=2">2</a>
<a href="?page=3">3</a>
<a href="?page=4">4</a>
<a href="?page=5">5</a>
<a href="?page=6">6</a>
<a href="?page=7">7</a>
<a href="?page=8">8</a>
<a href="?page=9">9</a>
<a href="?page=10">10</a>
Вот, собственно, и весь принцип пагинатора. Если у Вас в таблице фиксированное количество строк, то можно вполне обойтись и таким скриптом.
А если количество данных меняются время от времени, то так не пойдет. Меню навигации нужно тоже сформировать динамически.
Для этого как минимум нам нужно знать, сколько всего записей в таблице. По этому потребуется еще один запрос.
<?php
include './mysql.php';
// Количество строк на странице
$num = 10;
// Принимаем номер требуемой страницы
$page = !empty($_GET['page']) ? (int)$_GET['page'] : 1;
// Считаем количество строк в таблице и делим на заданное. Получится количество страниц
$res = mysql_query("SELECT COUNT(*) AS `cnt` FROM `table`");
$count = mysql_result($res, 0) / $num;
// Формируем меню
$menu = '';
for($i = 1; $i <= $count; ++$i)
$menu .= '<a href="?page='. $i .'">'. $i .'</a> ';
// Формируем вывод
$res = mysql_query("SELECT * FROM `table` LIMIT ". (($page - 1) * $num) .", ". $num);
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
echo $menu;
Вот и всё, по большому счету пагинатор уже работает. Но он какой то ущербный и куций. Негодный вобщем.
Надо добавить красоты.
Шаг 3
Меню навигации
Вот представьте, что у Вас в таблице несколько тысяч записей. Или еще больше. Если вывести все номера страниц в меню, то это самое меню займет львинную долю страницы. Чтобы этого избежать, нужно выводить текущий диапазон, как в Google допустим. Или еще красивее.
Сейчас этим и займемся. Для начала нужно добавить записей в таблицу, чтобы можно было это все увидеть наглядно. Для этого можно запустить такой скрипт:
<?php
include './mysql.php';
$res = mysql_query("SELECT MAX(id) FROM `table`");
$i = mysql_result($res, 0);
$insert = '';
while(++$i <= 10000)
{
$insert .= "('', 'текст". $i ."'),";
if(!($i % 500))
{
mysqlQuery("INSERT INTO `table`
(`id`, `text`)
VALUES
". trim($insert, ',')
);
$insert = '';
}
}
Он заполнит таблицу 10 тысячами записей, и мы сможем убедиться (запустив тестовый скрипт), как ужасно смотрится меню навигации. Вот и начнем потихоньку приводить его в порядок.
Сначала генератор меню поместим в функцию. За одно выделим активную страницу:
<?php
include './mysql.php';
// Количество строк на странице
$num = 10;
/**
* Generates the navigation menu
* @param int $count, $page
* @return string
*/
function createMenu($count, $page)
{
$menu = '';
for($i = 1; $i <= $count; ++$i)
{
if($i == $page)
$menu .= '<b>'. $i .'</b> ';
else
$menu .= '<a href="?page='. $i .'">'. $i .'</a> ';
}
return $menu;
}
// Принимаем номер требуемой страницы
$page = !empty($_GET['page']) ? (int)$_GET['page'] : 1;
// Считаем количество строк в таблице и делим на заданное. Получится количество страниц
$res = mysql_query("SELECT COUNT(*) AS `cnt` FROM `table`");
$count = (mysql_result($res, 0) / $num);
// Формируем вывод
$res = mysql_query("SELECT * FROM `table` LIMIT ". (($page - 1) * $num) .", ". $num);
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
echo createMenu($count, $page);
Теперь видно, где мы находимся.
А сейчас начнем эту функцию модернизировать, чтобы она нам выдала красивую навигацию.
Перво-наперво определим диапазон, при котором ничего делать не нужно. Это будет от 1 до 12 страниц.
<?php
/**
* Generates the navigation menu
* @param int $count, $page
* @return string
*/
function createMenu($count, $page)
{
$menu = '';
if($count <= 12)
{
for($i = 1; $i <= $count; ++$i)
{
if($i == $page)
$menu .= '<b>'. $i .'</b> ';
else
$menu .= '<a href="?page='. $i .'">'. $i .'</a> ';
}
}
return $menu;
}
Если запустить скрипт с такой функцией, то меню исчезнет, так как страниц у нас гораздо больше, чем 12. По этому нужно вывести и этот диапазон. Обрежем слева и справа от текущей страницы по пять штук.
<?php
/**
* Generates the navigation menu
* @param int $count, $page
* @return string
*/
function createMenu($count, $page)
{
$menu = '';
// Если страниц меньше 13, оставляем все по дефолту.
if($count < 13)
{
$i = 1;
$cnt = $count;
}
else
{
if($page < 6)
{ // Если текущая страница в диапазоне от 1 до 5, выводим первые 10 ссылок
$i = 1;
$cnt = 10;
}
elseif($page >= $count - 5)
{ // Если текущая страница на границе диапазона, или вышла за неё, показываем 10 последних
$i = $count - 10;
$cnt = $count;
}
else
{ // В ином случае показываем 11 страниц: текущую, 5 слева от неё и 5 справа.
$i = $page - 5;
$cnt = $count;
}
// Обрезаем меню
if($count - $i > 10)
$cnt = $i + 10;
}
// Формируем меню
while($i <= $cnt)
{
if($i == $page)
$menu .= '<b>'. $i .'</b> ';
else
$menu .= '<a href="?page='. $i .'">'. $i .'</a> ';
$i++;
}
return $menu;
}
Теперь у нас меню будет состоять из 11 ссылок. Текущая, пять вперед и пять назад. Вроде так ничего. Но все равно не то. А если нам захочется первую страницу посмотреть? Или последнюю? Придется три дня сидеть, листать. Негоже. Нужно вывести их обязательно. А лучше по две с обоих сторон. А за одно сделаем шпаргалку. То есть ссылку, которая будет шагать сразу на 10 страниц вперед или назад.
<?php
/**
* Generates the navigation menu
* @param int $count, $page
* @return string
*/
function createMenu($count, $page)
{
$menu = '';
// Если страниц меньше 13, оставляем все по дефолту.
if($count < 13)
{
$i = 1;
$cnt = $count;
}
else
{
// Стрелочка на 10 влево
if($page > 10)
$menu .= '<a href="?page='. ($page - 10) .'">-10<</a> ';
// Добавляем ссылки на две первые страницы
if($count > 12)
{
if($page == 7)
$menu .= '<a href="?page=1">1</a> ';
elseif($page == 8)
$menu .= '<a href="?page=1">1</a> <a href="?page=2">2</a> ';
elseif($page > 7)
$menu .= '<a href="?page=1">1</a> <a href="?page=2">2</a> <b>...</b> ';
}
if($page < 6)
{ // Если текущая страница в диапазоне от 1 до 5, выводим первые 10 записей
$i = 1;
$cnt = 10;
}
elseif($page >= $count - 5)
{ // Если текущая страница на границе диапазона, или вышла за неё, показываем 10 последних
$i = $count - 10;
$cnt = $count;
}
else
{ // В ином случае показываем 11 страниц, 5 слева от текущей, 5 справа.
$i = $page - 5;
$cnt = $count;
}
// Обрезаем ссылки
if($page < 6)
$cnt = $i + 9;
elseif($count - $i > 10)
$cnt = $i + 10;
}
// Формируем меню
while($i <= $cnt)
{
if($i == $page)
$menu .= '<b>'. $i .'</b> ';
else
$menu .= '<a href="?page='. $i .'">'. $i .'</a> ';
$i++;
}
// Добавляем ссылки на две последние страницы
if($count > 12)
{
if($page < $count - 6)
$menu .= ' <b>...</b> <a href="?page='. ($count - 1) .'">'. ($count - 1) .'</a> ';
if($page < $count - 5)
$menu .= '<a href="?page='. $count .'">'. $count .'</a> ';
}
// Стрелочка на 10 вправо
$end = ($page + 10 > $count)? $count : $page + 10;
if($page < $count - 5)
$menu .= ' <a href="?page='. $end .'">>+10</a>';
return $menu;
}
Ну вот, меню навигации привели в порядок. Вернее его функционал. Внешний вид подрихтуем чуть позже.
А пока упакуем скрипт в класс, чтобы было удобно с ним работать.
Шаг 4
Класс
Чтобы можно было использовать наш пагинатор в разных местах, не переписывая его весь в нужном месте, удобно засунуть его в класс и работать с объектом.
Вот сейчас мы это и сделаем.
Итак, каркас:
<?php
/**
* IRB_Paginator - Class of division of the information on a paginal mode
* NOTE: Requires PHP version 5 or later
* @package IRB_Paginator
* @author IT studio IRBIS-team
* @copyright © 2010 IRBIS-team
* @version 0.1
* @license http://www.opensource.org/licenses/rpl1.5.txt
*/
class IRB_Paginator
{
/////////////////////////////////////////////////
// PUBLIC
/////////////////////////////////////////////////
/**
* Establishes page number.
* @var int
*/
public $NumPage = 1;
/**
* Establishes quantity of numbers.
* @var int
*/
public $NumRows = 1;
/**
* Establishes quantity of columns.
* @var int
*/
public $NumColumns = 1;
/////////////////////////////////////////////////
// PROPERTIES AND PRIVATE
////////////////////////////////////////////////
private $TableTotal = 0;
private $TableCount = 0;
/**
* Constructor
* @param int $page
* @param int $rows
* @param int $columns
*/
public function __construct($page = 1, $rows = 1, $columns = 1)
{
if($rows > 1)
$this->NumPage = (int)$page;
if($rows > 1)
$this->NumRows = $rows;
if($columns > 1)
$this->NumColumns = $columns;
}
}
В конструктор мы передали три параметра. Номер текущей страницы, число рядов и число колонок, если придется выводить по нескольку строк в ряду.
Теперь займемся методом, который будет подсчитывать количество записей в таблице. Для того, чтобы можно было вызвать класс наименьшим числом движений, поместим оба запроса (считающий и основной) в один метод. Он сам разберется, что и как считать и выполнять. Для этого достанем из него название таблицы, подсчитаем количество записей простым запросом, подсчитаем стартовую позицию и выполним основной запрос с нужным лимитом.
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function countQuery($query)
{
$query = str_replace("\n", " ", $query);
preg_match("#FROM(.+)#i", $query, $table);
$result = mysql_query("SELECT COUNT(*) AS `cnt` FROM ". $table[1]);
$this->TableCount = mysql_result($result, 0);
$res = mysql_query($query . $this->createLimit());
return $res;
}
Лимит будет подготавливать другой метод, в который мы поместим нужные вычисления:
/**
* Calculates a position and prepares a limit for inquiry
* @access public
* @return string
*/
public function createLimit()
{
$this->TableTotal = intval(($this->TableCount - $this->NumColumns) / $this->NumRows * $this->NumColumns) - 1;
if($this->NumPage < 1)
$this->NumPage = 1;
if(empty($this->TableTotal) || $this->TableTotal < $this->TableCount)
$this->TableTotal = $this->TableCount;
if($this->NumPage > $this->TableTotal)
$this->NumPage = $this->TableTotal;
$start = $this->NumPage * $this->NumRows * $this->NumColumns - $this->NumRows * $this->NumColumns;
if($start < 0)
$start = 0;
return ' LIMIT '. $start .', '. $this->NumRows * $this->NumColumns;
}
Ну и преобразуем нашу функцию генерации меню в метод. Получится вот такой незатейливый класс:
irb_paginator.php
<?php
/**
* IRB_Paginator - Class of division of the information on a paginal mode
* NOTE: Requires PHP version 5 or later
* @package IRB_Paginator
* @author IT studio IRBIS-team
* @copyright © 2010 IRBIS-team
* @version 0.1
* @license http://www.opensource.org/licenses/rpl1.5.txt
*/
class IRB_Paginator
{
/////////////////////////////////////////////////
// PUBLIC
/////////////////////////////////////////////////
/**
* Establishes page number.
* @var int
*/
public $NumPage = 1;
/**
* Establishes quantity of numbers.
* @var int
*/
public $NumRows = 1;
/**
* Establishes quantity of columns.
* @var int
*/
public $NumColumns = 1;
/////////////////////////////////////////////////
// PROPERTIES AND PRIVATE
////////////////////////////////////////////////
private $TableTotal = 0;
private $TableCount = 0;
/**
* Constructor
* @param int $page
* @param int $rows
* @param int $columns
*/
public function __construct($page = 1, $rows = 1, $columns = 1)
{
if($rows > 1)
$this->NumPage = (int)$page;
if($rows > 1)
$this->NumRows = $rows;
if($columns > 1)
$this->NumColumns = $columns;
}
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function countQuery($query)
{
$query = str_replace("\n", " ", $query);
preg_match("#FROM(.+)#i", $query, $table);
$result = mysql_query("SELECT COUNT(*) AS `cnt`
FROM ". $table[1]);
$this->TableCount = mysql_result($result, 0);
$res = mysql_query($query . $this->createLimit());
return $res;
}
/**
* Calculates a position and prepares a limit for inquiry
* @param int $page
* @access public
* @return string
*/
public function createLimit()
{
$this->TableTotal = intval(($this->TableCount - $this->NumColumns) / $this->NumRows * $this->NumColumns) - 1;
if($this->NumPage < 1)
$this->NumPage = 1;
if(empty($this->TableTotal) || $this->TableTotal < $this->TableCount)
$this->TableTotal = $this->TableCount;
if($this->NumPage > $this->TableTotal)
$this->NumPage = $this->TableTotal;
$start = $this->NumPage * $this->NumRows * $this->NumColumns - $this->NumRows * $this->NumColumns;
if($start < 0)
$start = 0;
return ' LIMIT '. $start .', '. $this->NumRows * $this->NumColumns;
}
/**
* Generates the navigation menu
* @access private
* @return string
*/
function createMenu()
{
$count = ceil($this->TableTotal / $this->NumRows / $this->NumColumns);
$menu = "\n<!-- IRB_Paginator begin -->\n";
// Если страниц меньше 13, оставляем все по дефолту.
if($count < 13)
{
$i = 1;
$cnt = $count;
}
else
{
// Стрелочка на 10 влево
if($this->NumPage > 10)
$menu .= '<a href="?page='. ($this->NumPage - 10) .'">-10<</a> ';
// Добавляем ссылки на две первые страницы
if($count > 12)
{
if($this->NumPage == 7)
$menu .= '<a href="?page=1">1</a> ';
elseif($this->NumPage == 8)
$menu .= '<a href="?page=1">1</a> <a href="?page=2">2</a> ';
elseif($this->NumPage > 7)
$menu .= '<a href="?page=1">1</a> <a href="?page=2">2</a> <b>...</b> ';
}
if($this->NumPage < 6)
{ // Если текущая страница в диапазоне от 1 до 5, выводим первые 10 ссылок
$i = 1;
$cnt = 10;
}
elseif($this->NumPage >= $count - 5)
{ // Если текущая страница на границе диапазона, или вышла за неё, показываем 10 последних
$i = $count - 10;
$cnt = $count;
}
else
{ // В ином случае показываем 11 ссылок: 5 слева от текущей, текущая, и 5 справа.
$i = $this->NumPage - 5;
$cnt = $count;
}
// Обрезаем ссылки
if($this->NumPage < 6)
$cnt = $i + 9;
elseif($count - $i > 10)
$cnt = $i + 10;
}
// Формируем меню
while($i <= $cnt)
{
if($i == $this->NumPage)
$menu .= '<b>'. $i .'</b> ';
else
$menu .= '<a href="?page='. $i .'">'. $i .'</a> ';
$i++;
}
// Добавляем ссылки на две последние страницы
if($count > 12)
{
if($this->NumPage < $count - 6)
$menu .= ' <b>...</b> <a href="?page='. ($count - 1) .'">'. ($count - 1) .'</a> ';
if($this->NumPage < $count - 5 && $count - $this->NumPage >= 10)
$menu .= '<a href="?page='. $count .'">'. $count .'</a> ';
}
// Стрелочка на 10 вправо
$end = ($this->NumPage + 10 > $count)? $count : $this->NumPage + 10;
if($this->NumPage < $count - 5)
$menu .= ' <a href="?page='. $end .'">>+10</a>';
return $menu ."\n\n<!-- IRB_Paginator end -->\n";
}
}
А вызывать будем так:
<?php
// Количество строк на странице
$num = 10;
// Принимаем номер требуемой страницы
$page = !empty($_GET['page']) ? (int)$_GET['page'] : 1;
// Подключаем класс
include './irb_paginator.php';
// Инициализируем объект
$paginator = new IRB_Paginator($page, $num);
// Запрос
$res = $paginator -> countQuery("SELECT * FROM `table`");
// Формируем меню
$menu = $paginator -> createMenu();
// Формируем вывод
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
echo $menu;
Ну вот, теперь вроде бы все в ажуре. Но есть куча проблем.
Их решим в следующем разделе.
Шаг 5
Ссылки
Всё, что мы сделали, конечно хорошо. Но использовать в боевых условиях этот скрипт вряд ли получится. Дело в том, что ссылки могут содержать еще и другие GET-параметры, а у нас там все занято. Кроме того, некоторые скрипты используют ЧПУ, а у нас обычный GET.
Нужно этот момент подработать.
Ну и за одно предусмотрим возможность изменять название GET переменной. Это пригодится, если потребуется вложенная пагинация. То есть мы сможем листать вложенные страницы.
Сделаем еще один метод, который будет генерировать корректные ссылки.
/**
* Makes a hyperlink
* @param int $page
* @param string $link, $class
* @param bolean $active
* @access private
* @return string
*/
private function createLink($page = 1, $link = '', $class = '', $active = true)
{
// Формируем стартовые значения
if(strpos($_SERVER['REQUEST_URI'], $this->LevelPage) === false)
$string = $this->StartLink . $page;
else
{
// Определяем, используется ли ЧПУ и формируем параметры
if($this->SetRewrite)
$string = preg_replace('#'. $this->LevelPage .'/\d+#',
$this->LevelPage .'/'. $page,
$_SERVER['REQUEST_URI']);
else
$string = '?'. preg_replace('#'. $this->LevelPage .'=\d+#',
$this->LevelPage .'='. $page,
$_SERVER['QUERY_STRING']);
}
// Есть ли что то, отличное от номера ссылки
if(empty($link))
$link = $page;
// Формируем ссылку
if($active)
return "<span class=\"IRB_paginator". $class ."\">\n"
. "<a href=\"". $string ."\" />". $link ."</a>\n</span>\n";
else
return "<span class=\"IRB_paginator". $class ."\"> ". $link ." </span>\n";
}
Тут нужно отметить, что если используется ЧПУ, то нужно будет изменить правила перенаправления. В нашем случае нужно добавить такое правило:
RewriteEngine on
RewriteRule ^page/([0-9]+)$ index.php?page=$1
и тогда можно делать ссылки плана http://site.ru/page/1
Теперь изменим метод формирования меню:
/**
* Generates the navigation menu
* @access private
* @param string $link
* @return string
*/
function createMenu($rewrite = false, $level = '')
{
if(!empty($level))
$this->LevelPage = $level;
$this->SetRewrite = $rewrite;
$this->StartLink = $rewrite ? $this->LevelPage .'/' : '?'. $this->LevelPage .'=';
$count = ceil($this->TableTotal / $this->NumRows / $this->NumColumns);
$menu = "\n<!-- IRB_Paginator begin -->\n";
// Если страниц меньше 13, оставляем все по дефолту.
if($count < 13)
{
$i = 1;
$cnt = $count;
}
else
{
// Стрелочка на 10 влево
if($this->NumPage > 10)
$menu .= $this->createLink(($this->NumPage - 10), '-10<', '_top');
// Добавляем ссылки на две последние страницы
if($count > 12)
{
if($this->NumPage == 7)
$menu .= $this->createLink(1, 1);
elseif($this->NumPage == 8)
$menu .= $this->createLink(1, 1)
. $this->createLink(2, 2);
elseif($this->NumPage > 7)
$menu .= $this->createLink(1, 1)
. $this->createLink(2, 2)
. $this->createLink(0, '...', '_top', false);
}
if($this->NumPage < 6)
{ // Если текущая страница в диапазоне от 1 до 5, выводим первые 10 записей
$i = 1;
$cnt = 10;
}
elseif($this->NumPage >= $count - 5)
{ // Если текущая страница на границе диапазона, или вышла за неё, показываем 10 последних
$i = $count - 10;
$cnt = $count;
}
else
{ // В ином случае показываем 11 страниц, 5 слева от текущей, 5 справа.
$i = $this->NumPage - 5;
$cnt = $count;
}
// Обрезаем ссылки
if($this->NumPage < 6)
$cnt = $i + 9;
elseif($count - $i > 10)
$cnt = $i + 10;
}
// Формируем меню
while($i <= $cnt)
{
if($i == $this->NumPage)
$menu .= $this->createLink($i, $i, '_active', false);
else
$menu .= $this->createLink($i, $i);
$i++;
}
// Добавляем ссылки на две последние страницы
if($count > 12)
{
if($this->NumPage < $count - 6)
$menu .= $this->createLink(0, '...', '_top', false)
. $this->createLink(($count - 1), ($count - 1));
if($this->NumPage < $count - 5)
$menu .= $this->createLink($count, $count);
}
// Стрелочка на 10 вправо
$end = ($this->NumPage + 10 > $count) ? $count : $this->NumPage + 10;
if($this->NumPage < $count - 5 && $count - $this->NumPage >= 10)
$menu .= $this->createLink($end, '>+10', '_top');
return $menu ."\n\n<!-- IRB_Paginator end -->\n";
}
Собственно изменилось только то, что сами ссылки теперь формируются отдельным методом.
Еще нужно добавить одно свойство, то есть переменную, отвечающую за незвание GET-параметра
/**
* Includes mod_rewrite.
* @var string
*/
public $LevelPage = 'page';
И еще одн, которое поможет переключить скритп на использование реврайта
/////////////////////////////////////////////////
// PROPERTIES AND PRIVATE
////////////////////////////////////////////////
private $TableTotal = 0;
private $TableCount = 0;
private $StartLink = '';
Теперь у нас образовалось три новшества.
1. Можно использовать mod_rewrite, а можно и простые GET параметры. Для включения реврайта первым аргументом в метод createMenu() нужно передать true
<?php
$menu = $paginator -> createMenu(true);
2. Можно задавать название GET-параметра (по умолчанию page). Это можно сделать при вызове метода формирования меню, передав его вторым аргументом:
<?php
$menu = $paginator -> createMenu(false, 'sel');
3. И можно расскрасить нашу менюшку с помощью стилей:
<?php
// Количество строк на странице
$num = 10;
// Принимаем номер требуемой страницы
$page = !empty($_GET['page']) ? (int)$_GET['page'] : 1;
// Подключаем класс
// include './irb_paginator.php';
// Инициализируем объект
$paginator = new IRB_Paginator($page, $num);
// Запрос
$res = $paginator -> countQuery("SELECT * FROM `table`");
// Формируем меню
$menu = $paginator -> createMenu();
// Формируем вывод
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
?>
<style type="text/css">
.IRB_paginator {
padding: 2px;
font-weight:bold;
}
.IRB_paginator_active {
padding: 2px;
color:#FF0000;
font-weight:bold;
}
.IRB_paginator_top {
padding: 2px;
font-weight:bold;
color:#009900;
}
.IRB_paginator_block{
font-family:Verdana, Arial, Helvetica, sans-serif;
color: #000099;
}
.IRB_paginator_block a:link {
text-decoration:none;
color: #000099;
}
.IRB_paginator_block a:visited {
text-decoration:none;
color: #000099;
}
.IRB_paginator_block a:hover {
text-decoration:none;
color: #000099;
}
.IRB_paginator_block a:active {
text-decoration:none;
color: #000099;
}
.IRB_paginator_top a:link {
color:#009900;
}
.IRB_paginator_topa:visited {
text-decoration:none;
color: #009900;
}
.IRB_paginator_top a:hover {
text-decoration:none;
color: #009900;
}
.IRB_paginator_top a:active {
text-decoration:none;
color: #009900;
}
</style>
<div class="IRB_paginator_block">
<?php echo $menu; ?>
</div>
Вот теперь всё красиво и грамотно.
Но всё ли? Есть еще один подводный камушек. О нем в следующем разделе.
Шаг 5
Сложные запросы
Всё вроде как у нас вышло удачно. Однако это до поры до времени. А именно до тех пор, пока мы делаем простую выборку. А вот если нам потребуется сложный запрос (отлистать результаты поиска к примеру), то наш класс тут же пикнет. Нужно это упущение немедленно устранить.
Сделать это оказывается не так уж и сложно. Дело в том, что в SQL есть одна замечательная опция - SQL_CALC_FOUND_ROWS. Если включить её, то в одном запросе фактически будет выполнено два. Один - основной, с количеством строк, установленном в лимите, другой подсчитает количество строк всего, не взирая на установленный лимит. И мы сможем узнать это количество следующим запросом. Чтобы было проще понять, можно запустить такой скрипт:
<?php
include './mysql.php';
// Запрос
$res = mysql_query("SELECT * FROM `table` WHERE `text` LIKE '%2%'");
// Формируем вывод
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
Он явит нам на свет все записи, содержащие двойку. Теперь поставим ему лимит и включим опцию SQL_CALC_FOUND_ROWS, а следующим запросом посмотрим, сколько он насчитает нам записей:
<?php
include './mysql.php';
// Запрос
$res = mysql_query("SELECT SQL_CALC_FOUND_ROWS * FROM `table` WHERE `text` LIKE '%2%' LIMIT 0, 10");
// Считаем, сколько записей удовлетворяют условиям
$cnt = mysql_result(mysql_query('SELECT FOUND_ROWS()'), 0);
// Формируем вывод
$table = "<table width=\"50%\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n";
if(mysql_num_rows($res) > 0)
while($row = mysql_fetch_assoc($res))
{
$table .= "<tr>\n<td>\n\t". $row['text']. "\n</td>\n</tr>\n";
}
$table .= "</table>\n";
echo $table;
echo 'Всего найдено '. $cnt .' строк'
Вот на этом принципе мы и построим новый метод, который будем запускать в случае, если потребуется отлистать сложный запрос:
<?php
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function calcQuery($query)
{
// Заменяем SELECT на SELECT SQL_CALC_FOUND_ROWS
$query = preg_replace('#SELECT#i', 'SELECT SQL_CALC_FOUND_ROWS ', $query);
// Выполняем основной запрос
$res = mysql_query($query . ' LIMIT '. $this->NumPage .', '. $this->NumRows * $this->NumColumns);
// Считаем к-во рядов
$this->TableCount = mysql_result(mysql_query('SELECT FOUND_ROWS()'), 0);
// Запускаем метод для менюшки
$this->createLimit();
return $res;
}
У Вас может возникнуть вопрос - а почему отдельным методом. Почему это не сделать сразу?
Ответ тревиален. Дело в том, что запрос с SQL_CALC_FOUND_ROWS работает гораздо медленнее, чем обычный. И использовать его нужно только там, где это действительно необходимо.
Когда потребуется отлистать результаты сложного запроса, вместо метода countQuery() нужно вызвать метод calcQuery() и все будет чики-пики.
Ну а вот и полный текст класса:
<?php
include './mysql.php';
/**
* IRB_Paginator - Class of division of the information on a paginal mode
* NOTE: Requires PHP version 5 or later
* @package IRB_Paginator
* @author IT studio IRBIS-team (www.irbis-team.com)
* @copyright © 2010 IRBIS-team
* @version 0.1
* @license http://www.opensource.org/licenses/rpl1.5.txt
*/
class IRB_Paginator
{
/////////////////////////////////////////////////
// PUBLIC
/////////////////////////////////////////////////
/**
* Establishes page number.
* @var int
*/
public $NumPage = 1;
/**
* Establishes quantity of numbers.
* @var int
*/
public $NumRows = 1;
/**
* Establishes quantity of columns.
* @var int
*/
public $NumColumns = 1;
/**
* Includes mod_rewrite.
* @var string
*/
public $LevelPage = 'page';
/////////////////////////////////////////////////
// PROPERTIES AND PRIVATE
////////////////////////////////////////////////
private $TableTotal = 0;
private $TableCount = 0;
private $StartLink = '';
/**
* Constructor
* @param int $page
* @param int $rows
* @param int $columns
*/
public function __construct($page = 1, $rows = 1, $columns = 1)
{
if($rows > 1)
$this->NumPage = (int)$page;
if($rows > 1)
$this->NumRows = $rows;
if($columns > 1)
$this->NumColumns = $columns;
}
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function countQuery($query)
{
$query = str_replace("\n", " ", $query);
preg_match("#FROM(.+)#i", $query, $table);
$result = mysql_query("SELECT COUNT(*) AS `cnt`
FROM ". $table[1]);
$this->TableCount = mysql_result($result, 0);
$res = mysql_query($query . $this->createLimit());
return $res;
}
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function calcQuery($query)
{
// Заменяем SELECT на SELECT SQL_CALC_FOUND_ROWS
$query = preg_replace('#SELECT#i', 'SELECT SQL_CALC_FOUND_ROWS ', $query);
// Выполняем основной запрос
$res = mysql_query($query . ' LIMIT '. $this->NumPage .', '. $this->NumRows * $this->NumColumns);
// Считаем к-во рядов
$this->TableCount = mysql_result(mysql_query('SELECT FOUND_ROWS()'), 0);
// Запускаем метод для менюшки
$this->createLimit();
return $res;
}
/**
* Calculates a position and prepares a limit for inquiry
* @param int $page
* @access public
* @return string
*/
public function createLimit()
{
$this->TableTotal = intval(($this->TableCount - $this->NumColumns) / $this->NumRows * $this->NumColumns) - 1;
if($this->NumPage < 1)
$this->NumPage = 1;
if(empty($this->TableTotal) || $this->TableTotal < $this->TableCount)
$this->TableTotal = $this->TableCount;
if($this->NumPage > $this->TableTotal)
$this->NumPage = $this->TableTotal;
$start = $this->NumPage * $this->NumRows * $this->NumColumns - $this->NumRows * $this->NumColumns;
if($start < 0)
$start = 0;
return ' LIMIT '. $start .', '. $this->NumRows * $this->NumColumns;
}
/**
* Generates the navigation menu
* @access private
* @param string $link
* @return string
*/
function createMenu($rewrite = false, $level = '')
{
if(!empty($level))
$this->LevelPage = $level;
$this->SetRewrite = $rewrite;
$this->StartLink = $rewrite ? $this->LevelPage .'/' : '?'. $this->LevelPage .'=';
$count = ceil($this->TableTotal / $this->NumRows / $this->NumColumns);
$menu = "\n<!-- IRB_Paginator begin -->\n";
if($count < 13)
{
$i = 1;
$cnt = $count;
}
else
{
if($this->NumPage > 10)
$menu .= $this->createLink(($this->NumPage - 10), '-10<', '_top');