Sablonok

A Nette a Latte sablonrendszert használja. Egyrészt azért, mert ez a legbiztonságosabb sablonrendszer PHP-hoz, másrészt pedig a legintuitívabb rendszer. Nem kell sok újat tanulnia, elegendő a PHP ismerete és néhány tag.

Gyakori, hogy egy oldal egy layout sablonból + az adott akció sablonjából áll össze. Így nézhet ki például egy layout sablon, figyelje meg a {block} blokkokat és a {include} taget:

<!DOCTYPE html>
<html>
<head>
	<title>{block title}Saját Alkalmazás{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

És ez lesz az akció sablonja:

{block title}Kezdőlap{/block}

{block content}
<h1>Kezdőlap</h1>
...
{/block}

Ez definiálja a content blokkot, amely a {include content} helyére kerül a layoutban, és újra definiálja a title blokkot, amely felülírja a {block title}-t a layoutban. Próbálja meg elképzelni az eredményt.

Sablonok keresése

Nem kell a presenterekben megadnia, hogy melyik sablont kell renderelni, a keretrendszer maga vezeti le az utat, és megspórolja Önnek az írást.

Ha olyan könyvtárstruktúrát használ, ahol minden presenternek saját könyvtára van, egyszerűen helyezze el a sablont ebben a könyvtárban az akció (ill. view) nevével, azaz a default akcióhoz használja a default.latte sablont:

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

Ha olyan struktúrát használ, ahol a presenterek egy könyvtárban vannak, a sablonok pedig a templates mappában, mentse el vagy a <Presenter>.<view>.latte vagy a <Presenter>/<view>.latte fájlba:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home.default.latte  ← 1. változat
        └── Home/
            └── default.latte   ← 2. változat

A templates könyvtár egy szinttel feljebb is elhelyezkedhet, azaz ugyanazon a szinten, mint a presenter osztályokat tartalmazó könyvtár.

Ha a sablon nem található, a presenter 404 – page not found hibával válaszol.

A view-t a $this->setView('jineView') segítségével változtathatja meg. Közvetlenül is megadhatja a sablonfájlt a $this->template->setFile('/path/to/template.latte') segítségével.

A fájlokat, ahol a sablonokat keresi, meg lehet változtatni a formatTemplateFiles() metódus felülírásával, amely visszaadja a lehetséges fájlnevek tömbjét.

Layout sablon keresése

A Nette automatikusan megkeresi a layout fájlt is.

Ha olyan könyvtárstruktúrát használ, ahol minden presenternek saját könyvtára van, helyezze el a layoutot vagy a presenter mappájában, ha csak rá specifikus, vagy egy szinttel feljebb, ha több presenter számára közös:

app/
└── Presentation/
    ├── @layout.latte           ← közös layout
    └── Home/
        ├── @layout.latte       ← csak a Home presenterhez
        ├── HomePresenter.php
        └── default.latte

Ha olyan struktúrát használ, ahol a presenterek egy könyvtárban vannak, a sablonok pedig a templates mappában, a layoutot ezeken a helyeken várja:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── @layout.latte       ← közös layout
        ├── Home.@layout.latte  ← csak a Home-hoz, 1. változat
        └── Home/
            └── @layout.latte   ← csak a Home-hoz, 2. változat

Ha a presenter egy modulban található, akkor további könyvtárszinteken is keresni fog, a modul beágyazási mélységétől függően.

A layout nevét a $this->setLayout('layoutAdmin') segítségével lehet megváltoztatni, és akkor a @layoutAdmin.latte fájlban várja. Közvetlenül is megadhatja a layout sablonfájlt a $this->setLayout('/path/to/template.latte') segítségével.

A $this->setLayout(false) vagy a {layout none} tag használatával a sablonon belül kikapcsolható a layout keresése.

A fájlokat, ahol a layout sablonokat keresi, meg lehet változtatni a formatLayoutTemplateFiles() metódus felülírásával, amely visszaadja a lehetséges fájlnevek tömbjét.

Változók a sablonban

Változókat úgy adunk át a sablonnak, hogy beírjuk őket a $this->template-be, és utána lokális változókként érhetők el a sablonban:

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

Így egyszerűen bármilyen változót átadhatunk a sablonoknak. Robusztus alkalmazások fejlesztésekor azonban hasznosabb korlátozni magunkat. Például úgy, hogy explicit módon definiáljuk a sablon által várt változók listáját és azok típusait. Ennek köszönhetően a PHP ellenőrizni tudja a típusokat, az IDE helyesen tud súgni, és a statikus analízis felfedezheti a hibákat.

És hogyan definiálunk egy ilyen listát? Egyszerűen egy osztály és annak property-jei formájában. Nevezzük el hasonlóan a presenterhez, csak Template végződéssel:

/**
 * @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;

	// és további változók
}

A $this->template objektum a presenterben mostantól az ArticleTemplate osztály példánya lesz. Így a PHP ellenőrizni fogja a deklarált típusokat íráskor. És a PHP 8.2-es verziójától kezdve figyelmeztet a nem létező változóba való írásra is, korábbi verziókban ugyanezt a Nette\SmartObject trait használatával lehet elérni.

Az @property-read annotáció az IDE-nek és a statikus analízisnek szól, ennek köszönhetően működni fog a súgó, lásd PhpStorm and code completion for $this⁠-⁠>⁠template.

A súgó luxusát a sablonokban is élvezheti, csak telepíteni kell a PhpStorm-ba a Latte plugint, és a sablon elejére beírni az osztály nevét, további információk a Latte: hogyan a típusrendszerre cikkben:

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

Így működnek a sablonok a komponensekben is, csak be kell tartani a névkonvenciót, és például a FifteenControl komponenshez létrehozni egy FifteenTemplate sablonosztályt.

Ha a $template-et egy másik osztály példányaként kell létrehoznia, használja a createTemplate() metódust:

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

Alapértelmezett változók

A presenterek és komponensek automatikusan átadnak néhány hasznos változót a sablonoknak:

  • $basePath az abszolút URL elérési út a gyökérkönyvtárhoz (pl. /eshop)
  • $baseUrl az abszolút URL a gyökérkönyvtárhoz (pl. http://localhost/eshop)
  • $user a felhasználót reprezentáló objektum
  • $presenter az aktuális presenter
  • $control az aktuális komponens vagy presenter
  • $flashes a flashMessage() függvénnyel küldött üzenetek tömbje

Ha saját sablonosztályt használ, ezek a változók átadódnak, ha létrehoz hozzájuk property-t.

Linkek létrehozása

A sablonban a linkek más presenterekhez & akciókhoz a következőképpen hozhatók létre:

<a n:href="Product:show">termék részletei</a>

Az n:href attribútum nagyon praktikus a HTML <a> tag-ekhez. Ha máshol szeretnénk kiírni a linket, például szövegben, használjuk a {link}-et:

A cím: {link Home:default}

További információkat az URL linkek létrehozása fejezetben talál.

Saját szűrők, tag-ek stb.

A Latte sablonrendszert ki lehet bővíteni saját szűrőkkel, függvényekkel, tag-ekkel stb. Ezt meg lehet tenni közvetlenül a render<View> vagy beforeRender() metódusban:

public function beforeRender(): void
{
	// szűrő hozzáadása
	$this->template->addFilter('foo', /* ... */);

	// vagy közvetlenül konfiguráljuk a Latte\Engine objektumot
	$latte = $this->template->getLatte();
	$latte->addFilterLoader(/* ... */);
}

