PHP

Пагинатор

Шаг 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');
              
Добавлено: 15 Августа 2013 09:23:13 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...