Kohana Framework – Paginacja + ORM

Znając już zagadnienia związane z modelami ORM w Kohana Framework oraz wiążącymi je relacjami możemy pójść o krok dalej. Dzisiaj omówimy prosty sposób na stronicowanie wyników z bazy danych, inaczej nazywając – paginacje. W przypadku małej ilości wyników np: 10-20 problemu nie ma ponieważ nie burzy to wizualnego układu naszej strony. Schody zaczynają się gdy mamy w bazie dużą ilość rekordów: >100. Jest na to znane wszystkim rozwiązanie zwane paginacją/stronicowaniem.

Czym jest paginacja?

Paginacja jest niczym innym jak podziałem rekordów na wiele stron, zawierających jednakową ilość wyników. Biorąc pod uwagę, że mamy 100 rekordów w bazie danych a limit naszej paginacji wynosi 10 elementów na stronę, musimy wygenerować 10 stron, po 10 wyników na każdej. Pod listą elementów musimy umieścić specjalne odnośniki pozwalające przechodzić pomiędzy stronami: [1][2][3]…[10] itd.

Co tak naprawdę robi paginacja?

Działanie mechanizmu jest bardzo proste, poprzez parametr w adresie (może to być query_string lub parametr routingu) operujemy na aktualnie wyświetlanej stronie.
Znając numer strony, wartość limitu elementów na jedną stronę oraz ilość wszystkich elementów możemy modyfikować zapytanie do bazy danych dodając warunki OFFSET oraz LIMIT.

Co na to SEO ?

Nie jestem specjalistą od SEO marketingu lecz podstawowe kwestie optymalizacyjne powinien znać każdy szanujący się webmaster. Zgodnie z wytycznymi prezentowanymi przez Google aby wskazać robotowi i jednocześnie połączyć ze sobą wyniki wyszukiwania, dobrze jest użyć atrybutów rel="next" oraz rel="prev" wskazujących na następną oraz poprzednią stronę. Robot wiąże w ten sposób listę elementów, które mają być traktowane jako jeden ciągły wynik.

W przypadku gdy jesteśmy na pierwszej stronie, w sekcji należy umieścić wyłącznie atrybut „next”

W przypadku gdy numer strony jest większy niż 1 np, ?page=2 oraz mniejszy niż ostatnia wartość strony należy umieścić oba atrybuty wskazujące analogicznie na poprzednią i następną stronę z paginacji

Jeśli w paginacji dobrniemy do ostatniej strony, przykładowo 100, to w <head> musi być wyłącznie atrybut powrotni czyli rel="prev"

Piszemy mechanizm paginacji

Są do tego gotowe moduły, ale nie o to nam chyba chodzi 🙂 Chciałbym aby każdy zrozumiał zasadę działania takiej paginacji więc napiszemy podstawową wersję mechanizmu.

Zaczynamy od przygotowania testowej struktury bazy danych (tabela o nazwie „elements”) oraz wypełnienie jej przykładową treścią.

Następnie tworzymy model o nazwie Element

Czas na kontroler, nazwijmy go Elements:

Utwórzmy również widok dla naszej strony wyników, niech to będzie plik /application/views/elements.php

Piszemy mechanizm paginacji

Jakie informacje będą nam potrzebne?

  • Aktualnie wybrana strona.
  • Ilość elementów na 1 stronę.
  • Ilość wszystkich elementów.
  • Ilość wszystkich wygenerowanych stron, zgodnie z działaniem (ilość wszystkich elementów / ilość elementów na 1 stronę) zaokrąglamy w górę.
  • Musimy obliczyć również „offset” czyli przesunięcie, które dodane do zapytania wybierze odpowiednie wyniki.

Wewnątrz akcji index kontrolera Elements umieszczamy następujący kod zgodnie z listą zapotrzebowań wyżej.

Następnie we wcześniej utworzonym widoku umieszczamy kod generujący listę elementów oraz paginację.

Gotowe, nasza paginacja powinna działać 🙂

Konkluzja

Utworzyliśmy dzisiaj mechanizm paginacji. Jak widać kod jest bardzo prosty, a całość łatwo opakować w osobny moduł, dopracowując szczegóły i dodając plik konfiguracyjny. Użyta funkcja ceil ma za zadanie zaokrąglać w górę liczbę stron naszej paginacji, tak aby dla minimum 1 dodatkowego elementu utworzyć kolejną stronę.

Dzięki warunkowi OFFSET nasz system generował zapytanie zmieniając przesunięcie wraz ze zmianą kolejnych podstron paginacji:
Strona 1 – SELECT * FROM elements ORDER BY id ASC LIMIT 10 OFFSET 0
Strona 2 – SELECT * FROM elements ORDER BY id ASC LIMIT 10 OFFSET 10
Strona 5 – SELECT * FROM elements ORDER BY id ASC LIMIT 10 OFFSET 40

Jak można rozbudować ten system paginacji? W przypadku gdy mamy bardzo dużą ilość stron (50-100 lub więcej), nasza paginacja raczej nie zmieści się w obrębie strony i popsuje widok. Za pomocą instrukcji warunkowej IF można wygenerować dynamiczną paginację w postaci [1][2][3][4][5] ... [99][100][101][102], której widoczny zakres stron będzie aktualizowany wraz z przejściem do wyższej/niższej podstrony.

W razie pytań lub problemów zapraszam do dyskusji na naszym forum lub proszę zadać pytanie w komentarzu.

