Paginação dos resultados do banco de dados
Ao desenvolver aplicações web, você frequentemente atende à exigência de imprimir um número restrito de registros em uma página.
Saímos do estado quando listamos todos os dados sem paginação. Para selecionar os dados do banco de dados, temos a classe
ArticleRepository, que contém o construtor e o método findPublishedArticles
, que retorna todos os artigos
publicados ordenados em ordem decrescente de 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 Apresentador injetamos então a classe do modelo e no método de renderização pediremos os artigos publicados que passamos para o modelo:
namespace App\UI\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();
}
}
O modelo default.latte
se encarregará de listar os artigos:
{block content}
<h1>Articles</h1>
<div class="articles">
{foreach $articles as $article}
<h2>{$article->title}</h2>
<p>{$article->content}</p>
{/foreach}
</div>
Desta forma, podemos escrever todos os artigos, mas isto causará problemas quando o número de artigos crescer. Nesse momento, será útil implementar o mecanismo de paginação.
Isto garantirá que todos os artigos sejam divididos em várias páginas e mostraremos apenas os artigos de uma página atual. O número total de páginas e a distribuição dos artigos é calculada pelo próprio paginador, dependendo de quantos artigos temos no total e quantos artigos queremos exibir na página.
No primeiro passo, modificaremos o método para obter artigos na classe de repositório para devolver apenas artigos de uma página. Também acrescentaremos um novo método para obter o número total de artigos no banco de dados, que precisaremos definir um Paginador:
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,
);
}
/**
* Returns the total number of published articles
*/
public function getPublishedArticlesCount(): int
{
return $this->database->fetchField('SELECT COUNT(*) FROM articles WHERE created_at < ?', new \DateTime);
}
}
O próximo passo é editar o apresentador. Nós encaminharemos o número da página atualmente exibida para o método de renderização. Caso este número não seja parte da URL, precisamos definir o valor padrão para a primeira página.
Também expandimos o método de renderização para obter a instância Paginator, configurando-a e selecionando os artigos corretos a serem exibidos no modelo. Home PagePresenter terá este aspecto:
namespace App\UI\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
{
// Vamos encontrar o número total de artigos publicados
$articlesCount = $this->articleRepository->getPublishedArticlesCount();
// Nós faremos a instância do Paginador e a criaremos
$paginator = new Nette\Utils\Paginator;
$paginator->setItemCount($articlesCount); // contagem total de artigos
$paginator->setItemsPerPage(10); // itens por página
$paginator->setPage($page); // número de página real
// Encontraremos um conjunto limitado de artigos do banco de dados com base nos cálculos do Paginator
$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());
// que passamos para o modelo
$this->template->articles = $articles;
// e também o próprio Paginador para exibir as opções de paginação
$this->template->paginator = $paginator;
}
}
O modelo já itera sobre artigos em uma página, basta adicionar links de paginação:
{block content}
<h1>Articles</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">First</a>
|
<a n:href="default, $paginator->page-1">Previous</a>
|
{/if}
Page {$paginator->getPage()} of {$paginator->getPageCount()}
{if !$paginator->isLast()}
|
<a n:href="default, $paginator->getPage() + 1">Next</a>
|
<a n:href="default, $paginator->getPageCount()">Last</a>
{/if}
</div>
Foi assim que adicionamos a paginação usando o Paginador. Se for usado o Nette Database Explorer em vez do Nette Database Core como camada de banco de dados, podemos implementar paginação
mesmo sem o Paginator. A classe Nette\Database\Table\Selection
contém o método de paginação com lógica de paginação
extraída do Paginator.
O repositório terá este aspecto:
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ão temos que criar o Paginador no Apresentador, em vez disso usaremos o método do objeto Selection
devolvido
pelo repositório:
namespace App\UI\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
{
// Vamos encontrar artigos publicados
$articles = $this->articleRepository->findPublishedArticles();
// e sua parte limitada pelo método de cálculo por página, passaremos ao modelo
$lastPage = 0;
$this->template->articles = $articles->page($page, 10, $lastPage);
// e os dados necessários para exibir as opções de paginação também
$this->template->page = $page;
$this->template->lastPage = $lastPage;
}
}
Como não usamos o Paginador, precisamos editar a seção que mostra os links de paginação:
{block content}
<h1>Articles</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">First</a>
|
<a n:href="default, $page - 1">Previous</a>
|
{/if}
Page {$page} of {$lastPage}
{if $page < $lastPage}
|
<a n:href="default, $page + 1">Next</a>
|
<a n:href="default, $lastPage">Last</a>
{/if}
</div>
Desta forma, implementamos um mecanismo de paginação sem utilizar um Paginador.