Почти универсална техника за странициране на резултати от БД.
Публикувана от ko6rata на January 19 2011 10:35:58

Разширена новина
В този урок като ще видите как можете да си направите почти универсален страницатор.

Какво ново в тук показаната реализация?
Тук показаната реализация има следните по-важни черти:
1. Почти универсална е
2. Ниска степен на повтаряемост на кода при повторна нужда
3. Лесна адаптация към сложни БД заявки с възможност за управление на сортирането и групирането на резултати.
4. Приложимост върху повече от един списъка едновременно.
5. Грижата за генерирането на навигатора за разходка между страниците вече няма да ви тормози.

Казвам, че е почти универсална, защото при свързване на таблици по някое поле, понякога логиката на сортирането на резултатите не може да се приложи в самата SQL заявка и сортирането трябва да се прави извън функцията която ще видите тук.

Описание на функцията
Функцията наречена pageQuery() се вика с три параметъра $qry, $pagenow, $perpage, като последния не е задължителен.
Първия параметър е асоциативен масив в който са зададени компонентите на заявката, както и някои други параметри управляващи процеса.
Втория и третия управляват текущата страница и броя резултати които да се показват на страница.

Идеята на функцията е да свърши цялата работа която винаги е еднаква при тази задача.
1. Първо се прави запитване към БД с което се уточнява колко редове общо отговарят на критерия по който търсим (ако изобщо търсим нещо)
2. На базата на този брой и променливата $perpage се изчисляват броя на страниците.
3. Конструира се LIMIT клауза
4. Конструира се навигатор от вида: <<Първа <Предишна [ 1 | 2 | 3 | 4 | 5 | 6 ] Следваща> Последна>>
5. Конструира се изходен масив.

Листинг на функцията (коментарите са важни)

//Някъде си имаме дефинирани константите:
define('ROWS_PREPAGE', 20);
define('DEFAULT_PAGER_NAME', 'pager');

function pageQuery($qry, $pagenow=1, $perpage=0)
{

// В началото валидираме стойностите на входните променливи $pagenow и perpage
// Ако със стойностите нещо не е наред тогава им задаваме тези по подразбиране

if(!is_int($pagenow) || !isset($pagenow) || $pagenow < 1) $pagenow = 1;
if(!is_int($perpage) || $perpage == 0) $perpage = ROWS_PREPAGE;

//Валидираме стойностите на масива съдържащ компонентите на заявката
if(!isset($qry['PAGER']) || $qry['PAGER'] == '') $qry['PAGER'] = DEFAULT_PAGER_NAME;

if(!isset($qry['WHERE']) || $qry['WHERE'] == '') $where = ' ';
else $where = " WHERE $qry[WHERE]";

if(!isset($qry['ORDERBY']) || $qry['ORDERBY'] == '') $order_by = ' ';
else $order_by = " ORDER BY $qry[ORDERBY]";

if(!isset($qry['GROUPBY']) || $qry['GROUPBY'] == '') $group_by = ' ';
else $group_by = " GROUP BY $qry[GROUPBY]";

//Изпълняваме заявка към БД с цел да уточним колко резултати удовлетворяват WHERE клаузата
$query = "SELECT COUNT(*) AS CNT FROM $qry[FROM] $where $group_by";
$row_count = mysql_result(mysql_query($query, __FILE__.': '.__LINE__));

//И изчисляваме в колко страници се вместват резултатите които ни интересуват
$page_count = ceil($row_count/$perpage); //Важно е да използвате ceil() !

//Проверяваме дали номера на исканата страница не превишава $page_count
//Ако да -> променяме номера й на $page_count
if($pagenow > $page_count) $pagenow = (int)$page_count;

//Следващия фрагмент конструира LIMIT клаузата на SQL
//заявката, както и низ който ще представи информация за резултатите
if(is_int($pagenow) && $pagenow > 0){
$limit = (($pagenow-1)*$perpage).','.($perpage);
$str_results = (($pagenow-1)*$perpage+1).' до '.min($row_count, ($pagenow*$perpage));
}else {
$limit = '0,'.$perpage;
$str_results = '1 до '.min($row_count, ($pagenow*$perpage));
}
$limit = 'LIMIT '.$limit;

//Ако случайно няма повече от 1 страница -> Отказваме се от LIMIT клаузата
if($page_count == 0 || $page_count == 1) $limit = '';


/*Долния фрагмент се грижи за съзвадането на навигатора, чрез който потребителите могат да преминават
от страница в страница. Тук можете да поставите всякаква логика, която удовлетворява вашия графичен дизайн
В случая навигатора е стандартен от типа:

<<Първа <Предишна [ 1 | 2 | 3 | 4 | 5 | 6 ] Следваща> Последна>>
*/

//генериране на Първа и Предишна. Внимаваме да не накараме Предишна да сочи извън диапазона
$navi_str = "<a href="some_url.com?$qry[PAGER]=1"><<Първа </a>";
if($pagenow > 1) $navi_str .= "<a href="some_url.com?$qry[PAGER]=".$pagenow-1.""> <Предишна </a>";
else $navi_str .= " <Предишна ";
//Номера на страниците. Текущата не е линк но е bold-ната
$separator = '';
for ($i=1; $i<=$page_count; $i++){
if($i != $pagenow) $navi_str .= "<a href="some_url.com?$qry[PAGER]=$i">$separator $i </a>";
else $navi_str .= "$separator <b>$i</b> ";
$separator = "|";
}
//генериране на Следваща и Последна. Отново внимаваме да не накараме Следваща да сочи извън диапазона
if($pagenow < $page_count) $navi_str .= "<a href="some_url.com?$qry[PAGER]=".$pagenow+1.""> Следваща> </a>";
else $navi_str .= " Следваща> ";
$navi_str .= "<a href="some_url.com?$qry[PAGER]=$page_count"> Последна>></a>";

//ВАЖНО: В горния фрагмент, е изключително важно да се генерират правилно рефернциите на линковете!
//За целта се използва GET променлива с име $qry['PAGER'] която указва коя страничка искаме да разгледаме
//при щракването на този линк =>
//Целта името да може да бъде сменяно е, че може да ни се наложи да разлистваме повече от един списък в една HTML страница
// ОТНОВО: Внимавайте много с URL-тата. Във вашия конкретен случай ще е различно от тук!!!

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

//Като за начало да подготвим заявката с LIMIT каузата чрез която фактически ще извлечем резултатите от БД
$res['QUERY'] = "SELECT $qry[SELECT] FROM $qry[FROM] $where $group_by $order_by $limit";

//Всякъква друга информация
$res['PAGECOUNT'] = $page_count;
$res['PAGENOW'] = $pagenow;
$res['PERPAGE'] = $perpage;
$res['ROWCOUNT'] = $row_count; //Това показва общия брой резултати
$res['NAVIGATOR'] = $navi_str;
$res['RESULTS_INFO'] = "Резултати от $str_results; Страница $pagenow от $page_count";

//Ах колко банално - да върнем резултата! :)
return $res;
}

