Nette Documentation Preview

syntax
Templates
*********

.[perex]
Nette verwendet das Templating-System [Latte |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}`:

```latte
<!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:

```latte
{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`:

/--pre
app/
└── Presentation/
    └── Home/
        ├── HomePresenter.php
        └── <b>default.latte</b>
\--

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`:

/--pre
app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── <b>Home/</b>
        │   └── <b>default.latte</b>   ← 1. Variante
        └── <b>Home.default.latte</b>  ← 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 |presenters#Fehler 404 usw.].

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.

.[note]
Die Dateien, in denen nach Templates gesucht wird, lassen sich durch Überschreiben der Methode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::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:

/--pre
app/
└── Presentation/
    ├── <b>@layout.latte</b>           ← gemeinsames Layout
    └── Home/
        ├── <b>@layout.latte</b>       ← 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:

/--pre
app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── <b>@layout.latte</b>       ← gemeinsames Layout
        ├── <b>Home/</b>
        │   └── <b>@layout.latte</b>   ← nur für Home, 1. Variante
        └── <b>Home.@layout.latte</b>  ← 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.

.[note]
Die Dateien, in denen nach Layout-Templates gesucht wird, lassen sich durch Überschreiben der Methode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::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:

```php
$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: .{data-version:3.2.9}

```php
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:

- `$basePath` ist der absolute URL-Pfad zum Wurzelverzeichnis (z. B. `/eshop`)
- `$baseUrl` ist die absolute URL zum Wurzelverzeichnis (z. B. `http://localhost/eshop`)
- `$user` ist ein Objekt, das [den Benutzer repräsentiert |security:authentication]
- `$presenter` ist der aktuelle Presenter
- `$control` ist die aktuelle Komponente oder der Presenter
- `$flashes` ist ein Array von [Meldungen |presenters#Flash-Meldungen], die mit der Funktion `flashMessage()` 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:

```php
/**
 * @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":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template.

[* phpstorm-completion.webp *]

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 |latte:type-system]:

```latte
{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()`:

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

.{data-version:3.3.0}
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:

```php
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:

```latte
<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}`:

```latte
Die URL lautet: {link Home:default}
```

Mehr dazu finden Sie im Kapitel [Erstellen von URL-Links|creating-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>()`:

```php
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:

```latte
<p>Preis: {$price|money}</p>

{if isWeekend($now)} ... {/if}
```

Für komplexere Logik können Sie das Objekt `Latte\Engine` direkt konfigurieren:

```php
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|#Typsichere Templates] des Presenters oder der Komponente zu definieren und mit Attributen zu kennzeichnen:

```php
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 |latte:extending-latte#Latte Extension]. Diese Klasse bündelt alle Latte-Erweiterungen Ihres Projekts an einer Stelle. Ein kurzes Beispiel:

```php
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 |configuration#Latte-Templates]:

```neon
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 |dependency-injection:passing-dependencies] und registrieren Sie die Callbacks, z. B. beim Start der Anwendung:

```php
$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 [api: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 |https://componette.org/search/localization] 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 |dependency-injection:passing-dependencies], und zwar mit der Methode `setTranslator()`:

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

Alternativ lässt sich der Translator über die [Konfiguration |configuration#Latte-Templates] setzen:

```neon
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`):

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

Oder als Unterstrich-Tag:

```latte
<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):

```latte
<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:

```php
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.

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:

  • $basePath ist der absolute URL-Pfad zum Wurzelverzeichnis (z. B. /eshop)
  • $baseUrl ist die absolute URL zum Wurzelverzeichnis (z. B. http://localhost/eshop)
  • $user ist ein Objekt, das den Benutzer repräsentiert
  • $presenter ist der aktuelle Presenter
  • $control ist die aktuelle Komponente oder der Presenter
  • $flashes ist ein Array von Meldungen, die mit der Funktion flashMessage() 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';
}

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.