Paginação de resultados do banco de dados
Ao criar aplicações web, você frequentemente encontrará a exigência de limitar o número de itens exibidos por página.
Partiremos do estado em que exibimos todos os dados sem paginação. Para selecionar dados do banco de dados, temos a classe
ArticleRepository, que, além do construtor, contém o método findPublishedArticles
, que retorna todos os artigos
publicados ordenados decrescentemente pela data de publicação.
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,
);
}
}
No presenter, injetamos a classe do modelo e no método render solicitamos os artigos publicados, que passamos para o 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();
}
}
No template default.latte
, cuidamos da exibição dos artigos:
{block content}
<h1>Artigos</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
Desta forma, podemos exibir todos os artigos, o que, no entanto, começará a causar problemas quando o número de artigos aumentar. Nesse momento, a implementação de um mecanismo de paginação se torna útil.
Ele garantirá que todos os artigos sejam divididos em várias páginas e exibiremos apenas os artigos da página atual. O número total de páginas e a divisão dos artigos serão calculados pelo Paginator com base em quantos artigos temos no total e quantos artigos por página queremos exibir.
No primeiro passo, modificamos o método para obter artigos na classe do repositório para que ele possa retornar apenas artigos para uma página. Também adicionamos um método para descobrir o número total de artigos no banco de dados, que precisaremos para configurar o 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,
);
}
/**
* Retorna o número total de artigos publicados
*/
public function getPublishedArticlesCount(): int
{
return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
}
}
Em seguida, começamos a modificar o presenter. Passaremos o número da página atualmente exibida para o método render. Caso este número não faça parte da URL, definiremos o valor padrão da primeira página.
Também estenderemos o método render para obter a instância do Paginator, configurá-lo e selecionar os artigos corretos para exibição no template. O HomePresenter ficará assim após as modificações:
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
{
// Descobrimos o número total de artigos publicados
$articlesCount = $this->articleRepository->getPublishedArticlesCount();
// Criamos uma instância do Paginator e a configuramos
$paginator = new Nette\Utils\Paginator;
$paginator->setItemCount($articlesCount); // número total de artigos
$paginator->setItemsPerPage(10); // número de itens por página
$paginator->setPage($page); // número da página atual
// Do banco de dados, extraímos um conjunto limitado de artigos de acordo com o cálculo do Paginator
$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());
// que passamos para o template
$this->template->articles = $articles;
// e também o próprio Paginator para exibir as opções de paginação
$this->template->paginator = $paginator;
}
}
O template agora itera apenas sobre os artigos de uma página, basta adicionar os links de paginação:
{block content}
<h1>Artigos</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">Primeira</a>
|
<a n:href="default, $paginator->page-1">Anterior</a>
|
{/if}
Página {$paginator->getPage()} de {$paginator->getPageCount()}
{if !$paginator->isLast()}
|
<a n:href="default, $paginator->getPage() + 1">Próxima</a>
|
<a n:href="default, $paginator->getPageCount()">Última</a>
{/if}
</div>
Assim, adicionamos a opção de paginação à página usando o Paginator. Caso, em vez do Nette Database Core como camada de banco de dados, usemos o Nette Database Explorer, somos capazes de implementar a paginação mesmo
sem usar o Paginator. A classe Nette\Database\Table\Selection
contém o método page com a lógica de paginação herdada do
Paginator.
O repositório ficará assim com este método de implementação:
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');
}
}
No presenter, não precisamos criar o Paginator, usaremos em seu lugar o método da classe Selection
, que
o repositório nos retorna:
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
{
// Extraímos os artigos publicados
$articles = $this->articleRepository->findPublishedArticles();
// e para o template enviamos apenas sua parte limitada de acordo com o cálculo do método page
$lastPage = 0;
$this->template->articles = $articles->page($page, 10, $lastPage);
// e também os dados necessários para exibir as opções de paginação
$this->template->page = $page;
$this->template->lastPage = $lastPage;
}
}
Como agora não enviamos o Paginator para o template, modificamos a parte que exibe os links de paginação:
{block content}
<h1>Artigos</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">Primeira</a>
|
<a n:href="default, $page - 1">Anterior</a>
|
{/if}
Página {$page} de {$lastPage}
{if $page < $lastPage}
|
<a n:href="default, $page + 1">Próxima</a>
|
<a n:href="default, $lastPage">Última</a>
{/if}
</div>
Desta forma, implementamos o mecanismo de paginação sem usar o Paginator.