Я не успокоился и дописал его немного. Не совсем еще, только в части интеграции. Еще нужно обработку урла и менюшки разные наделать.
Но вот с интеграцией интересно получилось.
Вставляется куда угодно несколькими строчками:
/**
* Создаем объект
* Аргументами класс принимает номер текущей страницы,
* количество рядов и колонок (при табличном выводе)
* и префикс бд
*/
$group_page = new IRB_Paginator($p1, NUM_GROUP);
/**
* В запросе меняем mysql_query() на один из методов
* countQuery() - для простых запросов
* calcQuery() - для сложных запросов без сортировки и
* cacheQuery() - для кэширования сложных запросов с сортировкой.
* для последнего метода нужно создать в базе таблицу
CREATE TABLE `paginator` (
`id` int(11) NOT NULL auto_increment,
`queryhash` varchar(32) default NULL,
`cnt` int(11) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `querystring` (`queryhash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
*/
$res = $group_page -> countQuery($query1);
/**
* Так можно узнать время исполнения скрипта
* и выбрать оптимальный метод
*/
$group_time = $group_page -> getTime();
/**
* Здесь генерится меню навигации. Криво и косо пока - недоделано.
*/
$group_menu = $group_page -> createMenu('?id='.$id.'&p2='. $p2, 1);
Я не стал делать бенчи и рассуждать теоретически. Просто малость потестил в боевых условиях.
Я сделал таблицу из 1000 000 записей весом в 53 метра. Не ахти как много, но для теста достаточно.
Вот результаты тестирования:
Свернутый текст

