Biblioteka do paginacji – MVC z Composer
Czytelnikom bloga chyba nie muszę tłumaczyć co to jest i do czego służy paginacja. W tym wpisie udostępnię bibliotekę dostosowaną do szkieletu aplikacji pokazanego w cyklu MVC w praktyce z composer – tworzymy system artykułów.
Żeby zrozumieć treść tego artykułu konieczne jest zapoznanie się powyższym cyklem.
Kod biblioteki jest zmodyfikowaną klasą paginacji z aplikacji Open Cart (na licencji opensource).
Myślę, że nie ma sensu wchodzić tutaj w teoretyzowanie, a więc przejdźmy od razu do kodu.
Skrypt paginacji
W moim szkielecie aplikacji należy utworzyć plik src/Library/Pagination.php:
<?php namespace RacyMind\MVCWPraktyce\Library; /** * Paginacja * @package RacyMind\MVCWPraktyce\Library * @author Łukasz Socha <kontakt@lukasz-socha.pl> * @version 1.0 * @license http://www.gnu.org/copyleft/lesser.html */ final class Pagination { /** * @var int Ilość wszystkich elementów */ private $totalItems = 0; /** * @var int Aktualna strona */ private $page = 1; /** * @var int Ilość elementów na stronie */ private $itemsPerPage = 10; /** * @var int Ilość wyświetlanych lunków do stron */ private $numLinks = 3; /** * @var string Adres URL */ private $url = ''; /** * @var string Tekst podsumowania */ private $text = 'Pokazano od {start} do {end} zw {total} elementów ({pages} stron)'; /** * @var string Tekst linka do pierwszej strony */ private $textFirstPage = 'pierwsza'; /** * @var string Tekst linka do ostaniej strony */ private $textLastPage = 'ostania'; /** * @var string Tekst linka do następnej strony */ private $textNextPage = '>'; /** * @var string Tekst linka do poprzedniej strony */ private $textPrevPage = '<'; /** * @var string Separator z lewej strony */ private $dottedStringLeft = ' .... '; /** * @var string Separator z prawej strony */ private $dottedStringRight = ' .... '; /** * @var int Ilość stron */ private $numPages; /** * @var string do pierwszej strony */ private $titleFirstPage = ''; /** * @var string Tytuł linka do poprzedniej strony */ private $titlePrevPage = ''; /** * @var string Tytuł linka do x strony */ private $titleXPage = ''; /** * @var string Tytuł linka do następnej strony */ private $titleNextPage = ''; /** * @var string Tytuł linka do ostatniej strony */ private $titleLastPage = ''; /** * Pagination constructor. * @param string $url */ public function __construct($url) { $this->url = $url; } /** * @return int */ public function getTotalItems() { return $this->totalItems; } /** * @param int $totalItems */ public function setTotalItems($totalItems) { $this->totalItems = $totalItems; } /** * @return int */ public function getPage() { return $this->page; } /** * @param int $page */ public function setPage($page) { $this->page = $page; } /** * @return int */ public function getItemsPerPage() { return $this->itemsPerPage; } /** * @param int $itemsPerPage */ public function setItemsPerPage($itemsPerPage) { $this->itemsPerPage = $itemsPerPage; } /** * @return int */ public function getNumLinks() { return $this->numLinks; } /** * @param int $numLinks */ public function setNumLinks($numLinks) { $this->numLinks = $numLinks; } /** * @return string */ public function getUrl() { return $this->url; } /** * @param string $url */ public function setUrl($url) { $this->url = $url; } /** * @return string */ public function getText() { return $this->text; } /** * @param string $text */ public function setText($text) { $this->text = $text; } /** * @return string */ public function getTextFirstPage() { return $this->textFirstPage; } /** * @param string $textFirstPage */ public function setTextFirstPage($textFirstPage) { $this->textFirstPage = $textFirstPage; } /** * @return string */ public function getTextLastPage() { return $this->textLastPage; } /** * @param string $textLastPage */ public function setTextLastPage($textLastPage) { $this->textLastPage = $textLastPage; } /** * @return string */ public function getTextNextPage() { return $this->textNextPage; } /** * @param string $textNextPage */ public function setTextNextPage($textNextPage) { $this->textNextPage = $textNextPage; } /** * @return string */ public function getTextPrevPage() { return $this->textPrevPage; } /** * @param string $textPrevPage */ public function setTextPrevPage($textPrevPage) { $this->textPrevPage = $textPrevPage; } /** * @return string */ public function getDottedStringLeft() { return $this->dottedStringLeft; } /** * @param string $dottedStringLeft */ public function setDottedStringLeft($dottedStringLeft) { $this->dottedStringLeft = $dottedStringLeft; } /** * @return string */ public function getDottedStringRight() { return $this->dottedStringRight; } /** * @param string $dottedStringRight */ public function setDottedStringRight($dottedStringRight) { $this->dottedStringRight = $dottedStringRight; } /** * @return int */ public function getNumPages() { return $this->numPages; } /** * @param int $numPages */ public function setNumPages($numPages) { $this->numPages = $numPages; } /** * @return string */ public function getTitleFirstPage() { return $this->titleFirstPage; } /** * @param string $titleFirstPage */ public function setTitleFirstPage($titleFirstPage) { $this->titleFirstPage = $titleFirstPage; } /** * @return string */ public function getTitlePrevPage() { return $this->titlePrevPage; } /** * @param string $titlePrevPage */ public function setTitlePrevPage($titlePrevPage) { $this->titlePrevPage = $titlePrevPage; } /** * @return string */ public function getTitleXPage() { return $this->titleXPage; } /** * @param string $titleXPage */ public function setTitleXPage($titleXPage) { $this->titleXPage = $titleXPage; } /** * @return string */ public function getTitleNextPage() { return $this->titleNextPage; } /** * @param string $titleNextPage */ public function setTitleNextPage($titleNextPage) { $this->titleNextPage = $titleNextPage; } /** * @return string */ public function getTitleLastPage() { return $this->titleLastPage; } /** * @param string $titleLastPage */ public function setTitleLastPage($titleLastPage) { $this->titleLastPage = $titleLastPage; } public function render() { $numPages = ceil($this->totalItems / $this->itemsPerPage); $output = ''; if ($this->page > 1) { if ($this->textFirstPage) { $firstUrl = str_replace('{page}', 1, $this->url); $firstUrl = str_replace(array('_strona-1'), array(''), $firstUrl); $output .= '<a title="' . $this->titleFirstPage . '" href="' . $firstUrl . '">' . $this->textFirstPage . '</a>'; } if ($this->textPrevPage) { $prevUrl = str_replace('{page}', $this->page - 1, $this->url); if ($this->page - 1 == 1) $prevUrl = str_replace(array('_strona-1'), array(''), $prevUrl); $output .= '<a title="' . $this->titlePrevPage . '" href="' . $prevUrl . '">' . $this->textPrevPage . '</a> '; } } if ($numPages > 1) { if ($numPages <= $this->numLinks) { $start = 1; $end = $numPages; } else { $start = $this->page - floor($this->numLinks / 2); $end = $this->page + floor($this->numLinks / 2); if ($start < 1) { $end += abs($start) + 1; $start = 1; } if ($end > $numPages) { $start -= ($end - $numPages); $end = $numPages; } } if ($start > 1) { $output .= $this->dottedStringLeft; } for ($i = $start; $i <= $end; $i++) { if ($this->page == $i) { $output .= ' <span>' . $i . '</span> '; } else { $output .= ' <a title="' . str_replace('{page}', $i, $this->titleXPage) . '" href="' . str_replace('{page}', $i, $this->url) . '">' . $i . '</a> '; } if ($i == 1) { $output = str_replace(array('_strona-1'), array(''), $output); } } if ($end < $numPages) { if (strpos($this->dottedStringRight, '{all_pages}') !== false) { $output .= str_replace('{all_pages}', '<a title="' . str_replace('{page}', $numPages, $this->titleXPage) . '" href="' . str_replace('{page}', $numPages, $this->url) . '">' . $numPages . '</a>', $this->dottedStringRight); } else { $output .= str_replace('{pages}', $numPages, $this->dottedStringRight); } } } if ($this->page < $numPages) { if ($this->textNextPage) $output .= '<a title="' . $this->titleNextPage . '" href="' . str_replace('{page}', $this->page + 1, $this->url) . '">' . $this->textNextPage . '</a> '; if ($this->textLastPage) $output .= '<a title="' . $this->titleLastPage . '" href="' . str_replace('{page}', $numPages, $this->url) . '">' . $this->textLastPage . '</a> '; } $find = array( '{start}', '{end}', '{total}', '{pages}' ); $replace = array( ($this->totalItems) ? (($this->page - 1) * $this->itemsPerPage) + 1 : 0, ((($this->page - 1) * $this->itemsPerPage) > ($this->totalItems - $this->itemsPerPage)) ? $this->totalItems : ((($this->page - 1) * $this->itemsPerPage) + $this->itemsPerPage), $this->totalItems, $numPages ); $this->numPages = $numPages; return ($output ? '<div class="pagination-links">' . $output . '</div>' : '') . '<div class="pagination-result">' . str_replace($find, $replace, $this->text) . '</div>'; } } ?>
Wszystkie możliwości konfiguracji dostępne są za pomocą pól zainicjowanych na początku klasy. W komentarzach opisałem za co odpowiada dane pole. Metodą odpowiedzialną za wygenerowanie paginacji jest render().
Żeby korzystać z paginacji należy zmodyfikować kilka plików systemu artykułów.
Aktualizacja modelu artykułów
W pliku src/Model/Article.php należy zmodyfikować metodę getAll().
public function getAll($offset=null, $limit=null) { $sql = "SELECT a.id, a.title, a.date_add, a.author, c.name FROM articles AS a LEFT JOIN categories AS c ON a.id_categories=c.id "; if (isset($offset) && isset($limit)) { $sql .= "LIMIT " . (int)$offset . ", " . (int)$limit; } $query = $this->pdo->query($sql); $items = $query->fetchAll(\PDO::FETCH_ASSOC); if (isset($items)) { return $items; } else { return null; } }
Aktualizacja kontrolera artykułów
W pliku src/Controller/Article.php należy zmodyfikować metodę index().
public function index() { if(!empty($_GET['page'])) { $page=(int)$_GET['page']; } else { $page=1; } $model = new \RacyMind\MVCWPraktyce\Model\Article(); $total=sizeof($model->getAll()); $pagination = new \RacyMind\MVCWPraktyce\Library\Pagination($this->generateUrl('article/index/pages', array('page' => '{page}'))); $pagination->setTotalItems($total); $pagination->setPage($page); $pagination->setItemsPerPage(10); $articles = $model->getAll(($page-1)*$pagination->getItemsPerPage(), $pagination->getItemsPerPage()); $view = new \RacyMind\MVCWPraktyce\View\Article(); $view->pagination=$pagination->render(); $view->articles = $articles; $view->renderHTML('index', 'front/article/'); }
Aktualizacja szablonu z listą artykułów
W pliku src/template/front/article/index.html.php należy dopisać w dowolnym miejscu:
<?php echo $this->pagination; ?>
Dodanie reguły routera
Na koniec, w pliku config-router.php trzeba dodać nową regułę:
$collection->add('article/index/pages', new \RacyMind\MVCWPraktyce\Engine\Router\Route( HTTP_SERVER.'artykuly_strona-<page>?', array( 'file' => DIR_CONTROLLER.'Article.php', 'method' => 'index', 'class' => '\RacyMind\MVCWPraktyce\Controller\Article' ), array( 'page' => '\d+' ), array( 'page' => 1 ) ));
Myślę, że dodawanie paginacji w aplikacjach korzystających z mojego szkieletu nie powinno sprawiać większego problemu :).