Nette Documentation Preview

syntax
Migration von Latte 2 auf 3
***************************

.[perex]
Latte 3 hat einen komplett neu geschriebenen Compiler und eine formal genau definierte Grammatik. Diese sollte Latte 2 so weit wie möglich entsprechen, es gibt aber einige Konstrukte, die eine kleine Anpassung brauchen.

In der Praxis zeigt sich, dass die allermeisten Templates keinerlei Änderung benötigen und in Latte 2 genauso funktionieren wie in Latte 3. Wie aber findet man Inkompatibilitäten?

**Installieren Sie zuerst die Übergangsversion Latte 2.11.**

Diese Version bringt keine neuen Funktionen, sie warnt lediglich per E_USER_DEPRECATED bei Fällen, von denen sie weiß, dass das neue Latte sie nicht unterstützt, und rät Ihnen vor allem, wie Sie sie beheben. Um alle Templates durchzugehen und zu prüfen, ob sie kompatibel sind, können Sie das Werkzeug [Linter |/develop#Linter] verwenden, das Sie aus der Konsole starten:

```shell
vendor/bin/latte-lint <path>
```

Sobald Sie mögliche Inkompatibilitäten behoben haben, steigen Sie auf Latte 3.0 um. **Und starten Sie den Linter erneut**, um sicherzugehen, dass der neue strikte Parser wirklich alle Templates versteht.


Änderungen an der API
=====================

Die Änderungen an der API betreffen nur das Hinzufügen eigener Tags. Der Rest der API bleibt gegenüber Version 2 gleich, also dieselbe Art, Templates zu rendern, Parameter zu übergeben und Filter zu registrieren.

Eine Ausnahme ist der sogenannte dynamische Filter `Engine::addFilter(null, ...)`, um den sich nun [über eine Klasse registrierte Filter |/custom-filters#Filter über eine Klasse] mit der Methode `addFilter()` kümmern. Die ursprüngliche Methode `Engine::addFilterLoader()` existiert als Übergangslösung weiterhin, ist aber veraltet.

Die API zum Hinzufügen eigener Tags ist völlig anders, für Latte 2 entworfene Erweiterungen funktionieren damit also nicht. Siehe auch [#Erweiterungen aktualisieren].


Änderungen an der Syntax
========================

Die Änderungen sind folgende:

- Filter verwenden das Komma als Trennzeichen für Parameter, aus dem bisherigen `|filter: arg : arg` wird `|filter: arg, arg`
- der Tag `{label foo}...{/label}` ist immer ein Paar-Tag, unpaarig schreibt man ihn `{label /}`
- umgekehrt ist der Tag `{_'text'}` immer unpaarig, das paarige `{_}...{/}` ersetzt das neue `{translate}...{/translate}`
- Pseudo-Strings wie `{block foo-$var}` müssen in Anführungszeichen `{block "foo-$var"}` geschrieben oder um geschweifte Klammern ergänzt werden `{block foo-{$var}}`
- das gilt auch für Attribute, statt `n:block="foo-$var"` also `n:block="foo-{$var}"`
- bei Filtern muss in Latte 3 auf die Groß-/Kleinschreibung geachtet werden
- der Tag `{do ...}` bzw. `{php ...}` darf nur Ausdrücke enthalten; um beliebiges PHP zu verwenden, registrieren Sie [RawPhpExtension |/develop#RawPhpExtension]

Und weitere Randfälle:

- die Attribute `n:inner-xxx`, `n:tag-xxx` und `n:ifcontent` lassen sich nicht auf leeren HTML-Elementen verwenden
- das Attribut `n:inner-snippet` muss ohne inner- geschrieben werden
- die Tags `</script>` und `</style>` müssen beendet werden
- die magische Variable `$iterations` wurde entfernt (nicht mit `$iterator` zu verwechseln!)
- ersetzen Sie den Tag `{includeblock file.latte}` durch [`{include file.latte with blocks}` |/tags#include] oder [`{import}` |/template-inheritance#Horizontale Wiederverwendung]
- `{include "abc"}` sollte als `{include file "abc"}` geschrieben werden, sofern `"abc"` keinen Punkt enthält und damit klar ist, dass es sich um eine Datei handelt


Erweiterungen aktualisieren
===========================

Mit dem vollständigen Neuschreiben des Parsers hat sich die Art, eigene Tags zu schreiben, komplett geändert. Haben Sie eigene Tags für Latte erstellt, müssen Sie sie für Version 3 neu schreiben, siehe [Dokumentation|/custom-tags].

Verwenden Sie eine fremde Erweiterung, die Tags hinzufügt, müssen Sie warten, bis der Autor eine Version für Latte 3 veröffentlicht. Die Bibliotheken `nette/application`, `nette/caching` und `nette/forms` in Version 3.1 sowie Texy sind bereits aktualisiert und funktionieren sowohl mit Latte 2 als auch mit Latte 3.


nette/application
-----------------

.[note]
Bei gewöhnlicher Verwendung von Nette wird diese Extension automatisch gesetzt und es ist nichts zu ändern.

Alter Code für Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};

$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
```

Neuer Code für Latte 3:

```php
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
```

Die UIExtension fügt `n:href`, `{link}`, `{control}`, `{snippet}` usw. hinzu. Die Tags für Snippets wandern damit von Latte selbst in die Bibliothek `nette/application`. In Latte 3 wird die Presenter-Methode `templatePrepareFilters()` nicht mehr aufgerufen.


nette/forms
-----------

.[note]
Bei gewöhnlicher Verwendung von Nette wird diese Extension automatisch gesetzt und es ist nichts zu ändern.

Alter Code für Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
```

Neuer Code für Latte 3:

```php
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
```


nette/caching
-------------

.[note]
Bei gewöhnlicher Verwendung von Nette wird diese Extension automatisch gesetzt und es ist nichts zu ändern.

Alter Code für Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};

$latte->addProvider('cacheStorage', $cacheStorage);
```

Neuer Code für Latte 3:

```php
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
```


Tracy
-----

Das Panel für Tracy wird nun ebenfalls als Extension aktiviert.

Alter Code für Latte 2:

```php
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
```

Neuer Code für Latte 3:

```php
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
```


Übersetzungen
-------------

Die TranslatorExtension fügt die Übersetzungstags `{_'text'}`, das neue Paar `{translate}...{/translate}` und den Filter `|translate` hinzu.

Alter Code für Latte 2:

```php
$latte->addFilter('translate', [$translator, 'translate']);
```

Neuer Code für Latte 3:

```php
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
```

In Presentern wird sie automatisch aktiviert, indem dem Template mit der Methode `$template->setTranslator($translator)` der Translator gesetzt wird. Ohne das stehen die Übersetzungstags nicht zur Verfügung und Sie müssen die Extension von Hand oder über eine Konfigurationsdatei registrieren.


Konfigurationsdatei
===================

In Latte 2 ließen sich neue Tags über die [Konfigurationsdatei |application:configuration#Latte-Templates] im Abschnitt `latte › macros` registrieren. In Version 3 werden auf diese Weise ganze Extensions hinzugefügt:

```neon
latte:
	extensions:
		- App\Templating\LatteExtension
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```


Entwickeln Sie eine Erweiterung für Latte?
==========================================

Sie können in Ihrer Bibliothek beide Versionen von Latte zugleich unterstützen. Zum Erkennen der Version verwenden Sie am besten die Konstante `Latte\Engine::VERSION`, um die Verwendung von `onCompile[]` und `addMacro()` vom neuen `addExtension()` zu trennen:

```php
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
	// Initialisierung für Latte 2
	$this->latte->onCompile[] = function ($latte) {
		$latte->addMacro(/* ... */);
	};
} else {
	// Initialisierung für Latte 3
	$this->latte->addExtension(/* ... */);
}
```

Versuchen wir als Beispiel, den folgenden für Latte 2 gedachten Code in eine Form für Latte 3 umzuschreiben:

```php
// alter Code für Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
	$set = new Latte\Macros\MacroSet($latte->getCompiler());
	$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};
```

Latte 3 wird über [Extensions|/extending-latte] erweitert. Eine triviale Extension, die den Tag `foo` hinzufügt, sähe so aus:

```php
// neuer Code für Latte 3
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // die Klasse FooNode ergänzen wir gleich
		];
	}
}

