Nette Documentation Preview

syntax
Paginare i risultati del database
*********************************

.[perex]
Sviluppando applicazioni web incontrerete spesso l'esigenza di limitare il numero di elementi elencati per pagina, tecnica nota come paginazione.

Partiamo dalla situazione in cui elenchiamo tutti i dati senza paginazione. Per selezionare i dati dal database abbiamo la classe `ArticleRepository`. Oltre al costruttore contiene il metodo `findPublishedArticles`, che restituisce tutti gli articoli pubblicati ordinati per data di pubblicazione decrescente.

```php
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 ci facciamo poi iniettare questa classe di modello. Nel metodo render otteniamo gli articoli pubblicati e li passiamo al template:

```php
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();
	}
}
```

Il template `default.latte` si occuperà poi di elencare gli articoli:

```latte
{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 elencare tutti gli articoli, ma la cosa diventa problematica quando il loro numero cresce. A quel punto torna utile realizzare un meccanismo di paginazione.

Questo meccanismo divide tutti gli articoli in più pagine e mostra solo gli articoli della pagina attualmente selezionata. Il numero complessivo di pagine e la ripartizione degli articoli vengono calcolati dallo strumento [Paginator |utils:Paginator] in base al numero totale di articoli e al numero desiderato di articoli per pagina.

Nel primo passo modifichiamo nella classe repository il metodo che ottiene gli articoli, in modo che possa restituire gli articoli di una sola pagina. Aggiungiamo anche un metodo per ottenere il numero totale di articoli nel database, necessario per configurare il Paginator:

```php
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);
	}
}
```

Passiamo poi a modificare il presenter. Al metodo `renderDefault` passeremo il numero della pagina corrente. Se questo numero non fa parte dell'URL, imposteremo il valore predefinito 1 (la prima pagina).

Estenderemo inoltre il metodo render per creare e configurare un'istanza del Paginator e selezionare gli articoli giusti da mostrare nel template. L'`HomePresenter` modificato apparirà così:

```php
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 e configuriamo l'istanza del Paginator
		$paginator = new Nette\Utils\Paginator;
		$paginator->setItemCount($articlesCount); // numero totale di elementi
		$paginator->setItemsPerPage(10); // elementi per pagina
		$paginator->setPage($page); // numero della pagina corrente

		// carichiamo dal database un insieme limitato di articoli secondo il calcolo del Paginator
		$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());

		// li passiamo al template
		$this->template->articles = $articles;
		// e con essi anche il Paginator stesso, per mostrare i controlli di paginazione
		$this->template->paginator = $paginator;
	}
}
```

Il template ora itera solo sugli articoli della pagina corrente. Basta aggiungere i link di paginazione:

```latte
{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">Prima</a>
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() - 1">Precedente</a>
		&nbsp;|&nbsp;
	{/if}

	Pagina {$paginator->getPage()} di {$paginator->getPageCount()}

	{if !$paginator->isLast()}
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() + 1">Successiva</a>
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPageCount()">Ultima</a>
	{/if}
</div>
```


Così abbiamo completato la paginazione con l'aiuto del Paginator. Se come livello database usate [Nette Database Explorer |database:explorer] invece di [Nette Database Core |database:sql-way], potete realizzare la paginazione anche senza usare direttamente lo strumento Paginator. La classe `Nette\Database\Table\Selection` contiene infatti il metodo [page() |api:Nette\Database\Table\Selection::page()], che racchiude la logica di paginazione.

Con questo approccio il repository apparirà così:

```php
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 l'istanza del Paginator. Al suo posto useremo il metodo `page()` dell'oggetto `Selection` restituito dal repository:

```php
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 gli articoli pubblicati
		$articles = $this->articleRepository->findPublishedArticles();

		// e al template passiamo solo la loro parte limitata dal calcolo del metodo page
		$lastPage = 0;
		$this->template->articles = $articles->page($page, 10, $lastPage);

		// e con essa anche i dati necessari a mostrare i controlli di paginazione
		$this->template->page = $page;
		$this->template->lastPage = $lastPage;
	}
}
```

Poiché al template non passiamo più l'oggetto Paginator, dobbiamo modificare la parte che mostra i link di paginazione:

```latte
{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">Prima</a>
		&nbsp;|&nbsp;
		<a n:href="default, $page - 1">Precedente</a>
		&nbsp;|&nbsp;
	{/if}

	Pagina {$page} di {$lastPage}

	{if $page < $lastPage}
		&nbsp;|&nbsp;
		<a n:href="default, $page + 1">Successiva</a>
		&nbsp;|&nbsp;
		<a n:href="default, $lastPage">Ultima</a>
	{/if}
</div>
```

Abbiamo così realizzato il meccanismo di paginazione senza usare esplicitamente lo strumento Paginator.

{{priority: -1}}

Paginare i risultati del database

Sviluppando applicazioni web incontrerete spesso l'esigenza di limitare il numero di elementi elencati per pagina, tecnica nota come paginazione.

Partiamo dalla situazione in cui elenchiamo tutti i dati senza paginazione. Per selezionare i dati dal database abbiamo la classe ArticleRepository. Oltre al costruttore contiene il metodo findPublishedArticles, che restituisce tutti gli articoli pubblicati ordinati per data di pubblicazione decrescente.

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 ci facciamo poi iniettare questa classe di modello. Nel metodo render otteniamo gli articoli pubblicati e li 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();
	}
}

Il template default.latte si occuperà poi di elencare gli 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 elencare tutti gli articoli, ma la cosa diventa problematica quando il loro numero cresce. A quel punto torna utile realizzare un meccanismo di paginazione.

Questo meccanismo divide tutti gli articoli in più pagine e mostra solo gli articoli della pagina attualmente selezionata. Il numero complessivo di pagine e la ripartizione degli articoli vengono calcolati dallo strumento Paginator in base al numero totale di articoli e al numero desiderato di articoli per pagina.

Nel primo passo modifichiamo nella classe repository il metodo che ottiene gli articoli, in modo che possa restituire gli articoli di una sola pagina. Aggiungiamo anche un metodo per ottenere il numero totale di articoli nel database, necessario per configurare 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);
	}
}

Passiamo poi a modificare il presenter. Al metodo renderDefault passeremo il numero della pagina corrente. Se questo numero non fa parte dell'URL, imposteremo il valore predefinito 1 (la prima pagina).

Estenderemo inoltre il metodo render per creare e configurare un'istanza del Paginator e selezionare gli articoli giusti da mostrare nel template. L'HomePresenter modificato 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 e configuriamo l'istanza del Paginator
		$paginator = new Nette\Utils\Paginator;
		$paginator->setItemCount($articlesCount); // numero totale di elementi
		$paginator->setItemsPerPage(10); // elementi per pagina
		$paginator->setPage($page); // numero della pagina corrente

		// carichiamo dal database un insieme limitato di articoli secondo il calcolo del Paginator
		$articles = $this->articleRepository->findPublishedArticles($paginator->getLength(), $paginator->getOffset());

		// li passiamo al template
		$this->template->articles = $articles;
		// e con essi anche il Paginator stesso, per mostrare i controlli di paginazione
		$this->template->paginator = $paginator;
	}
}

Il template ora itera solo sugli articoli della pagina corrente. 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">Prima</a>
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() - 1">Precedente</a>
		&nbsp;|&nbsp;
	{/if}

	Pagina {$paginator->getPage()} di {$paginator->getPageCount()}

	{if !$paginator->isLast()}
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() + 1">Successiva</a>
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPageCount()">Ultima</a>
	{/if}
</div>

Così abbiamo completato la paginazione con l'aiuto del Paginator. Se come livello database usate Nette Database Explorer invece di Nette Database Core, potete realizzare la paginazione anche senza usare direttamente lo strumento Paginator. La classe Nette\Database\Table\Selection contiene infatti il metodo page(), che racchiude la logica di paginazione.

Con questo approccio il repository 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 l'istanza del Paginator. Al suo posto useremo il metodo page() dell'oggetto Selection restituito dal 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
	{
		// otteniamo gli articoli pubblicati
		$articles = $this->articleRepository->findPublishedArticles();

		// e al template passiamo solo la loro parte limitata dal calcolo del metodo page
		$lastPage = 0;
		$this->template->articles = $articles->page($page, 10, $lastPage);

		// e con essa anche i dati necessari a mostrare i controlli di paginazione
		$this->template->page = $page;
		$this->template->lastPage = $lastPage;
	}
}

Poiché al template non passiamo più l'oggetto Paginator, dobbiamo modificare la parte che mostra 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">Prima</a>
		&nbsp;|&nbsp;
		<a n:href="default, $page - 1">Precedente</a>
		&nbsp;|&nbsp;
	{/if}

	Pagina {$page} di {$lastPage}

	{if $page < $lastPage}
		&nbsp;|&nbsp;
		<a n:href="default, $page + 1">Successiva</a>
		&nbsp;|&nbsp;
		<a n:href="default, $lastPage">Ultima</a>
	{/if}
</div>

Abbiamo così realizzato il meccanismo di paginazione senza usare esplicitamente lo strumento Paginator.