Paginacja (stronicowanie) w PHP

Zaczynając swoją przygodę z PHP nie miałem pojęcia jak wykonać paginację newsów, która załamywałaby łańcuchy liczb w momencie, w których chce. Na dzień dzisiejszy postanowiłem napisać swój nowy pager, gdyż ten, który dotychczas używałem przez ostatnie 2 lata nie odpowiadał mi pod trzema względami:

  • nagromadzenie metod, tj. setLimit(), getLimit(), zamiast pojedynczego limit(),
  • brak możliwości ustawienia załamywania się łańcucha (domyślnie były to 2 liczby):
    1, 2 ... 6, 7, 8 ...  12, 13.
  • brak możliwości definiowania limitu, ilości elementów oraz aktualnie przeglądanej strony w jednej metodzie, najchętniej w samym konstruktorze.

Ostatnio potrzebowałem zwiększyć limit liczb “z przodu” i “tyłu” oraz “w środku”:
1, 2, 3 ... 5, 6, 7, 8, 9 ...  11, 12, 13

Dla tych, którzy ciągle szukają komponentu obsługującego paginację prezentuję Vframe_Pagination.

Implementacja od strony kontrolera (metoda krótka):

$oPager = new Vframe_Pagination($iItemsCount, $iLimit, $iCurrentPage);

Metoda długa:

$oPager = new Vframe_Pagination();
$oPager->limit($iLimit);
$oPager->items($iItemsCount);
$oPager->page($iPage);

Metody limit, items oraz page zwracają liczby odpowiadające ich nazwą niezależnie od tego, czy została podana nowa wartość w argumencie, czy nie, co jest absolutnie wygodnym (dla mnie) rozwiązaniem.

Aby wyświetlić oczekiwane rekordy, wykorzystujemy pagera:

$aData = $oModel->GetList($iUser, $oPager->start(), $oPager->limit());

Lub bezpośrednio w zapytaniu do bazy dancyh:

$sSql = "SELECT news_id FROM news LIMIT " . $oPager->start() . ", " . $oPager->limit();

Od strony widoku, prezentacja pagincaji prezentuje się w bardzo prosty sposób:

<?php echo $oPager->Render(true); ?>

Metoda render przyjmuje kolejno:

  1. $mMode (mixed: bool, null, string), jeżeli true, paginacja jest zwracana w formacie HTML. Jeżeli null, numerki stron podane w formie array’a zamiast linków HTML. Jeżeli string, zostaje zwracana wartość po renderowaniu pagera, np. element startujący przedział mnożony po limicie: $oPager->Render(‘start’);. Ten sam efekt powoduje metoda $oPager->start();
  2. $iDrawCutLimit (int) ilość numerków na początku i końcu łańcucha paginacji.
  3. $iDrawCutLimitCenter (int) ilość numerków “w środku” obok aktualnie przeglądanej strony.
  4. $bNavigation (bool) linki “poprzednia” i “następna” strona znajdujące się na początku i końcu łańcucha.

Możemy sami ostylować linki generowane przez pager. Wystarczy że w widoku dodamy swój apperance:

$this->oPager->PatternPage('<a href="?[$]">[$]</a>');
$this->oPager->PatternPageCurrent('<strong>[$]</strong>');
$this->oPager->PatternPageNavigation('<a href="?[$]" rel="nofollow">[$$]</a>', array('&laquo; poprzednia', 'następna &raquo;'));
$this->oPager->PatternSeparator('<span>...</span>');

Końcowy efekt, możemy nie wyświetlać pagera, gdy jest tylko jedna strona elementów:

<?php if($this->oPager->Render('pages') > 1) {
// wyswietl pager...
} ?>



7 Responses to “Paginacja (stronicowanie) w PHP”

  1. tiraeth says:

    “nagromadzenie metod, tj. setLimit(), getLimit(), zamiast pojedynczego limit(),”
    — Od tego są gettery i settery (zresztą nieźle działające od PHP w wersji 5), żeby z nich korzystać.

    “Jeżeli string, zostaje zwracana wartość po renderowaniu pagera, np. element startujący przedział mnożony po limicie: $oPager->Render(’start’);. Ten sam efekt powoduje metoda $oPager->start();”
    — Czy to nie jest bezsensowne?

    Znowu z przykrością muszę powiedzieć, że napisałeś ponad 300 linii, z czego ponad połowa jest niepotrzebna (nie mówię o komentarzach). Mamy gettery, mamy settery, samo ustawienie szablonu (czyli Twoje “Pattern”) powinno być obsługiwane jedną metodą (przyjmującą np. tablicę-hasz).

    Ilość to nie jakość. Wygląd kodu też nie. Osobiście nie polecałbym użycia tego, z prostego powodu, namotanie w tej klasie.

  2. krzyzak says:

    poza tym co to jest paginacja? chyba chodziło Ci o stronnicowanie.

  3. Athlan says:

    @tiraeth:
    — Gettery i settery nie są uniwersalne pod względem rzucania wyjątków (zarówno message, jak i walidacji wprowadzanych danych), na czym mi zależy. Pisanie ifów w setterach jest mijaniem się z celem.
    — Sensowne, czy nie… tak czy siak metoda Render() posiada tablicę po renderowaniu, dlatego parametr jako string jest dodatkową opcją jako klucz tej tablicy, który ma zwrócić.
    — 3 patterny można by było zmienić w jeden – tu masz całkowitą rację. Natomiast czwarty odnoszący się do nawigacji powinien zostać, jest wygodniejszy (patrz: argumenty funkcji i ich wykorzystanie).
    — Klasa po usunięciu komentarzy ma ~200 linii, 300 ma już z :) Pozwoliłem sobie policzyć.

    @krzyzak: Stronnicowanie to równie trafne pojęcie, ale chodziło mi o paginację :-)

    Ale dodam stronicowanie do tytułu.

  4. matipl says:

    Mi się nie podoba takie sztuczne zmniejszanie linii kodu, wolałbym z set/get i rozdzieleniem render na kilka metod, a nie jako możliwy parametr null, bool oraz string(!)

    Poza tym dziwię się, że jeszcze rozwijasz Vframe, chociaż pewnie masz sporo projektów już popisanych, i nagle sobie klient o czymś przypomni.
    Dzisiaj mamy już dosyć sporo dobrze rozbudowanych frameworków, paginację mamy chociażby we wspomnianym Zend_Paginator

  5. d~b says:

    wszystko fajnie, tylko tu masz BUGA:)
    przykład:
    $iItemsCount=1 lub 0;
    $iLimit=1;
    $iCurrentPage=3;

    powinniśmy dostać:
    LIMIT 0, 1
    a dostajemy
    LIMIT 1, 1

    linia: 110
    należy zamienić na: return $this->render(‘start’)-1;

  6. Athlan says:

    @d~b: to nie bug. W przypadku MySQL limit idzie od zera, natomiast biblioteka jest zaprojektowana pod bardziej uniwersalne rozwiązania, tylu indeksowanie tablic od indeksu pierwszego. Element startowy programista dopasowuje sobie w zależności od własnych potrzeb i narzuconych przez niego form indeksowania. Startowym elementem jest liczba naturalna 1. Jeżeli chcesz zero, należy faktycznie zdekrementować.

Leave a Reply