// Registrierung
$this->latte->addExtension(new FooExtension);
```

Der neue Compiler ist robuster, er kennt die bisherigen Abkürzungen nicht mehr, deshalb braucht das Schreiben eines Makros ein paar Zeilen Code mehr. Zum Beispiel können wir keinen String mit PHP-Code direkt übergeben wie in Latte 2, stattdessen erstellen wir eine Funktion. Zur Erinnerung: In Latte 2 sähe die Funktion ungefähr so aus:

```php
// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
	return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});
```

Latte 3 geht im Grunde genauso vor, nur heißt `MacroNode` nun `Latte\Compiler\Tag` und `PhpWriter` heißt `Latte\Compiler\PrintContext`. Vor allem aber gibt es einen zusätzlichen Zwischenschritt: Die Funktion gibt nicht direkt PHP-Code zurück, sondern einen Knoten, also einen Nachfahren von `StatementNode`, der dann Teil des AST-Baums ist. Und dieser Knoten hat eine Methode `print(Latte\Compiler\PrintContext $context): string`, die den PHP-Code zurückgibt:

```php
// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format('echo ...'); // gibt PHP-Code zurück
	}
}
```

Außerdem kennt die Maske in `$context->format()` die Abkürzungen `%node.***` nicht mehr, es wird vorausgesetzt, dass Sie zuerst den [Inhalt des Tags parsen |/custom-tags#Tag-Parsing-Funktion]. Wir verwenden also den Parser, um den Inhalt in Variablen (Unterknoten) zu zerlegen, und geben ihn dann aus:

```php
use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public ExpressionNode $subject;
	public ArrayNode $args;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		// Parsen des Inhalts des Tags
		$node->subject = $tag->parser->parseUnquotedStringOrExpression();
		$tag->parser->stream->tryConsume(',');
		$node->args = $tag->parser->parseArguments();
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format(
			'echo %escape(MyClass:myFunc(%node, %node));',
			$this->subject,
			$this->args,
		);
	}
}
```

Zum Schluss ergänzen wir die Methode `getIterator()`, damit sich die Unterknoten beim [Durchlaufen |/custom-tags#getIterator() für Unterknoten implementieren] durchlaufen lassen:

```php
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	// ...

	public function &getIterator(): \Generator
	{
		yield $this->subject;
		yield $this->args;
	}
}
```

{{priority: -1}}

Migration von Latte 2 auf 3

Latte 3 hat einen komplett neu geschriebenen Compiler und eine formal genau definierte Grammatik. Diese sollte Latte 2 so weit wie möglich entsprechen, es gibt aber einige Konstrukte, die eine kleine Anpassung brauchen.

In der Praxis zeigt sich, dass die allermeisten Templates keinerlei Änderung benötigen und in Latte 2 genauso funktionieren wie in Latte 3. Wie aber findet man Inkompatibilitäten?

Installieren Sie zuerst die Übergangsversion Latte 2.11.

Diese Version bringt keine neuen Funktionen, sie warnt lediglich per E_USER_DEPRECATED bei Fällen, von denen sie weiß, dass das neue Latte sie nicht unterstützt, und rät Ihnen vor allem, wie Sie sie beheben. Um alle Templates durchzugehen und zu prüfen, ob sie kompatibel sind, können Sie das Werkzeug Linter verwenden, das Sie aus der Konsole starten:

vendor/bin/latte-lint <path>

Sobald Sie mögliche Inkompatibilitäten behoben haben, steigen Sie auf Latte 3.0 um. Und starten Sie den Linter erneut, um sicherzugehen, dass der neue strikte Parser wirklich alle Templates versteht.

Änderungen an der API

Die Änderungen an der API betreffen nur das Hinzufügen eigener Tags. Der Rest der API bleibt gegenüber Version 2 gleich, also dieselbe Art, Templates zu rendern, Parameter zu übergeben und Filter zu registrieren.

Eine Ausnahme ist der sogenannte dynamische Filter Engine::addFilter(null, ...), um den sich nun über eine Klasse registrierte Filter mit der Methode addFilter() kümmern. Die ursprüngliche Methode Engine::addFilterLoader() existiert als Übergangslösung weiterhin, ist aber veraltet.

Die API zum Hinzufügen eigener Tags ist völlig anders, für Latte 2 entworfene Erweiterungen funktionieren damit also nicht. Siehe auch Erweiterungen aktualisieren.

Änderungen an der Syntax

Die Änderungen sind folgende:

  • Filter verwenden das Komma als Trennzeichen für Parameter, aus dem bisherigen |filter: arg : arg wird |filter: arg, arg
  • der Tag {label foo}...{/label} ist immer ein Paar-Tag, unpaarig schreibt man ihn {label /}
  • umgekehrt ist der Tag {_'text'} immer unpaarig, das paarige {_}...{/} ersetzt das neue {translate}...{/translate}
  • Pseudo-Strings wie {block foo-$var} müssen in Anführungszeichen {block "foo-$var"} geschrieben oder um geschweifte Klammern ergänzt werden {block foo-{$var}}
  • das gilt auch für Attribute, statt n:block="foo-$var" also n:block="foo-{$var}"
  • bei Filtern muss in Latte 3 auf die Groß-/Kleinschreibung geachtet werden
  • der Tag {do ...} bzw. {php ...} darf nur Ausdrücke enthalten; um beliebiges PHP zu verwenden, registrieren Sie RawPhpExtension

Und weitere Randfälle:

  • die Attribute n:inner-xxx, n:tag-xxx und n:ifcontent lassen sich nicht auf leeren HTML-Elementen verwenden
  • das Attribut n:inner-snippet muss ohne inner- geschrieben werden
  • die Tags </script> und </style> müssen beendet werden
  • die magische Variable $iterations wurde entfernt (nicht mit $iterator zu verwechseln!)
  • ersetzen Sie den Tag {includeblock file.latte} durch {include file.latte with blocks} oder {import}
  • {include "abc"} sollte als {include file "abc"} geschrieben werden, sofern "abc" keinen Punkt enthält und damit klar ist, dass es sich um eine Datei handelt

Erweiterungen aktualisieren

Mit dem vollständigen Neuschreiben des Parsers hat sich die Art, eigene Tags zu schreiben, komplett geändert. Haben Sie eigene Tags für Latte erstellt, müssen Sie sie für Version 3 neu schreiben, siehe Dokumentation.

Verwenden Sie eine fremde Erweiterung, die Tags hinzufügt, müssen Sie warten, bis der Autor eine Version für Latte 3 veröffentlicht. Die Bibliotheken nette/application, nette/caching und nette/forms in Version 3.1 sowie Texy sind bereits aktualisiert und funktionieren sowohl mit Latte 2 als auch mit Latte 3.

nette/application

Bei gewöhnlicher Verwendung von Nette wird diese Extension automatisch gesetzt und es ist nichts zu ändern.

Alter Code für Latte 2:

$latte->onCompile[] = function ($latte) {
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};

$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());

Neuer Code für Latte 3:

$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));

Die UIExtension fügt n:href, {link}, {control}, {snippet} usw. hinzu. Die Tags für Snippets wandern damit von Latte selbst in die Bibliothek nette/application. In Latte 3 wird die Presenter-Methode templatePrepareFilters() nicht mehr aufgerufen.

nette/forms

Bei gewöhnlicher Verwendung von Nette wird diese Extension automatisch gesetzt und es ist nichts zu ändern.

Alter Code für Latte 2:

$latte->onCompile[] = function ($latte) {
	Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};

Neuer Code für Latte 3:

$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);

nette/caching

Bei gewöhnlicher Verwendung von Nette wird diese Extension automatisch gesetzt und es ist nichts zu ändern.

Alter Code für Latte 2:

$latte->onCompile[] = function ($latte) {
	$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};

$latte->addProvider('cacheStorage', $cacheStorage);

Neuer Code für Latte 3:

$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));

Tracy

Das Panel für Tracy wird nun ebenfalls als Extension aktiviert.

Alter Code für Latte 2:

$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);

Neuer Code für Latte 3:

$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);

Übersetzungen

Die TranslatorExtension fügt die Übersetzungstags {_'text'}, das neue Paar {translate}...{/translate} und den Filter |translate hinzu.

Alter Code für Latte 2:

$latte->addFilter('translate', [$translator, 'translate']);

Neuer Code für Latte 3:

$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));

In Presentern wird sie automatisch aktiviert, indem dem Template mit der Methode $template->setTranslator($translator) der Translator gesetzt wird. Ohne das stehen die Übersetzungstags nicht zur Verfügung und Sie müssen die Extension von Hand oder über eine Konfigurationsdatei registrieren.

Konfigurationsdatei

In Latte 2 ließen sich neue Tags über die Konfigurationsdatei im Abschnitt latte › macros registrieren. In Version 3 werden auf diese Weise ganze Extensions hinzugefügt:

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

Entwickeln Sie eine Erweiterung für Latte?

Sie können in Ihrer Bibliothek beide Versionen von Latte zugleich unterstützen. Zum Erkennen der Version verwenden Sie am besten die Konstante Latte\Engine::VERSION, um die Verwendung von onCompile[] und addMacro() vom neuen addExtension() zu trennen:

if (version_compare(Latte\Engine::VERSION, '3', '<')) {
	// Initialisierung für Latte 2
	$this->latte->onCompile[] = function ($latte) {
		$latte->addMacro(/* ... */);
	};
} else {
	// Initialisierung für Latte 3
	$this->latte->addExtension(/* ... */);
}

Versuchen wir als Beispiel, den folgenden für Latte 2 gedachten Code in eine Form für Latte 3 umzuschreiben:

// alter Code für Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
	$set = new Latte\Macros\MacroSet($latte->getCompiler());
	$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};

Latte 3 wird über Extensions erweitert. Eine triviale Extension, die den Tag foo hinzufügt, sähe so aus:

// neuer Code für Latte 3
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // die Klasse FooNode ergänzen wir gleich
		];
	}
}

// Registrierung
$this->latte->addExtension(new FooExtension);

Der neue Compiler ist robuster, er kennt die bisherigen Abkürzungen nicht mehr, deshalb braucht das Schreiben eines Makros ein paar Zeilen Code mehr. Zum Beispiel können wir keinen String mit PHP-Code direkt übergeben wie in Latte 2, stattdessen erstellen wir eine Funktion. Zur Erinnerung: In Latte 2 sähe die Funktion ungefähr so aus:

// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
	return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});

Latte 3 geht im Grunde genauso vor, nur heißt MacroNode nun Latte\Compiler\Tag und PhpWriter heißt Latte\Compiler\PrintContext. Vor allem aber gibt es einen zusätzlichen Zwischenschritt: Die Funktion gibt nicht direkt PHP-Code zurück, sondern einen Knoten, also einen Nachfahren von StatementNode, der dann Teil des AST-Baums ist. Und dieser Knoten hat eine Methode print(Latte\Compiler\PrintContext $context): string, die den PHP-Code zurückgibt:

// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format('echo ...'); // gibt PHP-Code zurück
	}
}

Außerdem kennt die Maske in $context->format() die Abkürzungen %node.*** nicht mehr, es wird vorausgesetzt, dass Sie zuerst den Inhalt des Tags parsen. Wir verwenden also den Parser, um den Inhalt in Variablen (Unterknoten) zu zerlegen, und geben ihn dann aus:

use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public ExpressionNode $subject;
	public ArrayNode $args;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		// Parsen des Inhalts des Tags
		$node->subject = $tag->parser->parseUnquotedStringOrExpression();
		$tag->parser->stream->tryConsume(',');
		$node->args = $tag->parser->parseArguments();
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format(
			'echo %escape(MyClass:myFunc(%node, %node));',
			$this->subject,
			$this->args,
		);
	}
}

Zum Schluss ergänzen wir die Methode getIterator(), damit sich die Unterknoten beim Durchlaufen durchlaufen lassen:

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	// ...

	public function &getIterator(): \Generator
	{
		yield $this->subject;
		yield $this->args;
	}
}