Sablonok

A Nette a Latte sablonrendszert használja. A Latte-t azért használjuk, mert ez a legbiztonságosabb sablonrendszer a PHP számára, ugyanakkor a legintuitívabb rendszer. Nem kell sok újat tanulnod, csak ismerned kell a PHP-t és néhány Latte taget.

Az a szokásos, hogy az oldal az elrendezési sablonból + az akció sablonból készül el. Így nézhet ki egy layout sablon, figyeld meg a {block} blokkokat és a {include} címkét :

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

És ez lehet az akció sablon:

{block title}Homepage{/block}

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

Meghatározza a content blokkot, amely az elrendezésben a {include content} helyére kerül, és újra definiálja a title blokkot is, amely felülírja az elrendezésben a {block title} blokkot. Próbálja meg elképzelni az eredményt.

Sablon keresés

A prezenterekben nem kell megadnia, hogy melyik sablont kell megjeleníteni; a keretrendszer automatikusan meghatározza az útvonalat, megkönnyítve ezzel a kódolást.

Ha olyan könyvtárstruktúrát használ, ahol minden prezenternek saját könyvtára van, egyszerűen helyezze el a sablont ebben a könyvtárban az akció (pl. nézet) neve alatt. Például a default művelethez használja a default.latte sablont:

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

Ha olyan struktúrát használ, ahol az előadók együttesen egy könyvtárban vannak, a sablonok pedig a templates mappában, mentse el vagy egy fájlban <Presenter>.<view>.latte vagy a <Presenter>/<view>.latte:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home.default.latte  ← 1st variant
        └── Home/
            └── default.latte   ← 2nd variant

A templates könyvtár egy szinttel feljebb is elhelyezhető, ugyanazon a szinten, mint az előadói osztályokat tartalmazó könyvtár.

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

A nézetet a $this->setView('anotherView') segítségével lehet megváltoztatni. Lehetőség van a sablonfájl közvetlen megadására is a $this->template->setFile('/path/to/template.latte') segítségével.

A fájlokat, amelyekben a sablonok keresése történik, a formatTemplateFiles() metódus felülbírálásával lehet megváltoztatni, amely a lehetséges fájlnevek tömbjét adja vissza.

Layout sablon keresés

A Nette automatikusan megkeresi az elrendezési fájlt is.

Ha olyan könyvtárstruktúrát használ, ahol minden előadónak saját könyvtára van, akkor az elrendezést vagy az előadóval közös mappába helyezze el, ha csak rá jellemző, vagy egy szinttel feljebb, ha több előadó számára közös:

app/
└── UI/
    ├── @layout.latte           ← common layout
    └── Home/
        ├── @layout.latte       ← only for Home presenter
        ├── HomePresenter.php
        └── default.latte

Ha olyan struktúrát használ, ahol az előadók egy könyvtárban vannak csoportosítva, a sablonok pedig a templates mappában találhatók, az elrendezés a következő helyeken várható:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── @layout.latte       ← common layout
        ├── Home.@layout.latte  ← only for Home, 1st variant
        └── Home/
            └── @layout.latte   ← only for Home, 2nd variant

Ha a bemutató egy modulban van, akkor a modul beágyazottságának megfelelően a könyvtárfában feljebb is keresni fog.

Az elrendezés nevét a $this->setLayout('layoutAdmin') segítségével lehet megváltoztatni, majd a @layoutAdmin.latte fájlban várjuk el. Az elrendezés sablonfájlt közvetlenül is megadhatja a $this->setLayout('/path/to/template.latte') segítségével.

A $this->setLayout(false) vagy a {layout none} címke használata a sablonon belül kikapcsolja az elrendezéskeresést.

A fájlok, amelyekben az elrendezési sablonok keresése történik, megváltoztathatók a formatLayoutTemplateFiles() metódus felülbírálásával, amely a lehetséges fájlnevek tömbjét adja vissza.

Változók a sablonban

A változókat a $this->template címre írva adjuk át a sablonba, majd a sablonban helyi változóként állnak rendelkezésre:

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

Így könnyen átadhatunk bármilyen változót a sablonoknak. Robusztus alkalmazások fejlesztésénél azonban gyakran hasznosabb, ha korlátozzuk magunkat. Például úgy, hogy explicit módon definiáljuk a sablon által elvárt változók listáját és azok típusát. Ez lehetővé teszi a PHP számára a típusellenőrzést, az IDE számára a helyes automatikus kitöltést, a statikus elemzés számára pedig a hibák felderítését.

És hogyan definiáljunk egy ilyen felsorolást? Egyszerűen egy osztály és annak tulajdonságai formájában. A presenterhez hasonlóan nevezzük el, de a végén Template címmel:

