Paginazione dei risultati del database
Durante la creazione di applicazioni web, incontrerete molto spesso la necessità di limitare il numero di elementi visualizzati per pagina.
Partiamo dallo stato in cui visualizziamo tutti i dati senza paginazione. Per selezionare i dati dal database abbiamo la
classe ArticleRepository, che oltre al costruttore contiene il metodo findPublishedArticles
, che restituisce tutti
gli articoli pubblicati ordinati in modo decrescente per data di pubblicazione.
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,
);
}
}
Nel presenter, quindi, iniettiamo la classe del modello e nel metodo render richiediamo gli articoli pubblicati, che passiamo al template:
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();
}
}
Nel template default.latte
ci occupiamo quindi della visualizzazione degli articoli:
{block content}
<h1>Articoli</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
In questo modo sappiamo visualizzare tutti gli articoli, il che però inizia a creare problemi nel momento in cui il numero di articoli aumenta. In quel momento diventa utile implementare un meccanismo di paginazione.
Questo garantirà che tutti gli articoli vengano divisi in più pagine e noi visualizzeremo solo gli articoli di una pagina corrente. Il numero totale di pagine e la divisione degli articoli verranno calcolati da Paginator stesso in base a quanti articoli abbiamo in totale e quanti articoli per pagina vogliamo visualizzare.
Nel primo passo, modifichiamo il metodo per ottenere gli articoli nella classe del repository in modo che possa restituirci solo gli articoli per una pagina. Aggiungiamo anche un metodo per determinare il numero totale di articoli nel database, che ci servirà per impostare il Paginator:
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,
);
}
/**
* Restituisce il numero totale di articoli pubblicati
*/
public function getPublishedArticlesCount(): int
{
return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
}
}
Successivamente, ci dedichiamo alle modifiche del presenter. Al metodo render passeremo il numero della pagina attualmente visualizzata. Nel caso in cui questo numero non sia parte dell'URL, imposteremo il valore predefinito della prima pagina.
Inoltre, estenderemo il metodo render con l'ottenimento dell'istanza di Paginator, la sua impostazione e la selezione degli articoli corretti per la visualizzazione nel template. HomePresenter dopo le modifiche apparirà così:
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
{
// Otteniamo il numero totale di articoli pubblicati
$articlesCount = $this->articleRepository->getPublishedArticlesCount();
// Creiamo un'istanza di Paginator e la impostiamo
$paginator = new Nette\Utils\Paginator;
$paginator->setItemCount($articlesCount); // numero totale di articoli
$paginator->setItemsPerPage(10); // numero di elementi per pagina
$paginator->setPage($page); // numero della pagina corrente
// Estraiamo dal database un sottoinsieme limitato di articoli secondo il calcolo del Paginator
$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());
// che passiamo al template
$this->template->articles = $articles;
// e anche il Paginator stesso per visualizzare le opzioni di paginazione
$this->template->paginator = $paginator;
}
}
Il template ora itera solo sugli articoli di una pagina, ci basta aggiungere i link di paginazione:
{block content}
<h1>Articoli</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">Primo</a>
|
<a n:href="default, $paginator->page-1">Precedente</a>
|
{/if}
Pagina {$paginator->getPage()} di {$paginator->getPageCount()}
{if !$paginator->isLast()}
|
<a n:href="default, $paginator->getPage() + 1">Successivo</a>
|
<a n:href="default, $paginator->getPageCount()">Ultimo</a>
{/if}
</div>
In questo modo abbiamo aggiunto alla pagina la possibilità di paginazione tramite Paginator. Nel caso in cui, invece di Nette Database Core come livello di database utilizziamo Nette Database Explorer, siamo in grado di implementare la paginazione anche
senza l'uso di Paginator. La classe Nette\Database\Table\Selection
infatti contiene il metodo page con la logica di paginazione ereditata
da Paginator.
Il repository con questo metodo di implementazione apparirà così:
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');
}
}
Nel presenter non dobbiamo creare Paginator, useremo al suo posto il metodo della classe Selection
, che ci
restituisce il repository:
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
{
// Estraiamo gli articoli pubblicati
$articles = $this->articleRepository->findPublishedArticles();
// e inviamo al template solo una loro parte limitata secondo il calcolo del metodo page
$lastPage = 0;
$this->template->articles = $articles->page($page, 10, $lastPage);
// e anche i dati necessari per visualizzare le opzioni di paginazione
$this->template->page = $page;
$this->template->lastPage = $lastPage;
}
}
Poiché ora non inviamo Paginator al template, modifichiamo la parte che visualizza i link di paginazione:
{block content}
<h1>Articoli</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">Primo</a>
|
<a n:href="default, $page - 1">Precedente</a>
|
{/if}
Pagina {$page} di {$lastPage}
{if $page < $lastPage}
|
<a n:href="default, $page + 1">Successivo</a>
|
<a n:href="default, $lastPage">Ultimo</a>
{/if}
</div>
In questo modo abbiamo implementato il meccanismo di paginazione senza l'uso di Paginator.