В таблице по порядку 1 - 2000 - 4000 и т.д страницы.
Ну как видно, простой коунт работает просто и эфективно. Но если запрос окажется сложным, то будет плохо. Придется выполнять два запроса. Это видно на последнем тесте.
Для этого в мускуле этого есть флаг SQL_CALC_FOUND_ROWS. Удобная вещь. И все бы ничего, но дело в том, что этот запрос шлепает до конца, не затыкаясь на лимите. А значит, если есть сортировка, то сортироваться будет вся выборка. Что очень наглядно видно в тестовой таблице.
Вот тут можно применить кэширование запроса. Я заполнил таблицу кэша 100000 случайными записями, закэшировал результат и сверху кинул еще 100000, что бы было по середине. Результаты меня порадовали.
Вобщем то эксперемент считаю удавшимся, интеграция предельно проста, скорость для сайтов с невысокой посещаемостью достаточно приличная, осталось малость подточить (урлы и менюшки) и будет универсальный пагинатор. Ну идей много надавали, думаю не застрянет.
Так что конкурс дал свои результаты. )))
Кому интересно, посмотреть можно тут, класс ниже, весь тест в аттаче.
Скоро начну тестить все остальные.
Свернутый текст
<?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 © 2009 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;
/**
* Establishes a prefix of tables of a database.
* @var string
*/
public $TablePrefix = '';
/**
* Includes mod_rewrite.
* @var string
*/
public $ModRewrite = 'off';
/**
* Includes a debugging mode.
* @var boolean
*/
public $PaginatorDebug = true;
/**
* Establishes a file of messages on errors.
* @var array
*/
public $ErrorInfo = array(
'mysql_error' => 'Exclusive situation with a database',
'no_query' => 'There is no inquiry for processing',
'no_cache' => 'It is impossible to clear a cache',
'no_rows' => 'The number of numbers or columns should be more zero',
);
/////////////////////////////////////////////////
// PROPERTIES AND PRIVATE
////////////////////////////////////////////////
private $PageUrl = '';
private $TableTotal = 0;
private $TableCount = 0;
private $QueryString = '';
private $RowsCache = 0;
private $NoCache = false;
private $TimeStart = 0;
/////////////////////////////////////////////////
// METHODS
/////////////////////////////////////////////////
/**
* Constructor
* @param int $rows
* @param int $columns
* @param string $prefix
* @Establishes quantity of numbers , columns and prefix database.
*/
public function __construct($page = 1, $rows = 1, $columns = 1, $prefix = '')
{
if($rows > 1)
$this->NumPage = (int)$page;
if($rows > 1)
$this->NumRows = $rows;
if($columns > 1)
$this->NumColumns = $columns;
if(!empty($prefix))
$this->TablePrefix = $prefix;
$this->TimeStart = microtime(true);
}
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function countQuery($query)
{
if(empty($query))
$this->paginatorDebug(__METHOD__, 'no_query');
$query = str_replace("\n", " ", $query);
preg_match("#FROM(.+)#i", $query, $table);
$result = mysql_query("SELECT COUNT(*) AS `cnt`
FROM ". $table[1])
or $this->paginatorDebug(__METHOD__, 'mysql_error');
$this->TableCount = mysql_result($result, 0);
$res = mysql_query($query . $this->createLimit())
or $this->paginatorDebug(__METHOD__, 'mysql_error');
return $res;
}
/**
* Operates a cache of difficult inquiries
* @param string $query
* @access public
* @return void
*/
public function calcQuery($query = '')
{
if(empty($query))
$this->paginatorDebug(__METHOD__, 'no_query');
$query = preg_replace('#SELECT#i', 'SELECT SQL_CALC_FOUND_ROWS ', $query);
$res = mysql_query($query . ' LIMIT '. $this->NumPage .', '. $this->NumRows * $this->NumColumns)
or $this->paginatorDebug(__METHOD__, 'mysql_error');
$this->TableCount = mysql_result(mysql_query('SELECT FOUND_ROWS()'), 0)
or $this->paginatorDebug(__METHOD__, 'mysql_error');
$this->createLimit();
return $res;
}
/**
* Operates a cache of difficult inquiries
* @param string $query
* @param boolean $cache
* @param int $id
* @access public
* @return void
*/
public function cacheQuery($query = '')
{
if(empty($query))
$this->paginatorDebug(__METHOD__, 'no_query');
$res = mysql_query("SELECT `cnt`
FROM `". $this->TablePrefix ."paginator`
WHERE `queryhash` = '". md5($query) ."'")
or $this->paginatorDebug(__METHOD__, 'mysql_error');
$row = mysql_fetch_assoc($res);
if($row['cnt'] > 0){
$this->TableCount = $row['cnt']; }
else
$this->manageCache($query);
$res = mysql_query($query . $this->createLimit())
or $this->paginatorDebug(__METHOD__, 'mysql_error');
return $res;
}
/**
* Counts up quantity of numbers of inquiry
* @param string $query
* @access public
* @return void
*/
private function getCache($query = '')
{
if(empty($query))
$this->paginatorDebug(__METHOD__, 'no_query');
$res = mysql_query("SELECT COUNT(*) AS `cnt`
FROM (". $query .") AS `query`")
or $this->paginatorDebug(__METHOD__, 'mysql_error');
$this->TableCount = mysql_result($res, 0);
}
/**
* Keeps quantity of numbers
* @param string $query
* @access public
* @return int
*/
public function manageCache($query = '')
{
if(empty($query))
$this->paginatorDebug(__METHOD__, 'no_query');
$this->getCache($query);
mysql_query("INSERT INTO `". $this->TablePrefix ."paginator`
SET
`queryhash` = '". md5($query) ."',
`cnt` = ". $this->TableCount ."
ON DUPLICATE KEY UPDATE
`cnt` = ". $this->TableCount)
or $this->paginatorDebug(__METHOD__, 'mysql_error');
return mysql_insert_id();
}
/**
* Clears a cache
* @param int $limit
* @access public
* @return void
*/
public function clearCache()
{
if($this->NoCache)
$this->paginatorDebug(__METHOD__, 'no_cache');
mysql_query("TRUNCATE TABLE `". $this->TablePrefix ."paginator`")
or $this->paginatorDebug(__METHOD__, 'mysql_error');
}
/**
* Calculates a position and prepares a limit for inquiry
* @param int $page
* @access public
* @return string
*/
public function createLimit()
{
if($this->NumRows == 0 || $this->NumColumns == 0)
$this->paginatorDebug(__METHOD__, 'no_rows');
$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;
}
/**
* Prepares parametres for a hyperlink and chooses a menu variant
* @param string $url
* @param int $level
* @param string $lib
* @access public
* @return string
*/
public function createMenu($url = '', $level = 1, $lib = '')
{
if($this->ModRewrite == 'on')
$sep = '/';
else
$sep = (strstr($url, '?') === false)?'?p'. $level .'=':'&p'. $level .'=';
$this->PageUrl = $url . $sep;
if(empty($lib))
return $this->defaultMenu();
else
return $this->selectMenu($lib);
}
/**
* Generates the navigation menu by default
* @access private
* @return string
*/
private function defaultMenu()
{
$total = ceil($this->TableTotal / $this->NumRows / $this->NumColumns);
$menu = "\n<!-- IRB_Paginator begin -->\n<ul>\n";
if($total < 10)
{
for ($i = 1; $i <= $total; $i++)
{
if($this->NumPage == $i)
$menu .= $this->createLink($i, $i, '_active');
else
$menu .= $this->createLink($i);
}
}
else
{
$for = $this->NumPage - 1;
if($for < 1)
$for = 1;
if($this->NumPage > 10)
$menu .= $this->createLink(($this->NumPage - 10), '-10', '_top');
if($this->NumPage > 1)
$menu .= $this->createLink($for, '<<', '_top');
if($this->NumPage == 7)
$menu .= $this->createLink(1, '1', '_top') . $this->createLink('', '<b>...</b>', '_active');
elseif($this->NumPage > 7)
$menu .= $this->createLink(1) .
$this->createLink(2) .
$this->createLink('', '<b>...</b>', '_active');
if($this->NumPage <= 4 && $total > 4)
{
$count = ($total < 10)? $total : 10;
for ($i = 1; $i <= $count; $i++)
{
if($this->NumPage == $i)
$menu .= $this->createLink($i, $i, '_active');
else
$menu .= $this->createLink($i);
}
}
else
{
if($this->NumPage - 5 < 1)
{
$i = 1;
$count = 10;
}
elseif($this->NumPage >= $total)
{
$i = $total - 10;
$count = $total;
}
else
{
$i = $this->NumPage - 5;
$count = $total;
}
if($count - $i > 10)
$count = $i + 10;
if($count > $total)
$count = $total;
for ( ; $i <= $count; $i++)
{
if($this->NumPage == $i)
$menu .= $this->createLink($i, $i, '_active');
elseif($i >= $this->NumPage - 5)
$menu .= $this->createLink($i);
elseif($i <= $this->NumPage + 6)
$menu .= $this->createLink($i);
}
}
if($total > 12)
{
if($this->NumPage < $total - 6)
$menu .= $this->createLink('', '<b>...</b>', '_active') .
$this->createLink(($total - 1));
if($this->NumPage < $total - 5)
$menu .= $this->createLink($total);
}
if($this->NumPage < $total)
$menu .= $this->createLink(($this->NumPage + 1), '>>', '_top');
$end = ($this->NumPage + 10 > $total)? $total:$this->NumPage + 10;
if($this->NumPage < $total - 5)
$menu .= $this->createLink($end, '10+', '_top');
}
$menu .= "</ul>\n<!-- IRB_Paginator end -->\n";
return $menu;
}
/**
* Makes a hyperlink
* @param int $page
* @param string $num, $class
* @access public
* @return string
*/
public function createLink($page, $num = '', $class = '')
{
if(empty($num))
$num = $page;
if($class == '_active')
return "<li class=\"IRB_paginator_active\">". $num ."</li>\n";
else
return "<li class=\"IRB_paginator". $class ."\">
<a href=\"". $this->PageUrl . $page ."\" />". $num ."</a></li>\n";
}
/**
* Considers time of performance of a script
* @access public
* @return string
*/
public function getTime()
{
return sprintf('Performance time %.5f с', microtime(true) - $this->TimeStart);
}
/**
* Function of diagnosing of errors
* @param string $method
* @param string $error
* @access private
* @return void
*/
private function paginatorDebug($method, $error)
{
if($this->PaginatorDebug)
die('<b>' . $method .':</b><br>
<b style="color:red">'. $this->ErrorInfo[$error] .'</b>
<br>'. mysql_error());
}
}
Спустя 35 минут, 18 секунд (9.12.2009 - 20:09) glock18 написал(а):
Так я и не понял. С кэшем все так же осталось? Да, видно, что из таблицы с кэшем для like '%%' выбираться будет быстрее, чем каунт по 1000000. оно и понятно. но как кэш чистить то? вот считаем мы например комментарии. всего их на данный момент 1000000. я написал еще один, и нужно сбросить кэш или пересчитать и записать по-новой. здесь пока только увидел очистку полным убиением всего живого в таблице. если так чистить из-за моего коммента, то лучше уж каунт сразу 
результаты хорошие, но опять же непонятно как быть с очисткой кэша (это поверь очень важно, а если не получится, то все напрасно считай сделано). развеешь мои сомнения?

результаты хорошие, но опять же непонятно как быть с очисткой кэша (это поверь очень важно, а если не получится, то все напрасно считай сделано). развеешь мои сомнения?
Спустя 11 минут, 34 секунды (9.12.2009 - 20:20) twin написал(а):
А в чем такой затык то... Не вопрос и коунт поставить.
А вообще подумать надо конечно. Меня больше волновал сам принцип, как всем угодить тремя строчками.
Следующим этапом возьмусь за кэш.

А вообще подумать надо конечно. Меня больше волновал сам принцип, как всем угодить тремя строчками.
Следующим этапом возьмусь за кэш.
Спустя 10 секунд (9.12.2009 - 20:21) glock18 написал(а):
Кстати говоря реализация шибко похожа на типичное кэширование. только в качестве хранилища используется таблица, а не память. так почему не сделать упор на традиционные средства? до сих пор не понимаю, чего в позу вставать из-за двух с половиной строк. Лично мне при интеграции важнее, чтобы скрипт не делал чего-то, на что я повлиять не смогу, не изменяя его. Как он строит вывод - это то, на что можно будет по всей видимости повлиять только опираясь на предопределенные методы.
ЗЫ:
тут подумал насчет интеграции. некоторая часть предоставленных скриптов предлагает решение типа
1. получить count
2. сформировать постраничку
3. вывести данные.
4. вывести постраничку.
при этом в этих работах скрипт сам по себе выполняет только вторую и четвертую части.
так вот я к чему. взять типичный скрипт, в который будет встраиваться постраничка. он уже будет содержать вывод данных (куда иначе интегрировать). мое личное мнение - написать count для того, чтобы интегрировать скрипт на сайт - мелочь.
кстати говоря интеграция типа createMenu (выше) как раз потребует убить весь вывод, которые был до этого. что тоже являет собой изменения в изначальном скрипте.
ЗЫ:
тут подумал насчет интеграции. некоторая часть предоставленных скриптов предлагает решение типа
1. получить count
2. сформировать постраничку
3. вывести данные.
4. вывести постраничку.
при этом в этих работах скрипт сам по себе выполняет только вторую и четвертую части.
так вот я к чему. взять типичный скрипт, в который будет встраиваться постраничка. он уже будет содержать вывод данных (куда иначе интегрировать). мое личное мнение - написать count для того, чтобы интегрировать скрипт на сайт - мелочь.
кстати говоря интеграция типа createMenu (выше) как раз потребует убить весь вывод, которые был до этого. что тоже являет собой изменения в изначальном скрипте.
Спустя 3 минуты, 29 секунд (9.12.2009 - 20:24) glock18 написал(а):
Цитата |
А в чем такой затык то... Не вопрос и коунт поставить. |
ну вот ты попробуй банально с этим кэшированием. вот оно включено.
допустим страница имеет размер 10. таблицу чистим для начала.
забей в нее 8 записей, к примеру.
открой страницу со скриптом -> данные будут закэшированы.
забей еще 10 записей. открой страницу еще раз.
Спустя 21 минута, 43 секунды (9.12.2009 - 20:46) twin написал(а):
Цитата |
так почему не сделать упор на традиционные средства? до сих пор не понимаю, чего в позу вставать из-за двух с половиной строк. |
А где тут позу то ты увидел

Цитата |
допустим страница имеет размер 10. таблицу чистим для начала. забей в нее 8 записей, к примеру. открой страницу со скриптом -> данные будут закэшированы. забей еще 10 записей. открой страницу еще раз. |
Вообще, 95% скриптов, куда по моим оценкам может быть встроен этот класс, будут пользоваться коунтом. Потому что такой класс нужен тем, кто пока затрудняется написать свой вариант. Там о сложных запросах и речи быть не может. Остальное - больше спортивный интерес. А кэш, это вообще крайний случай. Я же говорю - надо думать. Может для редактирования метод написать... Не знаю пока.
Но уж больно хочется сделать универсальную машинку)))
Цитата |
мое личное мнение - написать count для того, чтобы интегрировать скрипт на сайт - мелочь. |
Да не мелочь это... Когда запрос довольно сложный и не опирается на постоянную таблицу - ой как не просто. Ни для разработчика, ни для сервера.
Вот для таких единичных случаев хочется тоже что то сделать удобоваримое.
Цитата |
кстати говоря интеграция типа createMenu (выше) как раз потребует убить весь вывод, которые был до этого. что тоже являет собой изменения в изначальном скрипте. |
Не понял, зачем? Просто вывести туда, куда нужно и все... Убивать не надо никого. Если ты про урл, то я написал, что не делал пока. Времянка для проверки.
Спустя 5 минут, 36 секунд (9.12.2009 - 20:51) glock18 написал(а):
Не, не про урл.
Вот у меня сайт
имеет такой вывод данных из базадаты. если я вставлю createMenu, то его убрать надо будет.
хотя наверно вру я. получается то же самое, что и при обычной реализации. только на один запрос меньше руками писать. это в плане интеграции, понятно.
а что касается кэширования. так ведь только с ним это все круто так. потому что если без него, то будет каунт, да еще и более адский, чем мог бы быть.
то есть либо с кэшированием, либо более простой каунт. третье не дано, на мой взгляд.
Вот у меня сайт
<div>
<?php foreach($items as $item) :?>
<p><?php print_r($item);?></p>
<?php endforeach; ?>
</div>
имеет такой вывод данных из базадаты. если я вставлю createMenu, то его убрать надо будет.
хотя наверно вру я. получается то же самое, что и при обычной реализации. только на один запрос меньше руками писать. это в плане интеграции, понятно.
а что касается кэширования. так ведь только с ним это все круто так. потому что если без него, то будет каунт, да еще и более адский, чем мог бы быть.
то есть либо с кэшированием, либо более простой каунт. третье не дано, на мой взгляд.
_____________
Если вам недостаточно собственных заблуждений, можно расширить их мнениями экспертов.
Нужно уважать мнение оппонета. Ведь заблуждаться - его святое право.
Настаивал, настаиваю и буду настаивать на своем. На кедровых орешках.
