Templates
Nette verwendet das Templating-System Latte. Latte wird verwendet, weil es das sicherste Templating-System für PHP ist und zugleich das intuitivste. Sie müssen nicht viel Neues lernen; Kenntnisse von PHP und einigen wenigen Tags genügen.
Üblicherweise setzt sich eine Seite aus einem Layout-Template und dem Template der konkreten Aktion zusammen. So kann ein
Layout-Template aussehen; beachten Sie die Blöcke {block} und den Tag {include}:
<!DOCTYPE html>
<html>
<head>
<title>{block title}Meine App{/block}</title>
</head>
<body>
<header>...</header>
{include content}
<footer>...</footer>
</body>
</html>
Und so würde das Template der Aktion aussehen:
{block title}Startseite{/block}
{block content}
<h1>Startseite</h1>
...
{/block}
Es definiert den Block content, der im Layout anstelle von {include content} eingefügt wird, und
definiert außerdem den Block title neu, der {block title} im Layout überschreibt. Versuchen Sie sich
das Ergebnis vorzustellen.
Suche nach Templates
In Presentern müssen Sie nicht angeben, welches Template gerendert werden soll; das Framework leitet den Pfad selbst ab und erspart Ihnen das Schreiben.
Wenn Sie eine Verzeichnisstruktur verwenden, bei der jeder Presenter sein eigenes Verzeichnis hat, legen Sie das Template
einfach in dieses Verzeichnis unter dem Namen der Aktion (also des Views). Für die Aktion default verwenden Sie zum
Beispiel das Template default.latte:
app/
└── Presentation/
└── Home/
├── HomePresenter.php
└── default.latte
Wenn Sie eine Struktur verwenden, bei der die Presenter gemeinsam in einem Verzeichnis liegen und die Templates im Ordner
templates, speichern Sie es entweder in der Datei <Presenter>.<view>.latte oder
<Presenter>/<view>.latte:
app/
└── Presenters/
├── HomePresenter.php
└── templates/
├── Home/
│ └── default.latte ← 1. Variante
└── Home.default.latte ← 2. Variante
Das Verzeichnis templates kann auch eine Ebene höher liegen, also auf derselben Ebene wie das Verzeichnis mit den
Presenter-Klassen.
Wird das Template nicht gefunden, antwortet der Presenter mit dem Fehler 404 – Seite nicht gefunden.
Den View ändern Sie mit $this->setView('otherView'). Es lässt sich auch direkt die Template-Datei mit
$this->template->setFile('/path/to/template.latte') angeben.
Die Dateien, in denen nach Templates gesucht wird, lassen sich durch Überschreiben der Methode formatTemplateFiles() ändern, die ein Array möglicher Dateinamen zurückgibt.
Suche nach dem Layout-Template
Nette sucht auch die Layout-Datei automatisch.
Wenn Sie eine Verzeichnisstruktur verwenden, bei der jeder Presenter sein eigenes Verzeichnis hat, legen Sie das Layout entweder in den Ordner mit dem Presenter, falls es nur für ihn gilt, oder eine Ebene höher, falls es mehreren Presentern gemeinsam ist:
app/
└── Presentation/
├── @layout.latte ← gemeinsames Layout
└── Home/
├── @layout.latte ← nur für den Presenter Home
├── HomePresenter.php
└── default.latte
Wenn Sie eine Struktur verwenden, bei der die Presenter gemeinsam in einem Verzeichnis liegen und die Templates im Ordner
templates, wird das Layout an diesen Orten erwartet:
app/
└── Presenters/
├── HomePresenter.php
└── templates/
├── @layout.latte ← gemeinsames Layout
├── Home/
│ └── @layout.latte ← nur für Home, 1. Variante
└── Home.@layout.latte ← nur für Home, 2. Variante
Liegt der Presenter in einem Modul, wird entsprechend der Verschachtelung der Module auch in den höheren Verzeichnisebenen gesucht.
Den Namen des Layouts ändern Sie mit $this->setLayout('layoutAdmin'), es wird dann in der Datei
@layoutAdmin.latte erwartet. Sie können die Datei des Layout-Templates auch direkt mit
$this->setLayout('/path/to/template.latte') angeben.
Mit $this->setLayout(false) oder dem Tag {layout none} im Template wird die Suche nach dem Layout
abgeschaltet.
Die Dateien, in denen nach Layout-Templates gesucht wird, lassen sich durch Überschreiben der Methode formatLayoutTemplateFiles() ändern, die ein Array möglicher Dateinamen zurückgibt.
Variablen im Template
Variablen übergibt man an Templates, indem man sie in $this->template schreibt. Im Template stehen sie dann
als lokale Variablen zur Verfügung:
$this->template->article = $this->articles->getById($id);
Um den Wert einer Property automatisch als Variable an das Template zu übergeben, kennzeichnen Sie sie
mit dem Attribut #[TemplateVariable] und public-Sichtbarkeit:
use Nette\Application\Attributes\TemplateVariable;
class ArticlePresenter extends Nette\Application\UI\Presenter
{
#[TemplateVariable]
public string $siteName = 'Mein Blog';
}
Übergeben Sie dem Template eine Variable gleichen Namens, überschreibt #[TemplateVariable] sie nicht.
Standardvariablen
Presenter und Komponenten übergeben Templates automatisch mehrere nützliche Variablen:
$basePathist der absolute URL-Pfad zum Wurzelverzeichnis (z. B./eshop)$baseUrlist die absolute URL zum Wurzelverzeichnis (z. B.http://localhost/eshop)$userist ein Objekt, das den Benutzer repräsentiert$presenterist der aktuelle Presenter$controlist die aktuelle Komponente oder der Presenter$flashesist ein Array von Meldungen, die mit der FunktionflashMessage()gesendet wurden
Wenn Sie eine eigene Template-Klasse verwenden, werden diese Variablen übergeben, sofern Sie eine Property dafür anlegen.
Typsichere Templates
Bei der Entwicklung robuster Anwendungen ist es nützlich, ausdrücklich festzulegen, welche Variablen das Template erwartet und welche Typen sie haben. Das bringt Typprüfung in PHP, intelligente Hinweise in Ihrer IDE und ermöglicht der statischen Analyse, Fehler aufzudecken.
Wie definiert man eine solche Liste? Einfach als Klasse mit Properties, die die Variablen des Templates darstellen. Benennen
Sie sie ähnlich wie den Presenter, nur mit Template am Ende:
/**
* @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;
// und weitere Variablen
}
Das Objekt $this->template im Presenter ist nun eine Instanz der Klasse ArticleTemplate. PHP
prüft dadurch beim Schreiben die deklarierten Typen.
Nette wählt die Template-Klasse automatisch. Zuerst sucht es eine Klasse namens
<Presenter><Aktion>Template, z. B. ArticleEditTemplate für die Aktion edit,
und erst wenn diese nicht existiert, greift es auf <Presenter>Template zurück.
Die Annotation @property-read ist für die IDE und die statische Analyse gedacht und ermöglicht
Code-Vervollständigung, siehe PhpStorm und
Code-Vervollständigung für $this->template.

Code-Vervollständigung lässt sich auch direkt in Templates nutzen. Installieren Sie einfach das Latte-Plugin für PhpStorm und geben Sie am Anfang des Templates den Klassennamen der Template-Parameter an, mehr im Kapitel Latte: Typsystem:
{templateType App\Presentation\Article\ArticleTemplate}
...
Dasselbe gilt für Komponenten. Halten Sie sich einfach an die Namenskonvention und legen Sie für eine Komponente wie
FifteenControl eine Parameterklasse FifteenTemplate an.
Wenn Sie eine andere Parameterklasse verwenden müssen, nutzen Sie die Methode createTemplate():
public function renderDefault(): void
{
$template = $this->createTemplate(SpecialTemplate::class);
$template->foo = 123;
// ...
$this->sendTemplate($template);
}
Wenn Sie beeinflussen wollen, wie das Template vor dem Rendern fertiggestellt wird – zum Beispiel um
Variablen zu ergänzen, die alle Aktionen gemeinsam haben -, können Sie im Presenter die Methode completeTemplate()
überschreiben. Sie wird unmittelbar vor dem Rendern des Templates aufgerufen:
protected function completeTemplate(Nette\Application\UI\Template $template): void
{
parent::completeTemplate($template);
$template->siteName = 'Mein Blog';
}
Links erstellen
Im Template werden Links auf andere Presenter & Aktionen so erstellt:
<a n:href="Product:show">Produktdetail</a>
Das Attribut n:href ist für HTML-Tags <a> sehr praktisch. Wollen wir den Link anderswo
ausgeben, zum Beispiel im Text, verwenden wir {link}:
Die URL lautet: {link Home:default}
Mehr dazu finden Sie im Kapitel Erstellen von URL-Links.
Eigene Filter, Tags usw.
Das Templating-System Latte lässt sich um eigene Filter, Funktionen, Tags und weitere Elemente erweitern. Dafür stehen drei Wege zur Verfügung, von schnellen Ad-hoc-Lösungen bis zu architektonischen Mustern für ganze Anwendungen.
Ad hoc in Methoden des Presenters
Der schnellste Weg ist, Filter oder Funktionen direkt im Code des Presenters oder der Komponente zu ergänzen. In Presentern
eignen sich dafür die Methoden beforeRender() oder render<View>():
protected function beforeRender(): void
{
// einen Filter hinzufügen
$this->template->addFilter('money', fn($val) => number_format($val, 2) . ' €');
// eine Funktion hinzufügen
$this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6);
}
Im Template:
<p>Preis: {$price|money}</p>
{if isWeekend($now)} ... {/if}
Für komplexere Logik können Sie das Objekt Latte\Engine direkt konfigurieren:
protected function beforeRender(): void
{
$latte = $this->template->getLatte();
$latte->setFeature(Latte\Feature::MigrationWarnings);
}
Mit Attributen
Ein eleganterer Weg ist, Filter und Funktionen als Methoden direkt in der Parameterklasse des Templates des Presenters oder der Komponente zu definieren und mit Attributen zu kennzeichnen:
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 findet und registriert die mit diesen Attributen gekennzeichneten Methoden automatisch. Der Name des Filters bzw. der Funktion im Template entspricht dem Methodennamen. Diese Methoden müssen public sein.
Global mit Extensions
Die bisherigen Wege eignen sich für Filter und Funktionen, die nur in bestimmten Presentern oder Komponenten gebraucht werden, nicht anwendungsweit. Für die gesamte Anwendung eignet sich am besten das Erstellen einer Extension. Diese Klasse bündelt alle Latte-Erweiterungen Ihres Projekts an einer Stelle. Ein kurzes Beispiel:
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
{
// ...
}
// ...
}
Registrieren Sie die Extension über die Konfiguration:
latte:
extensions:
- App\Presentation\Accessory\LatteExtension
Extensions bieten mehrere Vorteile: Unterstützung für Dependency Injection, Zugriff auf die Model-Schicht Ihrer Anwendung und zentrale Verwaltung aller Erweiterungen. Sie unterstützen außerdem eigene Tags, Provider, Compiler-Pässe und mehr.
Alle Templates einrichten
Der Service TemplateFactory, der alle Templates erzeugt, bietet ein öffentliches Array von Callbacks
$onCreate. Diese werden bei jedem Erzeugen eines beliebigen Templates aufgerufen, sodass Sie Filter, Funktionen oder
Variablen für alle Templates der Anwendung von einer einzigen Stelle aus einrichten können. Jedes Callback erhält das neu
erzeugte Template. Lassen Sie sich den Service TemplateFactory übergeben und registrieren Sie die Callbacks, z. B.
beim Start der Anwendung:
$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void {
$template->addFilter('money', fn($val) => number_format($val, 2) . ' €');
};
Übersetzen
Wenn Sie eine mehrsprachige Anwendung programmieren, werden Sie manche Texte im Template in verschiedenen Sprachen ausgeben
müssen. Das Nette Framework definiert dafür das Übersetzungs-Interface Nette\Localization\Translator mit der einzigen
Methode translate(). Sie nimmt die Nachricht $message entgegen, die üblicherweise ein String ist, sowie
beliebige weitere Parameter. Ihre Aufgabe ist es, den übersetzten String zurückzugeben. Nette enthält keine
Standardimplementierung; Sie können nach Ihren Bedürfnissen aus mehreren fertigen Lösungen wählen, die auf Componette verfügbar sind. In deren Dokumentation erfahren Sie, wie der
Translator konfiguriert wird.
Templates lassen sich mit einem Translator einrichten, den wir uns übergeben lassen, und zwar mit der Methode
setTranslator():
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator);
}
Alternativ lässt sich der Translator über die Konfiguration setzen:
latte:
extensions:
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Der Translator lässt sich dann zum Beispiel als Filter |translate verwenden, samt weiteren Parametern, die an die
Methode translate() übergeben werden (siehe foo, bar):
<a href="basket">{='Warenkorb'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>
Oder als Unterstrich-Tag:
<a href="basket">{_'Warenkorb'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>
Für die Übersetzung eines Abschnitts des Templates gibt es den Paar-Tag {translate} (seit Latte 2.11, zuvor
wurde der Tag {_} verwendet):
<a href="order">{translate}Bestellen{/translate}</a>
<a href="order">{translate foo, bar}Bestellen{/translate}</a>
Der Translator wird normalerweise zur Laufzeit beim Rendern des Templates aufgerufen. Latte in Version 3 kann jedoch alle statischen Texte bereits während der Kompilierung des Templates übersetzen. Das spart Leistung, denn jeder String wird nur einmal übersetzt, und die entstandene Übersetzung wird in die kompilierte Form geschrieben. Im Cache-Verzeichnis entstehen dadurch mehrere kompilierte Versionen des Templates, eine für jede Sprache. Dazu genügt es, die Sprache als zweiten Parameter anzugeben:
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator, $lang);
}
Statischer Text bedeutet zum Beispiel {_'hello'} oder {translate}hello{/translate}. Nicht statische
Texte wie {_$foo} werden weiterhin zur Laufzeit übersetzt.