Paginarea rezultatelor bazei de date
La crearea aplicațiilor web, vă veți întâlni foarte des cu cerința de a limita numărul de elemente afișate pe pagină.
Pornim de la starea în care afișăm toate datele fără paginare. Pentru selectarea datelor din baza de date avem clasa
ArticleRepository
, care, pe lângă constructor, conține metoda findPublishedArticles
, ce returnează
toate articolele publicate sortate descrescător după data publicării.
namespace App\Model;
use Nette;
class ArticleRepository
{
public function __construct(
private Nette\Database\Connection $database,
) {
}
public function findPublishedArticles(): Nette\Database\ResultSet
{
return $this->database->query('
SELECT * FROM articles
WHERE created_at < ?
ORDER BY created_at DESC',
new \DateTime,
);
}
}
În presenter injectăm apoi clasa model și în metoda render solicităm articolele publicate, pe care le transmitem șablonului:
namespace App\Presentation\Home;
use Nette;
use App\Model\ArticleRepository;
class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articleRepository,
) {
}
public function renderDefault(): void
{
$this->template->articles = $this->articleRepository->findPublishedArticles();
}
}
În șablonul default.latte
ne ocupăm apoi de afișarea articolelor:
{block content}
<h1>Articole</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
În acest mod putem afișa toate articolele, ceea ce însă începe să cauzeze probleme în momentul în care numărul articolelor crește. În acel moment devine utilă implementarea unui mecanism de paginare.
Acesta asigură că toate articolele sunt împărțite în mai multe pagini și noi afișăm doar articolele unei pagini curente. Numărul total de pagini și împărțirea articolelor sunt calculate de Paginator singur, în funcție de câte articole avem în total și câte articole dorim să afișăm pe pagină.
În primul pas, vom folosi obiectul Paginator
în presenter pentru a calcula limita și offset-ul necesare pentru
interogarea bazei de date. Clasa ArticleRepository
nu necesită modificări dacă folosim
Nette\Database\Explorer
, deoarece putem aplica paginarea direct pe obiectul Selection
.
namespace App\Model;
use Nette;
class ArticleRepository
{
public function __construct(
private Nette\Database\Connection $database,
) {
}
public function findPublishedArticles(int $limit, int $offset): Nette\Database\ResultSet
{
return $this->database->query('
SELECT * FROM articles
WHERE created_at < ?
ORDER BY created_at DESC
LIMIT ?
OFFSET ?',
new \DateTime, $limit, $offset,
);
}
/**
* Returnează numărul total de articole publicate
*/
public function getPublishedArticlesCount(): int
{
return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
}
}
Ulterior, ne apucăm de modificările presenterului. În metoda render vom transmite numărul paginii afișate curent. Pentru cazul în care acest număr nu va face parte din URL, setăm valoarea implicită a primei pagini.
Extindem, de asemenea, metoda render cu obținerea instanței Paginatorului, setarea sa și selectarea articolelor corecte pentru afișare în șablon. HomePresenter va arăta astfel după modificări:
namespace App\Presentation\Home;
use Nette;
use App\Model\ArticleRepository;
class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articleRepository,
) {
}
public function renderDefault(int $page = 1): void
{
// Aflăm numărul total de articole publicate
$articlesCount = $this->articleRepository->getPublishedArticlesCount();
// Creăm o instanță a Paginatorului și o setăm
$paginator = new Nette\Utils\Paginator;
$paginator->setItemCount($articlesCount); // numărul total de articole
$paginator->setItemsPerPage(10); // numărul de elemente pe pagină
$paginator->setPage($page); // numărul paginii curente
// Extragem din baza de date un set limitat de articole conform calculului Paginatorului
$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());
// pe care îl transmitem șablonului
$this->template->articles = $articles;
// și, de asemenea, Paginatorul însuși pentru afișarea opțiunilor de paginare
$this->template->paginator = $paginator;
}
}
Șablonul nostru iterează acum doar peste articolele unei singure pagini, este suficient să adăugăm linkurile de paginare:
{block content}
<h1>Articole</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
<div class="pagination">
{if !$paginator->isFirst()}
<a n:href="default, 1">Prima</a>
|
<a n:href="default, $paginator->page-1">Anterioara</a>
|
{/if}
Pagina {$paginator->getPage()} din {$paginator->getPageCount()}
{if !$paginator->isLast()}
|
<a n:href="default, $paginator->getPage() + 1">Următoarea</a>
|
<a n:href="default, $paginator->getPageCount()">Ultima</a>
{/if}
</div>
Astfel am completat pagina cu posibilitatea de paginare folosind Paginator
. În cazul în care folosim Nette Database Explorer, suntem capabili să implementăm paginarea și
fără a utiliza explicit obiectul Paginator
în presenter, deoarece clasa
Nette\Database\Table\Selection
conține metoda page()
care încapsulează logica paginatorului.
Repository-ul rămâne același ca în exemplul cu Explorer:
namespace App\Model;
use Nette;
class ArticleRepository
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function findPublishedArticles(): Nette\Database\Table\Selection
{
return $this->database->table('articles')
->where('created_at < ', new \DateTime)
->order('created_at DESC');
}
}
În presenter nu trebuie să creăm Paginator, folosim în locul său metoda clasei Selection
, pe care
ne-o returnează repository-ul:
namespace App\Presentation\Home;
use Nette;
use App\Model\ArticleRepository;
class HomePresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ArticleRepository $articleRepository,
) {
}
public function renderDefault(int $page = 1): void
{
// Extragem articolele publicate
$articles = $this->articleRepository->findPublishedArticles();
// și trimitem către șablon doar o parte din ele, limitată conform calculului metodei page
$lastPage = 0;
$this->template->articles = $articles->page($page, 10, $lastPage);
// și, de asemenea, datele necesare pentru afișarea opțiunilor de paginare
$this->template->page = $page;
$this->template->lastPage = $lastPage;
}
}
Deoarece acum nu trimitem Paginator
către șablon, modificăm partea care afișează linkurile de paginare pentru
a folosi variabilele $page
și $lastPage
:
{block content}
<h1>Articole</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
<div class="pagination">
{if $page > 1}
<a n:href="default, 1">Prima</a>
|
<a n:href="default, $page - 1">Anterioara</a>
|
{/if}
Pagina {$page} din {$lastPage}
{if $page < $lastPage}
|
<a n:href="default, $page + 1">Următoarea</a>
|
<a n:href="default, $lastPage">Ultima</a>
{/if}
</div>
În acest mod am implementat mecanismul de paginare fără utilizarea Paginatorului.