A Latte 3-as verziója fejlettebb módszert kínál, mégpedig egy extension létrehozását minden webprojekthez. Egy ilyen osztály töredékes példája:

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()),
			// ...
		];
	}

	// ...
}

Regisztráljuk a konfiguráció segítségével:

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

Fordítás

Ha többnyelvű alkalmazást programoz, valószínűleg szüksége lesz néhány szöveg lefordítására a sablonban különböző nyelvekre. A Nette Framework erre a célra definiál egy interfészt a fordításhoz Nette\Localization\Translator, amelynek egyetlen metódusa van, a translate(). Ez fogadja az üzenetet $message, ami általában egy string, és tetszőleges további paramétereket. Feladata a lefordított string visszaadása. A Nette-ben nincs alapértelmezett implementáció, választhat igényei szerint több kész megoldás közül, amelyeket a Componette oldalon talál. Dokumentációjukban megtudhatja, hogyan konfigurálja a translatort.

A sablonoknak beállítható egy fordító, amelyet átkérünk, a setTranslator() metódussal:

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

A translatort alternatívaként be lehet állítani a konfiguráció segítségével is:

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

Ezután a fordítót például használhatjuk |translate szűrőként, beleértve a kiegészítő paramétereket is, amelyek átadódnak a translate() metódusnak (lásd foo, bar):

<a href="basket">{='Kosár'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>

Vagy aláhúzásos tagként:

<a href="basket">{_'Kosár'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>

A sablon egy szakaszának fordításához létezik egy páros {translate} tag (Latte 2.11-től, korábban a {_} tag volt használatos):

<a href="order">{translate}Rendelés{/translate}</a>
<a href="order">{translate foo, bar}Rendelés{/translate}</a>

A translator alapértelmezés szerint futásidőben hívódik meg a sablon renderelésekor. A Latte 3-as verziója azonban képes az összes statikus szöveget már a sablon fordítása során lefordítani. Ezzel teljesítményt takarítunk meg, mert minden string csak egyszer fordítódik le, és az eredményül kapott fordítás beíródik a lefordított formába. A cache könyvtárban így több lefordított sablonverzió jön létre, minden nyelvre egy. Ehhez elegendő csak a nyelvet második paraméterként megadni:

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

Statikus szöveg alatt például a {_'hello'} vagy {translate}hello{/translate} értendő. A nem statikus szövegek, mint például a {_$foo}, továbbra is futásidőben fordítódnak.