Programista, administrator - miłośnik nowych technologii. Jak każdy fachowiec w branży nie oprę się porannej kawie w towarzystwie świeżej prasy. Hobbystycznie fotografuję, psuję, naprawiam, lutuję. Czego nie lubię? Nieskromnych ludzi i brzydkiego kodu.

  • Riu

    A dlaczego nie wykorzystać modułu paginacji dla KO, który ma już to wszystko plus to czego nie ma artykule?
    Dlaczego to nie jest odseparowane, od akcji – w sensie za każdym razem jak będziesz chciał mieć paginacje to będziesz to robił kopiego pejsta?

    Dlaczego „query” zamiast „param”?
    Dlaczego obliczasz pewne rzeczy w widoku?
    Do tworzenia linków to HTML::anchor, prawda?
    Dlaczego zapytanie do bazy nie jest częścią modelu (np.: $elements->getAll($offset, $per_page)?
    Dlaczego zakładasz, że zawsze zwróci jakiś wynik?

    • sbl

      Gdybym wykorzystał moduł paginacji to jaki byłby cel tego wpisu? Jak chcesz nauczyć syna wymiany filtra powietrza w samochodzie, to oddajesz auto do ASO czy sam demonstrujesz mu jak przebiega ten proces?

      Wiem, poszedłem trochę na łatwiznę, ale głównym celem było wytłumaczenie o co chodzi z paginacją oraz jak generuje dalsze podstrony. Mogłem również użyć tutaj czystego PHP i nie byłoby tego problemu.

      – Dlaczego „query” zamiast „param”?
      A jakie to ma znaczenie? Nie ma żadnego, jak ktoś zechce użyć param to go użyje.

      – Dlaczego obliczam pewne rzeczy w widoku?
      Bo nie spakowałem tego w odrębną klasę, która przekazywałaby do widoku 1 obiekt z wszystkimi potrzebnymi elementami.

      – Dlaczego nie używam helpera do generowania 1 linijki kodu?
      To, że ktoś dał helper do generowania kodu to znaczy, że muszę go używać? Nie, to również nie ma znaczenia, czy użyjesz helpera który wypisze 1 linijkę za Ciebie czy sam ją napiszesz.

      – Dlaczego nie stworzyłem funkcji modelu do przerabiania zapytania?
      Bo nie musi być częścią modelu.

      – Dlaczego zakładam że zawsze zwróci jakis wynik?
      Bo gdy nie zwróci to nic się nie wyświetli 🙂

      Nie wiem czy zauważyłeś ale to nie jest moduł ani kompletna aplikacja tylko wycinek kawałka kodu, który prezentuje zasadę działania paginacji. Oczywiście można to wszystko opakować w osobną klasę, stworzyć uniwersalny moduł, który zwróci do widoku 1 obiekt bez potrzeby zaśmiecania. Można również sprawdzać czy coś zwróci i wyświetlać komunikaty, można również pisać wiersze nocami.

      Dlaczego nie zrobiłem z tego modułu? Bo nie mam czasu 🙂 i poświęcam każdą wolną chwilę aby coś skrobnąć na Mastaha.

      PS. Czekam na jakieś interesujące wpisy z Twojej strony przekazujące praktykę a nie teorię własnych względów 🙂

      • Riu

        „PS. Czekam na jakieś interesujące wpisy z Twojej strony przekazujące praktykę a nie teorię własnych względów :)”

        Mogłeś sobie oszczędzić to PS. Wskazałem Ci praktyczne elementy o które trzeba zapytać, albo o których trzeba wspomnieć, bo chociaż są małe to są istotne w praktyce.

  • Maciek

    Poprawne słowo to stronicowanie, nie paginacja 🙂

  • piotrooo

    Kod kontrolera jest jedną wielka masakrą – wstyd to publicznie pokazywać.

  • Satyr

    Dodam od siebie tylko, że o ile jeszcze ktoś nigdy w życiu nie robił paginacji, to żeby nie robił tak jak jest to pokazane w przykładzie powyżej;) Przy okazji, jeśli w bazie nie masz rekordów to „all_pages” będzie wynosić 0, tym samym powodując niekończącą się pętlę „for” z widoku.

    Doceniam chęci, ale wg mnie więcej pożytku byłoby gdybyś zrobił tutorial dotyczący integracji istniejącego modułu do stronicowania, niż pokazywał jak napisać swój. Bardziej przydałaby się wiedza jak np ugryźć wyliczanie liczby wszystkich wierszy, ale przy założeniu że są jeszcze dodatkowe warunki (np where active = 1).

    • sbl

      Czy swoje teze opierasz na wiedzy czy przypuszczeniach? Pętla for z widoku nie wykona się ani razu gdy wartość $all_pages będzie wynosiła 0.
      Po drugie, ktoś kto zrozumie jak to działa oraz ma wiedzę z poprzednich artykułów na pewno poradzi sobie z problemem gdy w bazie danych nie ma żadnych rekordów.

  • miku

    Kiedy pojawią się nowe wpisy w tej serii?

  • OFERTA PRACY:

    Witam,
    Poszukuję programisty PHP ze znajomością frameworku Kohana. Miejsce pracy: Warszawa. Zapraszam do kontaktu: 533 339 756.

    Pozdrawiam,
    Krystian Sikora

  • Łukasz

    ($current_page === 1 ? 0 : ($per_page * ($current_page-1))) można zapisać jako $per_page * ($current_page-1) wyjdzie na to samo 😀

Send this to a friend

webmastah.weekly
Cotygodniowa porcja linków ze świata WEBDEV BEZ spamu, TYLKO samo mięcho!
Zobacz poprzednie wydania. Dołącz do 2 tysięcy webdeveloperów!
HTML5, CSS3, JS (React, Angular, Ember, Vue), PHP, SQL