Nette Documentation Preview

syntax
Paginarea rezultatelor bazei de date
************************************

.[perex]
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.

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

În presenter injectăm apoi clasa model și în metoda render solicităm articolele publicate, pe care le transmitem șablonului:

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

În șablonul `default.latte` ne ocupăm apoi de afișarea articolelor:

```latte
{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 |utils: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`.

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

	/**
	 * 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:

```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
	{
		// 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:

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

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

	{if !$paginator->isLast()}
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() + 1">Următoarea</a>
		&nbsp;|&nbsp;
		<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 |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:

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

În presenter nu trebuie să creăm Paginator, folosim în locul său metoda clasei `Selection`, pe care ne-o returnează repository-ul:

```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
	{
		// 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`:

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

	Pagina {$page} din {$lastPage}

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

În acest mod am implementat mecanismul de paginare fără utilizarea Paginatorului.

{{priority: -1}}
{{sitename: Best Practices}}

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

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

	{if !$paginator->isLast()}
		&nbsp;|&nbsp;
		<a n:href="default, $paginator->getPage() + 1">Următoarea</a>
		&nbsp;|&nbsp;
		<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>
		&nbsp;|&nbsp;
		<a n:href="default, $page - 1">Anterioara</a>
		&nbsp;|&nbsp;
	{/if}

	Pagina {$page} din {$lastPage}

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

În acest mod am implementat mecanismul de paginare fără utilizarea Paginatorului.