Praktiken für Entwickler

Installation

Am besten installieren Sie Latte mit Composer:

composer require latte/latte

Unterstützte PHP-Versionen (gilt für die jeweils letzten Patch-Versionen von Latte):

Version kompatibel mit PHP
Latte 3.1 PHP 8.2 – 8.5
Latte 3.0 PHP 8.0 – 8.5

Wie rendert man ein Template?

Wie rendert man ein Template? Verwenden Sie einfach diesen schlichten Code:

$latte = new Latte\Engine;
// Cache-Verzeichnis
$latte->setCacheDirectory('/path/to/tempdir');

$params = [ /* Variablen des Templates */ ];
// oder $params = new TemplateParameters(/* ... */);

// in die Ausgabe rendern
$latte->render('template.latte', $params);
// oder in eine Variable rendern
$output = $latte->renderToString('template.latte', $params);

Die Parameter können Arrays sein oder besser noch ein Objekt, das Typprüfung und Vorschläge im Editor mitbringt.

Beispiele zur Verwendung finden Sie auch im Repository Latte examples.

Leistung und Caching

Latte-Templates sind ausgesprochen schnell, weil Latte sie direkt in PHP-Code kompiliert und auf der Festplatte cacht. Sie haben damit keinerlei zusätzlichen Overhead gegenüber Templates, die in reinem PHP geschrieben sind.

Der Cache wird bei jeder Änderung der Quelldatei automatisch neu erzeugt. Sie können Ihre Latte-Templates während der Entwicklung also bequem bearbeiten und die Änderungen sofort im Browser sehen. In der Produktionsumgebung lässt sich diese Funktion abschalten, um ein wenig Leistung zu sparen:

$latte->setAutoRefresh(false);

Beim Deployment auf einem Produktionsserver kann das erstmalige Erzeugen des Caches, besonders bei größeren Anwendungen, verständlicherweise eine Weile dauern. Latte hat eine eingebaute Vorbeugung gegen cache stampede. Das ist die Situation, in der beim Server eine große Zahl gleichzeitiger Requests eingeht und diese, weil der Cache von Latte noch nicht existiert, ihn alle zugleich erzeugen würden. Was die CPU in die Höhe treibt. Latte ist schlau, und bei mehreren gleichzeitigen Requests erzeugt nur der erste Thread den Cache, die übrigen warten und nutzen ihn dann.

Sie können den Cache auch beim Deployment vorab erzeugen (zum Beispiel in einem Deploy-Skript), und zwar mit der Methode Engine::warmupCache(). Sie kompiliert das angegebene Template im Voraus in den Cache, sodass der erste Besucher nicht warten muss: $latte->warmupCache('template.latte').

Wege, Latte zu erweitern

Latte lässt sich auf mehreren Wegen anpassen, von einfachen Helfern bis zu völlig neuen Sprachkonstrukten. Die Seite Latte erweitern behandelt sie ausführlich; hier ein kurzer Überblick:

  • Eigene Filter: zum Formatieren oder Umwandeln von Daten in der Ausgabe des Templates (z. B. {$var|myFilter}).
  • Eigene Funktionen: für eigene Logik, die Sie in Template-Ausdrücken aufrufen (z. B. {myFunction($arg)}).
  • Eigene Tags: für völlig neue Sprachkonstrukte ({mytag}...{/mytag} oder n:mytag).
  • Compiler-Pässe: Funktionen, die den AST des Templates zwischen dem Parsen und dem Erzeugen des PHP-Codes verändern (zum Beispiel Optimierungen oder Sicherheitsprüfungen).
  • Eigene Loader: zum Ändern der Art, wie Latte Template-Dateien findet und lädt.

Wollen Sie Ihre Erweiterungen über Projekte hinweg wiederverwenden oder mit anderen teilen, bündeln Sie sie in einer Klasse Latte-Extension.

Parameter als Klasse