Използване
Сега да видим как можем да ползваме тази функция
Нека имаме някаква база данни с две таблици mastr_table и slave_table, като данните в двете се свързват по полето ID на master_table. Нека всички работи по канекцията към базата не ни интересуват. Сега за нас е важно следното:

//1. Като начало конструираме масив с компонентите на SQL заявката
$qry['SELECT'] = 'master_table.*, slave_table.*';
$qry['FROM'] = 'master_table, slave_table';
$qry['WHERE'] = 'master_table.ID>5 AND slave_table.MasterID = master_table.ID';
$qry['ORDERBY'] = 'master_table.ID DESC';
$qry['PAGER'] = 'articles_pager';
//2. Извикваме pageQuery();
$pgr_result = pageQuery($qry, (int)$HTTP_GET_VARS['articles_pager']);
//3. Взимаме си резултатите от Базата Данни
$result = mysql_query($pgr_result['QUERY']);
//4. И по най-стандартния начин обхождаме резултатите за да ги визуализираме
while ($row = mysql_fetch_array($result)){
//..като тук формираме HTML-а който ще визуализира самите резултати с данни
}

// Живи ли сте още? :)

Сега всеки път когато ви потрябва странициране просто е нужно да направите стъпките от 1 до 4 показани по-горе.
Вече можете да сте независими от случая и когато ви потрябва странициране го имате в рамките на 40 секунди.

Някои неказани неща
1. $qry масива има следната структура
$qry['SELECT'] - съдържа информация за SELECT клаузата. Задължително трябва да присъства
$qry['FROM'] - съдържа информация за FROM клаузата. Задължително трябва да присъства
$qry [ 'WHERE' ] - съдържа информация за WHERE клаузата. Незадължителна
$qry [ 'ORDERBY' ] - съдържа информация за ORDER BY клаузата.Незадължителна
$qry [ 'GROUPBY' ] - съдържа информация за GROUP BY клаузата.Незадължителна
$qry [ 'PAGER' ] - името на странициращата GET поменлива. Използва се за генериране на линковете в навигатора. Елемента не е задължиелен.

2. Обърнете внимание как е повикана pageQuery() в стъпка 3. и по специално на $HTTP_GET_VARS[ 'xxxxx' ];