/**
 * @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 egyéb változók
}

A $this->template objektum a prezenterben mostantól a ArticleTemplate osztály példánya lesz. A PHP tehát ellenőrizni fogja a deklarált típusokat íráskor. A PHP 8.2-től kezdve pedig figyelmeztetni fog a nem létező változóba való írásra is, a korábbi verziókban ugyanezt a Nette\SmartObject tulajdonsággal lehet elérni.

A @property-read annotáció az IDE és a statikus elemzés számára készült, ez teszi működőképessé az automatikus kitöltést, lásd PhpStorm és kódkiegészítés $this->template számára.

A sablonokban is megengedheted magadnak a suttogás luxusát, csak telepítsd a Latte plugint a PhpStormban, és add meg az osztály nevét a sablon elején, lásd a Latte: hogyan kell tipizálni a rendszert című cikket:

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

Így működnek a sablonok a komponensekben is, csak kövesse a névadási konvenciót, és hozzon létre egy sablon osztályt FifteenTemplate a komponenshez pl. FifteenControl.

Ha a $template egy másik osztály példányaként kell létrehozni, használjuk 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 prezenterek és komponensek számos hasznos változót adnak át automatikusan a sablonoknak:

  • $basePath egy abszolút URL elérési útvonal a gyökérkönyvtárhoz (például /CD-collection).
  • $baseUrl egy abszolút URL cím a gyökérkönyvtárhoz (pl. http://localhost/CD-collection)
  • $user egy objektum, amely a felhasználót képviseli
  • $presenter az aktuális előadó
  • $control az aktuális komponens vagy bemutató
  • $flashes a módszer által küldött üzenetek listája flashMessage()

Ha egyéni sablonosztályt használ, ezeket a változókat akkor adja át, ha létrehoz egy tulajdonságot számukra.

A sablonban az alábbiak szerint hozunk létre linkeket más előadókra és műveletekre:

<a n:href="Product:show">detail</a>

A n:href attribútum nagyon hasznos a HTML címkékhez. <a>. Ha a linket máshol, például a szövegben akarjuk kiírni, akkor a {link}-t használjuk:

URL is: {link Home:default}

További információért lásd: Linkek létrehozása.

Egyéni szűrők, címkék stb.

A Latte templating rendszer kibővíthető egyéni szűrőkkel, függvényekkel, címkékkel stb. Ez közvetlenül a render<View> vagy a beforeRender() metódusban:

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

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

A Latte 3. verziója egy fejlettebb módszert kínál, amely minden egyes webes projekthez egy bővítményt hoz létre. Íme egy durva példa egy ilyen osztályra:

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

	// ...
}

configuration segítségével regisztráljuk:

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

Fordítás

Ha többnyelvű alkalmazást programoz, valószínűleg a sablonban lévő szöveg egy részét különböző nyelveken kell majd kiadnia. Ehhez a Nette Framework definiál egy fordítási felületet Nette\Localization\Translator, amelynek egyetlen metódusa a translate(). Ez elfogadja a $message üzenetet, amely általában egy karakterlánc, és bármilyen más paramétert. A feladat a lefordított karakterlánc visszaadása. A Nette-ben nincs alapértelmezett implementáció, a Componette-en található számos kész megoldás közül választhatunk igényeinknek megfelelően. A dokumentációjukból megtudhatjuk, hogyan kell a fordítót konfigurálni.

A sablonokat a setTranslator() metódus segítségével állíthatjuk be egy fordítóval, amelyet átadunk magunknak:

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

Alternatívaként a fordítót a konfiguráció segítségével is beállíthatjuk:

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

A fordító ekkor például a |translate szűrőként használható, a translate() metódusnak átadott további paraméterekkel (lásd foo, bar):

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

Vagy aláhúzáscímkeként:

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

A sablonrészlet fordításához van egy párosított tag {translate} (a Latte 2.11 óta, korábban a {_} taget használták ):

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

A fordítót alapértelmezés szerint futásidőben hívja meg a sablon renderelésekor. A Latte 3. verziója azonban képes lefordítani az összes statikus szöveget a sablon összeállítása során. Ez teljesítményt takarít meg, mivel minden egyes karakterláncot csak egyszer fordít le, és az így kapott fordítás a lefordított formába kerül. Ez a sablon több lefordított változatát hozza létre a gyorsítótárban, egyet-egyet minden nyelvhez. Ehhez csak a nyelvet kell megadni második paraméterként:

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

Statikus szöveg alatt például a {_'hello'} vagy a {translate}hello{/translate} szöveget értjük. A nem statikus szövegek, például a {_$foo}, továbbra is menet közben kerülnek lefordításra.