Besser, als dem Template die Variablen als Array zu übergeben, ist es, eine Klasse zu erstellen. Sie bekommen typsichere Schreibweise, gute Vorschläge in der IDE und einen Weg, Filter und Funktionen zu registrieren.

class MailTemplateParameters
{
	public function __construct(
		public string $lang,
		public Address $address,
		public string $subject,
		public array $items,
		public ?float $price = null,
	) {}
}

$latte->render('mail.latte', new MailTemplateParameters(
	lang: $this->lang,
	subject: $title,
	price: $this->getPrice(),
	items: [],
	address: $userAddress,
));

Automatisches Escaping einer Variablen abschalten

Enthält eine Variable einen HTML-String, können Sie sie so kennzeichnen, dass Latte sie nicht automatisch (und damit doppelt) escapt. Damit müssen Sie im Template kein |noescape angeben.

Am einfachsten hüllen Sie den String in ein Objekt Latte\Runtime\Html:

$params = [
	'articleBody' => new Latte\Runtime\Html($article->htmlBody),
];

Latte escapt außerdem keine Objekte, die das Interface Latte\Runtime\HtmlStringable implementieren. Sie können also eine eigene Klasse erstellen, deren Methode __toString() HTML-Code zurückgibt, der nicht automatisch escapt wird:

class Emphasis implements Latte\Runtime\HtmlStringable
{
	public function __construct(
		private string $str,
	) {
	}

	public function __toString(): string
	{
		return '<em>' . htmlspecialchars($this->str) . '</em>';
	}
}

$params = [
	'foo' => new Emphasis('hello'),
];

Die Methode __toString muss korrektes HTML zurückgeben und für das Escaping der Parameter sorgen, sonst kann eine XSS-Sicherheitslücke entstehen!

Wie man Latte um Filter, Tags usw. erweitert

Wie fügt man Latte einen eigenen Filter, eine Funktion, einen Tag usw. hinzu? Das erfahren Sie im Kapitel Latte erweitern. Wollen Sie Ihre Änderungen in verschiedenen Projekten wiederverwenden oder mit anderen teilen, sollten Sie anschließend eine Extension erstellen.

Beliebiger Code im Template {php ...}

Innerhalb des Tags {do} lassen sich nur PHP-Ausdrücke schreiben, Sie können dort also zum Beispiel keine Konstrukte wie if ... else oder mit einem Semikolon abgeschlossene Anweisungen einfügen.

Sie können jedoch die Extension RawPhpExtension registrieren, die den Tag {php ...} hinzufügt. Damit lässt sich beliebiger PHP-Code einfügen. Er unterliegt keinen Regeln des Sandbox-Modus, die Verwendung liegt also in der Verantwortung des Template-Autors.

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

Prüfung des generierten Codes

Latte kompiliert Templates in PHP-Code. Es sorgt selbstverständlich dafür, dass der erzeugte Code syntaktisch gültig ist. Bei der Verwendung von Erweiterungen Dritter oder von RawPhpExtension kann Latte die Korrektheit der erzeugten Datei jedoch nicht garantieren. Außerdem lässt sich in PHP Code schreiben, der syntaktisch korrekt, aber verboten ist (zum Beispiel das Zuweisen eines Werts an die Variable $this) und einen PHP Compile Error verursacht. Schreiben Sie eine solche Operation in ein Template, landet sie auch im erzeugten PHP-Code. Da es in PHP über zweihundert verschiedene verbotene Operationen gibt, strebt Latte nicht an, sie zu erkennen. PHP selbst meldet sie beim Rendern, was üblicherweise kein Problem ist.

Es gibt jedoch Situationen, in denen Sie schon beim Kompilieren des Templates wissen wollen, dass es keine PHP Compile Errors enthält. Vor allem, wenn Templates von Benutzern bearbeitet werden können oder wenn Sie die Sandbox verwenden. Lassen Sie die Templates in einem solchen Fall beim Kompilieren prüfen. Diese Funktion aktivieren Sie mit der Methode Engine::enablePhpLinter(). Da sie für die Prüfung das PHP-Binary aufrufen muss, übergeben Sie dessen Pfad als Parameter:

