Latte erweitern

Latte ist mit Blick auf Erweiterbarkeit entworfen. Sein Standardsatz an Tags, Filtern und Funktionen deckt zwar viele Anwendungsfälle ab, oft brauchen Sie aber eigene Logik oder eigene Helfer. Diese Seite gibt einen Überblick darüber, wie Sie Latte genau an die Anforderungen Ihres Projekts anpassen – von einfachen Helfern bis zu komplexer neuer Syntax.

Wege, Latte zu erweitern

Hier ein kurzer Überblick über die wichtigsten Wege, Latte anzupassen und zu erweitern:

  • Eigene Filter: Zum Formatieren oder Umwandeln von Daten direkt in der Ausgabe des Templates (z. B. {$var|myFilter}). Ideal für Aufgaben wie Datumsformatierung, Textbearbeitung oder das Anwenden eines bestimmten Escapings. Sie können damit auch größere Blöcke von HTML-Inhalt verändern, indem Sie den Inhalt in einen anonymen {block} hüllen und einen eigenen Filter anwenden.
  • Eigene Funktionen: Zum Ergänzen wiederverwendbarer Logik, die sich in Template-Ausdrücken aufrufen lässt (z. B. {myFunction($arg1, $arg2)}). Nützlich für Berechnungen, den Zugriff auf Helfer der Anwendung oder das Erzeugen kleiner Inhaltsstücke.
  • Eigene Tags: Zum Erstellen völlig neuer Sprachkonstrukte ({mytag}...{/mytag} oder n:mytag). Tags bieten die größte Macht: Sie können damit eigene Strukturen definieren, das Parsen des Templates steuern und komplexe Rendering-Logik umsetzen.
  • Compiler-Pässe: Funktionen, die den abstrakten Syntaxbaum (AST) des Templates nach dem Parsen, aber vor dem Erzeugen des PHP-Codes verändern. Eingesetzt für fortgeschrittene Optimierungen, Sicherheitsprüfungen (wie die Sandbox) oder automatische Codeänderungen.
  • Eigene Loader: Zum Ändern der Art, wie Latte Template-Dateien findet und lädt (z. B. Laden aus einer Datenbank, aus verschlüsseltem Speicher usw.).

Die Wahl des richtigen Erweiterungswegs ist entscheidend. Bevor Sie einen komplexen Tag erstellen, überlegen Sie, ob nicht ein einfacherer Filter oder eine Funktion genügt. Veranschaulichen wir das an einem Beispiel: der Umsetzung eines Lorem-ipsum-Generators, der die Anzahl der zu erzeugenden Wörter als Argument bekommt.

  • Als Tag? {lipsum 40} – möglich, aber Tags eignen sich besser für Kontrollstrukturen oder das Erzeugen komplexen Markups. Tags lassen sich nicht direkt in Ausdrücken verwenden.
  • Als Filter? {=40|lipsum} – funktioniert technisch, aber Filter sind dazu da, eine Eingabe zu transformieren. Hier ist 40 ein Argument, nicht der umzuwandelnde Wert. Das wirkt semantisch falsch.
  • Als Funktion? {lipsum(40)} – das passt am natürlichsten! Funktionen nehmen Argumente entgegen und geben Werte zurück, was sie für die Verwendung in jedem Ausdruck perfekt macht: {var $text = lipsum(40)}.

Allgemeine Empfehlung: Verwenden Sie Funktionen zum Rechnen und Erzeugen, Filter zum Umwandeln und Tags für neue Sprachstrukturen oder komplexes Markup. Pässe dienen der Manipulation des AST und Loader dem Beschaffen von Templates.

Direkte Registrierung

Für projektspezifische Helfer oder schnelle Ergänzungen erlaubt Latte die direkte Registrierung von Filtern und Funktionen am Objekt Latte\Engine.

Zum Registrieren eines Filters dient addFilter(). Das erste Argument Ihrer Filterfunktion ist der Wert vor der Pipe |, die weiteren Argumente sind die nach dem Doppelpunkt : übergebenen.

$latte = new Latte\Engine;

// Definition des Filters (Callable: Funktion, statische Methode usw.)
$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length);

// Registrierung
$latte->addFilter('truncate', $myTruncate);

// Verwendung im Template: {$text|truncate} oder {$text|truncate:100}

Zum Registrieren einer Funktion, die sich in Template-Ausdrücken verwenden lässt, dient addFunction().

$latte = new Latte\Engine;

// Definition der Funktion
$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6;

// Registrierung
$latte->addFunction('isWeekend', $isWeekend);

// Verwendung im Template: {if isWeekend($myDate)}Wochenende!{/if}

Mehr dazu unter Eigene Filter erstellen und Funktionen.

Der robuste Weg: Latte-Extension

Die direkte Registrierung ist einfach, der übliche und empfohlene Weg, Anpassungen von Latte zu bündeln und zu verteilen, sind jedoch Extension-Klassen. Eine Extension dient als zentraler Konfigurationspunkt für die Registrierung mehrerer Tags, Filter, Funktionen, Compiler-Pässe und weiterer Dinge.

Warum Extensions verwenden?

  • Organisation: Hält zusammengehörige Anpassungen (Tags, Filter usw. für eine bestimmte Funktion) in einer Klasse beisammen.
  • Wiederverwendbarkeit und Weitergabe: Sie können Ihre Erweiterungen leicht paketieren, um sie in anderen Projekten zu verwenden oder mit der Community zu teilen (etwa über Composer).
  • Volle Mächtigkeit: Eigene Tags und Compiler-Pässe lassen sich ausschließlich über Extensions registrieren.

