Șabloane

Nette utilizează sistemul de șabloane Latte. Pe de o parte, pentru că este cel mai sigur sistem de șabloane pentru PHP și, pe de altă parte, și cel mai intuitiv sistem. Nu trebuie să învățați multe lucruri noi, vă descurcați cu cunoștințele de PHP și câteva tag-uri.

Este obișnuit ca pagina să fie compusă dintr-un șablon de layout + șablonul acțiunii respective. Așa poate arăta, de exemplu, un șablon de layout, observați blocurile {block} și tag-ul {include}:

<!DOCTYPE html>
<html>
<head>
	<title>{block title}My App{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

Și acesta va fi șablonul acțiunii:

{block title}Homepage{/block}

{block content}
<h1>Homepage</h1>
...
{/block}

Acesta definește blocul content, care se va insera în locul {include content} din layout, și, de asemenea, re-definește blocul title, care va suprascrie {block title} din layout. Încercați să vă imaginați rezultatul.

Căutarea șabloanelor

Nu trebuie să specificați în presentere ce șablon trebuie redat, framework-ul deduce singur calea și vă scutește de scris.

Dacă utilizați o structură de directoare unde fiecare presenter are propriul director, plasați pur și simplu șablonul în acest director sub numele acțiunii (resp. view), adică pentru acțiunea default utilizați șablonul default.latte:

app/
└── Presentation/
    └── Home/
        ├── HomePresenter.php
        └── default.latte

Dacă utilizați o structură unde presenterele sunt împreună într-un singur director și șabloanele în folderul templates, salvați-l fie în fișierul <Presenter>.<view>.latte, fie <Presenter>/<view>.latte:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home.default.latte  ← prima variantă
        └── Home/
            └── default.latte   ← a doua variantă

Directorul templates poate fi plasat și cu un nivel mai sus, adică la același nivel cu directorul cu clasele presenterelor.

Dacă șablonul nu este găsit, presenterul răspunde cu eroarea 404 – page not found.

View-ul îl schimbați folosind $this->setView('jineView'). De asemenea, se poate specifica direct fișierul cu șablonul folosind $this->template->setFile('/path/to/template.latte').

Fișierele unde se caută șabloanele pot fi modificate prin suprascrierea metodei formatTemplateFiles(), care returnează un array de nume posibile de fișiere.

Căutarea șablonului de layout

Nette caută automat și fișierul cu layout-ul.

Dacă utilizați o structură de directoare unde fiecare presenter are propriul director, plasați layout-ul fie în folderul cu presenterul, dacă este specific doar pentru el, fie cu un nivel mai sus, dacă este comun pentru mai mulți presenteri:

app/
└── Presentation/
    ├── @layout.latte           ← layout comun
    └── Home/
        ├── @layout.latte       ← doar pentru presenterul Home
        ├── HomePresenter.php
        └── default.latte

Dacă utilizați o structură unde presenterele sunt împreună într-un singur director și șabloanele în folderul templates, layout-ul va fi așteptat în aceste locații:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── @layout.latte       ← layout comun
        ├── Home.@layout.latte  ← doar pentru Home, prima variantă
        └── Home/
            └── @layout.latte   ← doar pentru Home, a doua variantă

Dacă presenterul se află într-un modul, se va căuta și la niveluri de directoare superioare, în funcție de imbricarea modulului.

Numele layout-ului poate fi schimbat folosind $this->setLayout('layoutAdmin') și atunci se va aștepta în fișierul @layoutAdmin.latte. De asemenea, se poate specifica direct fișierul cu șablonul layout-ului folosind $this->setLayout('/path/to/template.latte').

Folosind $this->setLayout(false) sau tag-ul {layout none} în interiorul șablonului, căutarea layout-ului se dezactivează.

Fișierele unde se caută șabloanele de layout pot fi modificate prin suprascrierea metodei formatLayoutTemplateFiles(), care returnează un array de nume posibile de fișiere.

Variabile în șablon

Variabilele le transmitem șablonului scriindu-le în $this->template și apoi le avem disponibile în șablon ca variabile locale:

$this->template->article = $this->articles->getById($id);

Astfel de simplu putem transmite șabloanelor orice variabile. Însă, la dezvoltarea aplicațiilor robuste, este mai util să ne limităm. De exemplu, prin definirea explicită a listei de variabile pe care șablonul le așteaptă și a tipurilor lor. Datorită acestui fapt, PHP ne va putea verifica tipurile, IDE-ul ne va sugera corect și analiza statică va dezvălui erorile.

Și cum definim o astfel de listă? Simplu, sub forma unei clase și a proprietăților sale. O numim similar cu presenterul, doar cu Template la sfârșit:

/**
 * @property-read ArticleTemplate $template
 */
class ArticlePresenter extends Nette\Application\UI\Presenter
{
}

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	public Model\Article $article;
	public Nette\Security\User $user;

	// și alte variabile
}

Obiectul $this->template din presenter va fi acum o instanță a clasei ArticleTemplate. Deci, PHP va verifica tipurile declarate la scriere. Și începând cu versiunea PHP 8.2 va avertiza și la scrierea într-o variabilă inexistentă, în versiunile anterioare se poate obține același lucru folosind trait-ul Nette\SmartObject.

Adnotarea @property-read este destinată IDE-ului și analizei statice, datorită ei va funcționa sugerarea, vezi PhpStorm and code completion for $this⁠-⁠>⁠template.