$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');

try {
	$latte->compile('home.latte');
} catch (Latte\CompileException $e) {
	// fängt Fehler von Latte und auch Compile Error in PHP ab
	echo 'Error: ' . $e->getMessage();
}

Locale

Latte erlaubt es, das Locale zu setzen, was die Formatierung von Zahlen und Datumsangaben sowie die Sortierung beeinflusst. Gesetzt wird es mit der Methode setLocale(). Der Bezeichner des Locale folgt dem Standard IETF language tag, den die PHP-Erweiterung intl verwendet. Er besteht aus einem Sprachcode und gegebenenfalls einem Ländercode, zum Beispiel en_US für Englisch in den Vereinigten Staaten, de_DE für Deutsch in Deutschland usw.

$latte = new Latte\Engine;
$latte->setLocale('en_US');

Die Einstellung des Locale wirkt sich auf die Filter localDate, sort, number und bytes aus.

Erfordert die PHP-Erweiterung intl. Die Einstellung in Latte beeinflusst die globale Locale-Einstellung in PHP nicht.

Strikter Modus

Im strikten Parsing-Modus prüft Latte auf fehlende schließende HTML-Tags und verbietet außerdem die Verwendung der Variablen $this. Zum Einschalten:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::StrictParsing);

Um Templates mit dem Header declare(strict_types=1) zu erzeugen, gehen Sie so vor:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::StrictTypes);

Seit Latte 3.1 sind strikte Typen standardmäßig aktiviert. Sie können sie mit $latte->setFeature(Latte\Feature::StrictTypes, false) abschalten.

Migrationswarnungen

Latte 3.1 ändert das Verhalten einiger HTML-Attribute. So lassen null-Werte das Attribut nun entfallen, statt einen leeren String auszugeben. Um die Stellen leicht zu finden, an denen sich diese Änderung auf Ihre Templates auswirkt, können Sie Migrationswarnungen aktivieren:

$latte->setFeature(Latte\Feature::MigrationWarnings);

Ist das aktiviert, prüft Latte die gerenderten Attribute und löst eine Benutzerwarnung (E_USER_WARNING) aus, wenn sich die Ausgabe von der unterscheidet, die Latte 3.0 erzeugt hätte. Wenn Sie auf eine Warnung stoßen, wenden Sie eine der Lösungen an:

  1. Ist die neue Ausgabe für Ihren Anwendungsfall richtig (Sie ziehen es z. B. vor, dass das Attribut bei null verschwindet), unterdrücken Sie die Warnung durch Ergänzen des Filters |accept
  2. Wollen Sie, dass das Attribut als leer gerendert wird (z. B. title=""), statt bei null zu entfallen, geben Sie einen leeren String als Rückfall an: title={$val ?? ''}
  3. Brauchen Sie unbedingt das alte Verhalten (z. B. die Ausgabe "1" für true statt "true"), wandeln Sie den Wert ausdrücklich in einen String um: data-foo={(string) $val}

Sind alle Warnungen aufgelöst, schalten Sie die Migrationswarnungen ab und entfernen Sie alle |accept-Filter aus Ihren Templates, denn sie werden nicht mehr gebraucht.

Gültigkeitsbereich der Schleifenvariablen

Standardmäßig bleiben Variablen, die in einer {foreach}-Schleife definiert werden (etwa $key und $value), auch nach dem Ende der Schleife zugänglich – genau wie in PHP selbst. Das kann zum ungewollten Überschreiben von Variablen führen, wenn eine Schleifenvariable denselben Namen hat wie eine bestehende Template-Variable.

Die Funktion ScopedLoopVariables beschränkt den Gültigkeitsbereich der Schleifenvariablen auf den Schleifenkörper. Nach dem Ende der Schleife wird der ursprüngliche Wert der Variablen wiederhergestellt (sofern sie vorher existierte) oder die Variable aufgehoben:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::ScopedLoopVariables);

Beispiel für den Unterschied:

