[ Поиск ] - [ Пользователи ] - [ Календарь ]
Полная Версия: Досужие мысли о пагинаторе
twin
Сделал я полигончик, что бы конкурсные скрипты обкатать, за одно протестил свой класс.
Я не успокоился и дописал его немного. Не совсем еще, только в части интеграции. Еще нужно обработку урла и менюшки разные наделать.
Но вот с интеграцией интересно получилось.
Вставляется куда угодно несколькими строчками:
/**
* Создаем объект
* Аргументами класс принимает номер текущей страницы,
* количество рядов и колонок (при табличном выводе)
* и префикс бд
*/

$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 метра. Не ахти как много, но для теста достаточно.

Вот результаты тестирования:
Свернутый текст
user posted image


В таблице по порядку 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 de
bugging 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. я написал еще один, и нужно сбросить кэш или пересчитать и записать по-новой. здесь пока только увидел очистку полным убиением всего живого в таблице. если так чистить из-за моего коммента, то лучше уж каунт сразу smile.gif

результаты хорошие, но опять же непонятно как быть с очисткой кэша (это поверь очень важно, а если не получится, то все напрасно считай сделано). развеешь мои сомнения?

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

Спустя 10 секунд (9.12.2009 - 20:21) glock18 написал(а):
Кстати говоря реализация шибко похожа на типичное кэширование. только в качестве хранилища используется таблица, а не память. так почему не сделать упор на традиционные средства? до сих пор не понимаю, чего в позу вставать из-за двух с половиной строк. Лично мне при интеграции важнее, чтобы скрипт не делал чего-то, на что я повлиять не смогу, не изменяя его. Как он строит вывод - это то, на что можно будет по всей видимости повлиять только опираясь на предопределенные методы.

ЗЫ:
тут подумал насчет интеграции. некоторая часть предоставленных скриптов предлагает решение типа

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 написал(а):
Цитата
так почему не сделать упор на традиционные средства? до сих пор не понимаю, чего в позу вставать из-за двух с половиной строк.

А где тут позу то ты увидел smile.gif Какие ты имеешь ввиду средства? Мускул и эти запросы закэширует во первых, а во вторых, первый то запрос все равно долгим может оказаться. Или ты про что...
Цитата
допустим страница имеет размер 10. таблицу чистим для начала.
забей в нее 8 записей, к примеру.
открой страницу со скриптом -> данные будут закэшированы.
забей еще 10 записей. открой страницу еще раз.

Вообще, 95% скриптов, куда по моим оценкам может быть встроен этот класс, будут пользоваться коунтом. Потому что такой класс нужен тем, кто пока затрудняется написать свой вариант. Там о сложных запросах и речи быть не может. Остальное - больше спортивный интерес. А кэш, это вообще крайний случай. Я же говорю - надо думать. Может для редактирования метод написать... Не знаю пока.
Но уж больно хочется сделать универсальную машинку)))

Цитата
мое личное мнение - написать count для того, чтобы интегрировать скрипт на сайт - мелочь.

Да не мелочь это... Когда запрос довольно сложный и не опирается на постоянную таблицу - ой как не просто. Ни для разработчика, ни для сервера.
Вот для таких единичных случаев хочется тоже что то сделать удобоваримое.

Цитата
кстати говоря интеграция типа createMenu (выше) как раз потребует убить весь вывод, которые был до этого. что тоже являет собой изменения в изначальном скрипте.

Не понял, зачем? Просто вывести туда, куда нужно и все... Убивать не надо никого. Если ты про урл, то я написал, что не делал пока. Времянка для проверки.

Спустя 5 минут, 36 секунд (9.12.2009 - 20:51) glock18 написал(а):
Не, не про урл.

Вот у меня сайт

<div>
<?php
foreach($items as $item) :?>
<p><?php
print_r($item);?></p>
<?php
endforeach; ?>
</div>


имеет такой вывод данных из базадаты. если я вставлю createMenu, то его убрать надо будет.

хотя наверно вру я. получается то же самое, что и при обычной реализации. только на один запрос меньше руками писать. это в плане интеграции, понятно.

а что касается кэширования. так ведь только с ним это все круто так. потому что если без него, то будет каунт, да еще и более адский, чем мог бы быть.

то есть либо с кэшированием, либо более простой каунт. третье не дано, на мой взгляд.


_____________
Если вам недостаточно собственных заблуждений, можно расширить их мнениями экспертов.

Нужно уважать мнение оппонета. Ведь заблуждаться - его святое право.

Настаивал, настаиваю и буду настаивать на своем. На кедровых орешках.

user posted image
Быстрый ответ:

 Графические смайлики |  Показывать подпись
Здесь расположена полная версия этой страницы.
Invision Power Board © 2001-2024 Invision Power Services, Inc.