Szablony
Nette używa systemu szablonów Latte. Latte jest używane, bo to najbezpieczniejszy system szablonów dla PHP, a zarazem najbardziej intuicyjny. Nie musisz uczyć się wiele nowego; wystarczy znajomość PHP i kilku tagów.
Powszechnie strona składa się z szablonu layoutu i szablonu konkretnej akcji. Tak może wyglądać szablon layoutu; zwróć
uwagę na bloki {block} i tag {include}:
<!DOCTYPE html>
<html>
<head>
<title>{block title}Moja aplikacja{/block}</title>
</head>
<body>
<header>...</header>
{include content}
<footer>...</footer>
</body>
</html>
A tak wyglądałby szablon akcji:
{block title}Strona główna{/block}
{block content}
<h1>Strona główna</h1>
...
{/block}
Definiuje on blok content, który wstawiany jest w layoucie w miejsce {include content}, a także na
nowo definiuje blok title, który nadpisuje {block title} w layoucie. Spróbuj wyobrazić
sobie wynik.
Wyszukiwanie szablonów
W presenterach nie musisz podawać, który szablon ma zostać wyrenderowany; framework sam wywnioskuje ścieżkę, oszczędzając Ci pisania.
Jeśli używasz struktury katalogów, w której każdy presenter ma własny katalog, po prostu umieść szablon w tym katalogu
pod nazwą akcji (czyli widoku). Na przykład dla akcji default użyj szablonu default.latte:
app/
└── Presentation/
└── Home/
├── HomePresenter.php
└── default.latte
Jeśli używasz struktury, w której presentery leżą razem w jednym katalogu, a szablony w folderze templates,
zapisz go albo w pliku <Presenter>.<widok>.latte, albo
<Presenter>/<widok>.latte:
app/
└── Presenters/
├── HomePresenter.php
└── templates/
├── Home/
│ └── default.latte ← 1. wariant
└── Home.default.latte ← 2. wariant
Katalog templates może leżeć też o poziom wyżej, czyli na tym samym poziomie co katalog z klasami
presenterów.
Jeśli szablon się nie znajdzie, presenter odpowiada błędem 404 – strona nie znaleziona.
Widok możesz zmienić przez $this->setView('otherView'). Można też bezpośrednio wskazać plik szablonu
przez $this->template->setFile('/path/to/template.latte').
Pliki, w których wyszukiwane są szablony, można zmienić, nadpisując metodę formatTemplateFiles(), która zwraca tablicę możliwych nazw plików.
Wyszukiwanie szablonu layoutu
Nette automatycznie wyszukuje również plik layoutu.
Jeśli używasz struktury katalogów, w której każdy presenter ma własny katalog, umieść layout albo w folderze z presenterem, jeśli jest przeznaczony tylko dla niego, albo o poziom wyżej, jeśli jest wspólny dla kilku presenterów:
app/
└── Presentation/
├── @layout.latte ← wspólny layout
└── Home/
├── @layout.latte ← tylko dla presentera Home
├── HomePresenter.php
└── default.latte
Jeśli używasz struktury, w której presentery zgrupowane są w jednym katalogu, a szablony w folderze templates,
layout będzie oczekiwany w tych miejscach:
app/
└── Presenters/
├── HomePresenter.php
└── templates/
├── @layout.latte ← wspólny layout
├── Home/
│ └── @layout.latte ← tylko dla Home, 1. wariant
└── Home.@layout.latte ← tylko dla Home, 2. wariant
Jeśli presenter leży w module, wyszukiwanie postępuje też wyżej po poziomach katalogów, zgodnie z zagnieżdżeniem modułów.
Nazwę layoutu można zmienić przez $this->setLayout('layoutAdmin'), a wtedy będzie oczekiwany w pliku
@layoutAdmin.latte. Możesz też bezpośrednio wskazać plik szablonu layoutu przez
$this->setLayout('/path/to/template.latte').
Użycie $this->setLayout(false) albo tagu {layout none} wewnątrz szablonu wyłącza wyszukiwanie
layoutu.
Pliki, w których wyszukiwane są szablony layoutu, można zmienić, nadpisując metodę formatLayoutTemplateFiles(), która zwraca tablicę możliwych nazw plików.
Zmienne szablonu
Zmienne przekazuje się do szablonów, zapisując je do $this->template. Stają się wtedy dostępne w
szablonie jako zmienne lokalne:
$this->template->article = $this->articles->getById($id);
Aby wartość właściwości była automatycznie przekazywana do szablonu jako zmienna, oznacz ją
atrybutem #[TemplateVariable] i widocznością public:
use Nette\Application\Attributes\TemplateVariable;
class ArticlePresenter extends Nette\Application\UI\Presenter
{
#[TemplateVariable]
public string $siteName = 'Mój blog';
}
Jeśli przekażesz do szablonu zmienną o tej samej nazwie, #[TemplateVariable] jej nie nadpisze.
Zmienne domyślne
Presentery i komponenty automatycznie przekazują do szablonów kilka przydatnych zmiennych:
$basePathto bezwzględna ścieżka URL do katalogu głównego (np./eshop)$baseUrlto bezwzględny URL katalogu głównego (np.http://localhost/eshop)$userto obiekt reprezentujący użytkownika$presenterto bieżący presenter$controlto bieżący komponent albo presenter$flashesto tablica wiadomości wysłanych funkcjąflashMessage()
Jeśli używasz własnej klasy szablonu, zmienne te są przekazywane, o ile utworzysz dla nich właściwość.
Szablony bezpieczne typowo
Przy tworzeniu solidnych aplikacji przydaje się jawne określenie, jakich zmiennych oczekuje szablon i jakich są typów. Daje to kontrolę typów w PHP, inteligentne podpowiedzi w IDE i pozwala analizie statycznej wyłapywać błędy.
Jak zdefiniować taką listę? Po prostu jako klasę z właściwościami reprezentującymi zmienne szablonu. Nazwij ją
podobnie jak presenter, tylko z Template na końcu:
/**
* @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 inne zmienne
}
Obiekt $this->template w presenterze będzie teraz instancją klasy ArticleTemplate. PHP będzie
więc przy zapisie sprawdzać zadeklarowane typy.
Nette wybiera klasę szablonu automatycznie. Najpierw szuka klasy o nazwie
<Presenter><Akcja>Template, np. ArticleEditTemplate dla akcji edit, i dopiero
gdy jej nie ma, sięga po <Presenter>Template.
Adnotacja @property-read przeznaczona jest dla IDE i analizy statycznej, umożliwia uzupełnianie kodu, zobacz PhpStorm i uzupełnianie kodu dla
$this->template.

Uzupełniania kodu możesz używać również bezpośrednio w szablonach. Wystarczy zainstalować plugin Latte do PhpStorm i podać na początku szablonu nazwę klasy parametrów szablonu, więcej w rozdziale Latte: system typów:
{templateType App\Presentation\Article\ArticleTemplate}
...
To samo dotyczy komponentów. Wystarczy trzymać się konwencji nazewniczej i dla komponentu w rodzaju
FifteenControl utworzyć klasę parametrów FifteenTemplate.
Jeśli potrzebujesz użyć innej klasy parametrów, skorzystaj z metody createTemplate():
public function renderDefault(): void
{
$template = $this->createTemplate(SpecialTemplate::class);
$template->foo = 123;
// ...
$this->sendTemplate($template);
}
Jeśli potrzebujesz wpłynąć na to, jak szablon jest finalizowany przed renderowaniem, na przykład aby
dodać zmienne wspólne dla wszystkich akcji, możesz nadpisać w presenterze metodę completeTemplate(). Wywoływana
jest tuż przed wyrenderowaniem szablonu:
protected function completeTemplate(Nette\Application\UI\Template $template): void
{
parent::completeTemplate($template);
$template->siteName = 'Mój blog';
}
Tworzenie odnośników
W szablonie odnośniki do innych presenterów i akcji tworzy się tak:
<a n:href="Product:show">szczegóły produktu</a>
Atrybut n:href jest bardzo poręczny dla tagów HTML <a>. Jeśli chcemy wypisać odnośnik
gdzie indziej, na przykład w tekście, użyjemy {link}:
URL to: {link Home:default}
Więcej informacji znajdziesz w rozdziale Tworzenie odnośników URL.
Własne filtry, tagi itd.
System szablonów Latte można rozszerzać o własne filtry, funkcje, tagi i inne elementy. Dostępne są trzy podejścia, od szybkich rozwiązań doraźnych po wzorce architektoniczne dla całych aplikacji.
Doraźnie w metodach presentera
Najszybszym podejściem jest dodawanie filtrów albo funkcji bezpośrednio w kodzie presentera lub komponentu. W presenterach
dobrze nadają się do tego metody beforeRender() albo render<Widok>():
protected function beforeRender(): void
{
// dodanie filtra
$this->template->addFilter('money', fn($val) => '$' . number_format($val, 2));
// dodanie funkcji
$this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6);
}
W szablonie:
<p>Cena: {$price|money}</p>
{if isWeekend($now)} ... {/if}
Przy bardziej złożonej logice możesz skonfigurować bezpośrednio obiekt Latte\Engine:
protected function beforeRender(): void
{
$latte = $this->template->getLatte();
$latte->setFeature(Latte\Feature::MigrationWarnings);
}
Za pomocą atrybutów
Eleganckim podejściem jest zdefiniowanie filtrów i funkcji jako metod bezpośrednio w klasie parametrów szablonu presentera albo komponentu, oznaczonych atrybutami:
class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
#[Latte\Attributes\TemplateFilter]
public function money(float $val): string
{
return '$' . number_format($val, 2);
}
#[Latte\Attributes\TemplateFunction]
public function isWeekend(DateTimeInterface $date): bool
{
return $date->format('N') >= 6;
}
}
Latte automatycznie odnajduje i rejestruje metody oznaczone tymi atrybutami. Nazwa filtra albo funkcji w szablonach odpowiada nazwie metody. Metody te muszą być publiczne.
Globalnie za pomocą rozszerzeń
Poprzednie podejścia nadają się do filtrów i funkcji potrzebnych tylko w konkretnych presenterach albo komponentach, a nie w całej aplikacji. Dla całej aplikacji najlepiej sprawdza się utworzenie rozszerzenia. Klasa ta centralizuje wszystkie rozszerzenia Latte w Twoim projekcie. Krótki przykład:
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()),
// ...
];
}
private function filterTimeAgoInWords(DateTimeInterface $time): string
{
// ...
}
// ...
}
Rozszerzenie zarejestrujesz przez konfigurację:
latte:
extensions:
- App\Presentation\Accessory\LatteExtension
Rozszerzenia dają kilka korzyści: wsparcie dla wstrzykiwania zależności, dostęp do warstwy modelu Twojej aplikacji i centralne zarządzanie wszystkimi rozszerzeniami. Obsługują też własne tagi, providery, compiler passy i inne rzeczy.
Ustawienie wszystkich szablonów
Usługa TemplateFactory, która tworzy wszystkie szablony, oferuje publiczną tablicę callbacków
$onCreate. Wywoływane są one przy każdym utworzeniu dowolnego szablonu, dzięki czemu z jednego miejsca ustawisz
filtry, funkcje albo zmienne dla wszystkich szablonów w aplikacji. Każdy callback otrzymuje nowo utworzony szablon. Każ sobie
wstrzyknąć usługę
TemplateFactory i zarejestruj callbacki, np. przy starcie aplikacji:
$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void {
$template->addFilter('money', fn($val) => '$' . number_format($val, 2));
};
Tłumaczenie
Jeśli programujesz aplikację wielojęzyczną, prawdopodobnie będziesz potrzebować wypisywać w szablonie niektóre teksty w
różnych językach. Nette Framework definiuje w tym celu interfejs tłumaczenia Nette\Localization\Translator, który ma jedną
metodę translate(). Przyjmuje ona komunikat $message, którym zwykle jest string, oraz dowolne inne
parametry. Zadaniem jest zwrócenie przetłumaczonego stringa. Nette nie ma domyślnej implementacji; możesz wybrać z kilku
gotowych rozwiązań dostępnych na Componette według swoich potrzeb.
Ich dokumentacja wyjaśnia, jak skonfigurować translator.
Szablonom można ustawić translator, który otrzymamy wstrzyknięty, metodą
setTranslator():
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator);
}
Alternatywnie translator można ustawić przez konfigurację:
latte:
extensions:
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Translatora można potem używać na przykład jako filtra |translate, wraz z dodatkowymi parametrami
przekazywanymi do metody translate() (zobacz foo, bar):
<a href="basket">{='Koszyk'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>
Albo jako tagu z podkreśleniem:
<a href="basket">{_'Koszyk'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>
Do przetłumaczenia fragmentu szablonu służy tag parzysty {translate} (od Latte 2.11, wcześniej używano tagu
{_}):
<a href="order">{translate}Zamów{/translate}</a>
<a href="order">{translate foo, bar}Zamów{/translate}</a>
Translator wywoływany jest normalnie w czasie działania, przy renderowaniu szablonu. Latte w wersji 3 potrafi jednak przetłumaczyć wszystkie teksty statyczne już podczas kompilacji szablonu. Oszczędza to wydajność, bo każdy string tłumaczony jest tylko raz, a powstałe tłumaczenie zapisywane jest do skompilowanej postaci. W katalogu cache powstaje wtedy kilka skompilowanych wersji szablonu, po jednej na język. Wystarczy do tego podać język jako drugi parametr:
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator, $lang);
}
Przez tekst statyczny rozumiemy na przykład {_'hello'} albo {translate}hello{/translate}. Teksty
niestatyczne, jak {_$foo}, nadal będą tłumaczone w czasie działania.