{var $item = 'original'}
{foreach [1, 2] as $item}{$item}, {/foreach}
{$item}

Ohne ScopedLoopVariables: gibt 1, 2, 2 aus (die Variable wird überschrieben) Mit ScopedLoopVariables: gibt 1, 2, original aus (die Variable wird wiederhergestellt)

Das funktioniert auch mit Destructuring-Syntax, z. B. {foreach $array as [$a, $b]}.

Schleifenvariablen, die Referenzen verwenden ({foreach $array as &$value}) oder Properties zuweisen ({foreach $array as $obj->prop}), werden nicht auf den Schleifenkörper beschränkt, weil das ihren Zweck zunichtemachen würde.

Automatisches Entfernen der Einrückung

Bei der Verwendung von Paar-Tags wie {if}, {foreach} oder {block} rücken Sie den verschachtelten Inhalt der Lesbarkeit halber oft ein. Diese Einrückung landet standardmäßig jedoch in der erzeugten Ausgabe. Die Funktion Dedent entfernt sie automatisch, sodass die Ausgabe sauber bleibt, egal wie tief Sie Ihre Latte-Tags verschachteln:

$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::Dedent);

Beispiel:

{if true}
	Hello
	World
{/if}

Ohne Dedent enthielte die Ausgabe die Einrückung (\tHello\n\tWorld\n). Mit Dedent wird die Einrückung entfernt und die Ausgabe lautet Hello\nWorld\n.

Eine tiefere Einrückung innerhalb eines Blocks bleibt relativ zur Grundeinrückung erhalten:

{if true}
	Hello
		Indented
{/if}

Ausgabe: Hello\n\tIndented\n.

Die Einrückung innerhalb eines Blocks muss einheitlich sein (entweder Tabulatoren oder Leerzeichen). Werden sie gemischt, wirft Latte eine Exception Inconsistent indentation.

Übersetzen in Templates

Verwenden Sie die Extension TranslatorExtension, um dem Template {_...}, {translate} und den Filter translate hinzuzufügen. Sie dienen dazu, Werte oder Teile des Templates in andere Sprachen zu übersetzen. Der Parameter ist das Callable, das die Übersetzung durchführt, oder ein Objekt vom Typ Nette\Localization\Translator (übergeben Sie null, um Übersetzungen abzuschalten):

class MyTranslator
{
	public function __construct(private string $lang)
	{}

	public function translate(string $original): string
	{
		// $translated aus $original gemäß $this->lang erzeugen
		return $translated;
	}
}

$translator = new MyTranslator($lang);
$extension = new Latte\Essential\TranslatorExtension(
	$translator->translate(...), // [$translator, 'translate'] in PHP 8.0
);
$latte->addExtension($extension);

Der Translator wird zur Laufzeit beim Rendern des Templates aufgerufen. Latte kann jedoch alle statischen Texte 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 Datei geschrieben. Im Cache-Verzeichnis entstehen dadurch mehrere kompilierte Versionen des Templates, eine für jede Sprache. Dazu müssen Sie lediglich die Sprache als zweiten Parameter angeben:

$extension = new Latte\Essential\TranslatorExtension(
	$translator->translate(...),
	$lang,
);

Mit statischem Text meinen wir zum Beispiel {_'hello'} oder {translate}hello{/translate}. Nicht statischer Text wie {_$foo} wird weiterhin zur Laufzeit übersetzt.

Das Template kann dem Translator über {_$original, foo: bar} oder {translate foo: bar} auch weitere Parameter übergeben, die er als Array $params erhält:

public function translate(string $original, ...$params): string
{
	// $params['foo'] === 'bar'
}

Debugging und Tracy

Latte versucht, die Entwicklung so angenehm wie möglich zu machen. Zum Debuggen gibt es die drei Tags {dump}, {debugbreak} und {trace}.

Am bequemsten wird es, wenn Sie das großartige Debugging-Tool Tracy installieren und das Latte-Plugin aktivieren:

// aktiviert Tracy
Tracy\Debugger::enable();