De luxul sugerării vă puteți bucura și în șabloane, este suficient să instalați în PhpStorm plugin-ul pentru Latte și să specificați la începutul șablonului numele clasei, mai multe în articolul Latte: cum să folosiți sistemul de tipuri:

{templateType App\Presentation\Article\ArticleTemplate}
...

Astfel funcționează și șabloanele în componente, este suficient doar să respectați convenția de nume și pentru componenta, de ex. FifteenControl, să creați clasa șablonului FifteenTemplate.

Dacă aveți nevoie să creați $template ca instanță a altei clase, utilizați metoda createTemplate():

public function renderDefault(): void
{
	$template = $this->createTemplate(SpecialTemplate::class);
	$template->foo = 123;
	// ...
	$this->sendTemplate($template);
}

Variabile implicite

Presenterele și componentele transmit automat către șabloane câteva variabile utile:

  • $basePath este calea URL absolută către directorul rădăcină (de ex. /eshop)
  • $baseUrl este URL-ul absolut către directorul rădăcină (de ex. http://localhost/eshop)
  • $user este obiectul reprezentând utilizatorul
  • $presenter este presenterul curent
  • $control este componenta sau presenterul curent
  • $flashes array de mesaje trimise de funcția flashMessage()

Dacă utilizați propria clasă de șablon, aceste variabile se transmit dacă creați proprietăți pentru ele.

Crearea linkurilor

În șablon se creează linkuri către alți presenteri & acțiuni în acest mod:

<a n:href="Product:show">detaliu produs</a>

Atributul n:href este foarte util pentru tag-urile HTML <a>. Dacă dorim să afișăm linkul în altă parte, de exemplu în text, folosim {link}:

Adresa este: {link Home:default}

Mai multe informații găsiți în capitolul Crearea linkurilor URL.

Filtre personalizate, tag-uri etc.

Sistemul de șabloane Latte poate fi extins cu filtre, funcții, tag-uri etc. personalizate. Acest lucru se poate face direct în metoda render<View> sau beforeRender():

public function beforeRender(): void
{
	// adăugarea unui filtru
	$this->template->addFilter('foo', /* ... */);

	// sau configurăm direct obiectul Latte\Engine
	$latte = $this->template->getLatte();
	$latte->addFilterLoader(/* ... */);
}

Latte în versiunea 3 oferă o modalitate mai avansată și anume crearea unei extensii pentru fiecare proiect web. Un exemplu fragmentar al unei astfel de clase:

namespace App\Presentation\Accessory;

final class LatteExtension extends Latte\Extension
{
	public function __construct(
		private App\Model\Facade $facade,
		private Nette\Security\User $user,
		// ...
	) {
	}

	public function getFilters(): array
	{
		return [
			'timeAgoInWords' => $this->filterTimeAgoInWords(...),
			'money' => $this->filterMoney(...),
			// ...
		];
	}

	public function getFunctions(): array
	{
		return [
			'canEditArticle' =>
				fn($article) => $this->facade->canEditArticle($article, $this->user->getId()),
			// ...
		];
	}

	// ...
}

O înregistrăm folosind configurația:

latte:
	extensions:
		- App\Presentation\Accessory\LatteExtension

Traducere

Dacă programați o aplicație multilingvă, probabil veți avea nevoie să afișați unele texte din șablon în diferite limbi. Nette Framework definește în acest scop o interfață pentru traducere Nette\Localization\Translator, care are o singură metodă translate(). Aceasta primește mesajul $message, care de obicei este un șir de caractere, și orice alți parametri. Sarcina este de a returna șirul tradus. În Nette nu există nicio implementare implicită, puteți alege în funcție de nevoile dvs. dintre mai multe soluții gata făcute, pe care le găsiți pe Componette. În documentația lor veți afla cum să configurați translatorul.

Șabloanelor li se poate seta un traducător, pe care îl primim transmis, prin metoda setTranslator():

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator);
}

Translatorul poate fi setat alternativ folosind configurația:

latte:
	extensions:
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)

Apoi, traducătorul poate fi utilizat, de exemplu, ca filtru |translate, inclusiv cu parametri suplimentari, care sunt transmiși metodei translate() (vezi foo, bar):

<a href="basket">{='Coș'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>

Sau ca tag cu underscore:

<a href="basket">{_'Coș'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>

Pentru traducerea unei secțiuni a șablonului există un tag pereche {translate} (de la Latte 2.11, anterior se folosea tag-ul {_}):

<a href="order">{translate}Comandă{/translate}</a>
<a href="order">{translate foo, bar}Comandă{/translate}</a>

Translatorul este apelat standard în timpul rulării la redarea șablonului. Latte versiunea 3, însă, poate traduce toate textele statice deja în timpul compilării șablonului. Astfel se economisește performanță, deoarece fiecare șir se traduce o singură dată și traducerea rezultată se scrie în forma compilată. În directorul cu cache se creează astfel mai multe versiuni compilate ale șablonului, una pentru fiecare limbă. Pentru aceasta este suficient doar să specificați limba ca al doilea parametru:

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator, $lang);
}

Prin text static se înțelege, de exemplu, {_'hello'} sau {translate}hello{/translate}. Textele nestatice, cum ar fi {_$foo}, se vor traduce în continuare în timpul rulării.

versiune: 4.0