Eine Extension registrieren

Eine Extension wird bei Latte mit addExtension() registriert (oder über die Konfigurationsdatei):

$latte = new Latte\Engine;
$latte->addExtension(new MyProjectExtension);

Registrieren Sie mehrere Extensions und definieren diese gleichnamige Tags, Filter oder Funktionen, gewinnt die zuletzt hinzugefügte Extension. Das bedeutet zugleich, dass Ihre Extensions native Tags, Filter und Funktionen überschreiben können.

Sobald Sie eine Klasse ändern und die automatische Aktualisierung nicht abgeschaltet ist, kompiliert Latte Ihre Templates automatisch neu.

Eine Extension erstellen

Um eine eigene Extension zu erstellen, legen Sie eine Klasse an, die von Latte\Extension erbt. Für eine Vorstellung davon, wie eine Extension aussieht, werfen Sie einen Blick auf die eingebaute CoreExtension.

Sehen wir uns an, welche Methoden Sie implementieren können:

beforeCompile (Latte\Engine $engine)void

Wird vor dem Kompilieren des Templates aufgerufen. Die Methode lässt sich zum Beispiel für Initialisierungen rund um die Kompilierung verwenden.

getTags(): array

Wird beim Kompilieren des Templates aufgerufen. Gibt ein assoziatives Array Name des Tags ⇒ Callable zurück, wobei die Callables die Parsing-Funktionen der Tags sind. Mehr dazu.

public function getTags(): array
{
	return [
		'foo' => FooNode::create(...),
		'bar' => BarNode::create(...),
		'n:baz' => NBazNode::create(...),
		// ...
	];
}

Der Tag n:baz stellt ein reines n:Attribut dar, also einen Tag, der sich nur als Attribut schreiben lässt.

Bei den Tags foo und bar erkennt Latte automatisch, ob es sich um Paar-Tags handelt, und wenn ja, lassen sie sich automatisch als n:Attribute schreiben, einschließlich der Varianten mit den Präfixen n:inner-foo und n:tag-foo.

Die Reihenfolge der Ausführung solcher n:Attribute ergibt sich aus ihrer Reihenfolge in dem von getTags() zurückgegebenen Array. n:foo wird also immer vor n:bar ausgeführt, selbst wenn die Attribute im HTML-Tag in umgekehrter Reihenfolge als <div n:bar="..." n:foo="..."> stehen.

Müssen Sie die Reihenfolge der n:Attribute über mehrere Extensions hinweg festlegen, verwenden Sie die Hilfsmethode order(), bei der der Parameter before bzw. after bestimmt, welche Tags vor oder nach dem Tag eingeordnet werden.

public function getTags(): array
{
	return [
		'foo' => self::order(FooNode::create(...), before: 'bar'),
		'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']),
	];
}

getPasses(): array

Wird beim Kompilieren des Templates aufgerufen. Gibt ein assoziatives Array Name des Passes ⇒ Callable zurück, wobei die Callables Funktionen sind, die die sogenannten Compiler-Pässe darstellen, die den AST durchlaufen und verändern.

Auch hier lässt sich die Hilfsmethode order() verwenden. Der Wert der Parameter before oder after kann * sein, mit der Bedeutung vor/nach allen.

public function getPasses(): array
{
	return [
		'optimize' => Passes::optimizePass(...),
		'sandbox' => self::order($this->sandboxPass(...), before: '*'),
		// ...
	];
}

beforeRender (Latte\Runtime\Template $template)void

Wird vor jedem Rendern eines Templates aufgerufen. Die Methode lässt sich zum Beispiel verwenden, um Variablen zu initialisieren, die beim Rendern gebraucht werden.

afterRender (Latte\Runtime\Template $template)void

Wird nach jedem Rendern eines Templates aufgerufen. Sie läuft auch dann, wenn das Rendern vorzeitig über {exitIf} endet oder durch eine Exception unterbrochen wird, und ist damit der richtige Ort zum Aufräumen oder Messen.

getFilters(): array

Wird bei der Registrierung der Extension mit der Methode addExtension() aufgerufen. Gibt die Filter als assoziatives Array Name des Filters ⇒ Callable zurück. Mehr dazu.

public function getFilters(): array
{
	return [
		'batch' => $this->batchFilter(...),
		'trim' => $this->trimFilter(...),
		// ...
	];
}

getFunctions(): array

Wird bei der Registrierung der Extension mit der Methode addExtension() aufgerufen. Gibt die Funktionen als assoziatives Array Name der Funktion ⇒ Callable zurück. Mehr dazu.

public function getFunctions(): array
{
	return [
		'clamp' => $this->clampFunction(...),
		'divisibleBy' => $this->divisibleByFunction(...),
		// ...
	];
}

getProviders(): array

Wird bei der Registrierung der Extension mit der Methode addExtension() aufgerufen. Gibt ein Array von Providern zurück, meist Objekte, die Tags zur Laufzeit verwenden. Der Zugriff erfolgt über $this->global->.... Mehr dazu.

public function getProviders(): array
{
	return [
		'myFoo' => $this->foo,
		'myBar' => $this->bar,
		// ...
	];
}

getCacheKey (Latte\Engine $engine)mixed

Wird vor dem Rendern des Templates aufgerufen. Der Rückgabewert wird Teil des Schlüssels, dessen Hash im Namen der Datei mit dem kompilierten Template enthalten ist. Bei unterschiedlichen Rückgabewerten erzeugt Latte also unterschiedliche Cache-Dateien.

Version: 3.x