$latte = new Latte\Engine;
// aktiviert die Extension von Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);

Sie sehen nun alle Fehler auf einer übersichtlichen roten Seite, einschließlich der Fehler in Templates mit Hervorhebung von Zeile und Spalte (Video). Zugleich erscheint unten rechts in der sogenannten Tracy Bar ein Reiter für Latte, in dem Sie übersichtlich alle gerenderten Templates und ihre Beziehungen sehen (samt der Möglichkeit, in das Template oder den kompilierten Code zu klicken) sowie die Variablen:

Da Latte Templates in lesbaren PHP-Code kompiliert, können Sie sie in Ihrer IDE bequem durchsteppen.

Linter: Prüfung der Syntax von Templates

Das Werkzeug Linter dient dazu, alle Templates zu prüfen. Sein Zweck ist es, die angegebenen Dateien zu durchsuchen und sicherzustellen, dass sie keine Syntaxfehler und keine Verweise auf nicht existierende Tags, Filter, Funktionen, Klassen oder ähnliche Konstrukte enthalten.

Der Linter wird von der Kommandozeile aus ausgeführt:

vendor/bin/latte-lint <path>

Mit dem Parameter --strict aktivieren Sie den Strikter Modus. Der Parameter --debug gibt den Namen jeder verarbeiteten Datei und vollständige Details der Exception aus, was bei der Fehlersuche hilft.

Wenn Sie eigene Tags, Filter oder andere Latte-Extensions verwenden, müssen Sie eine eigene Variante des Linters erstellen, zum Beispiel custom-latte-lint. In diesem Skript registrieren Sie alle nötigen Extensions, bevor die eigentliche Prüfung der Templates stattfindet:

#!/usr/bin/env php
<?php

// hier den tatsächlichen Pfad zur Datei autoload.php eintragen
require __DIR__ . '/vendor/autoload.php';

$path = $argv[1] ?? '.';

$linter = new Latte\Tools\Linter;
$latte = $linter->getEngine();
// hier Ihre einzelnen Extensions hinzufügen
$latte->addExtension(/* ... */);

$ok = $linter->scanDirectory($path);
exit($ok ? 0 : 1);

Alternativ können Sie dem Linter Ihr eigenes Objekt Latte\Engine übergeben:

$latte = new Latte\Engine;
// hier konfigurieren wir das Objekt $latte
$linter = new Latte\Tools\Linter(engine: $latte);

Der so angepasste Linter lässt sich dann genauso verwenden wie das Standardwerkzeug, nur mit vollständiger Kenntnis all Ihrer eigenen Extensions.

Templates aus einem String laden

Müssen Sie Templates statt aus Dateien aus Strings laden, etwa zu Testzwecken? Dabei hilft Ihnen der StringLoader:

$latte->setLoader(new Latte\Loaders\StringLoader([
	'main.file' => '{include other.file}',
	'other.file' => '{if true} {$var} {/if}',
]));

$latte->render('main.file', $params);

Exception-Handler

Sie können einen eigenen Handler für erwartete Exceptions definieren. An ihn werden Exceptions übergeben, die innerhalb von {try} und in der Sandbox entstehen.

$loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) {
	$logger->log($e);
};

$latte = new Latte\Engine;
$latte->setExceptionHandler($loggingHandler);

Automatische Suche nach dem Layout

Mit dem Tag {layout} bestimmt das Template sein Eltern-Template. Es ist auch möglich, das Layout automatisch suchen zu lassen, was das Schreiben von Templates vereinfacht, weil sie den Tag {layout} nicht mehr enthalten müssen.

Das erreichen Sie so:

// gibt den Pfad zur Datei des Eltern-Templates zurück
$finder = fn(Latte\Runtime\Template $template) => 'automatic.layout.latte';
$latte = new Latte\Engine;
$latte->addProvider('coreParentFinder', $finder);

Soll ein Template kein Layout haben, zeigt es das mit dem Tag {layout none} an.

Version: 3.x