Pratiques pour les développeurs

Installation

La meilleure façon d'installer Latte est via Composer :

composer require latte/latte

Versions de PHP prises en charge (vaut pour les dernières versions correctives de Latte) :

version compatible avec PHP
Latte 3.1 PHP 8.2 – 8.5
Latte 3.0 PHP 8.0 – 8.5

Comment rendre un template

Comment rendre un template ? Ce code tout simple suffit :

$latte = new Latte\Engine;
// répertoire du cache
$latte->setCacheDirectory('/path/to/tempdir');

$params = [ /* variables du template */ ];
// ou $params = new TemplateParameters(/* ... */);

// rendu vers la sortie
$latte->render('template.latte', $params);
// ou rendu dans une variable
$output = $latte->renderToString('template.latte', $params);

Les paramètres peuvent être des tableaux ou, mieux encore, un objet, qui apportera le contrôle de types et la complétion dans l'éditeur.

Vous trouverez également des exemples d'utilisation dans le dépôt Latte examples.

Performance et cache

Les templates Latte sont extrêmement rapides, car Latte les compile directement en code PHP et les met en cache sur le disque. Ils n'ont donc aucun surcoût par rapport à des templates écrits en PHP pur.

Le cache est automatiquement régénéré chaque fois que vous modifiez le fichier source. Vous pouvez ainsi éditer confortablement vos templates Latte pendant le développement et voir les changements immédiatement dans le navigateur. Vous pouvez désactiver cette fonctionnalité en environnement de production et gagner un peu de performance :

$latte->setAutoRefresh(false);

Lors du déploiement sur un serveur de production, la génération initiale du cache peut logiquement prendre un moment, surtout pour les grosses applications. Latte intègre une prévention contre le cache stampede. C'est la situation où le serveur reçoit un grand nombre de requêtes concurrentes et où, le cache de Latte n'existant pas encore, elles le généreraient toutes en même temps, faisant grimper le CPU. Latte est malin : lorsque plusieurs requêtes concurrentes arrivent, seul le premier thread génère le cache, les autres attendent et l'utilisent ensuite.

Vous pouvez aussi pré-générer le cache pendant le déploiement (par exemple dans un script de déploiement) avec la méthode Engine::warmupCache(). Elle compile à l'avance le template indiqué dans le cache, de sorte que le premier visiteur n'ait pas à attendre : $latte->warmupCache('template.latte').

Façons d'étendre Latte

Latte se personnalise de plusieurs manières, du simple utilitaire jusqu'à de toutes nouvelles constructions de langage. La page Étendre Latte les traite en détail ; en voici un aperçu rapide :

  • Filtres personnalisés : pour formater ou transformer des données dans la sortie du template (par ex. {$var|myFilter}).
  • Fonctions personnalisées : pour la logique que vous appelez dans les expressions du template (par ex. {myFunction($arg)}).
  • Balises personnalisées : pour de toutes nouvelles constructions de langage ({mytag}...{/mytag} ou n:mytag).
  • Passes de compilation : fonctions qui modifient l'AST du template entre l'analyse et la génération du code PHP (optimisations, contrôles de sécurité…).
  • Loaders personnalisés : pour changer la façon dont Latte localise et charge les fichiers de template.

Si vous voulez réutiliser vos extensions d'un projet à l'autre ou les partager, regroupez-les dans une classe Extension Latte.

Paramètres en tant que classe

Plutôt que de passer les variables au template sous forme de tableau, mieux vaut créer une classe. Vous obtenez une écriture typée, une bonne complétion dans l'IDE et un moyen d'enregistrer des filtres et des fonctions.

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,
));

Désactivation de l'échappement automatique des variables

Si une variable contient une chaîne HTML, vous pouvez la marquer pour que Latte ne l'échappe pas automatiquement (et donc doublement). Cela évite d'avoir à préciser |noescape dans le template.

Le plus simple est d'envelopper la chaîne dans un objet Latte\Runtime\Html :

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

Latte n'échappe pas non plus les objets qui implémentent l'interface Latte\Runtime\HtmlStringable. Vous pouvez donc créer votre propre classe dont la méthode __toString() retournera du code HTML qui ne sera pas échappé automatiquement :

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'),
];

La méthode __toString doit retourner du HTML correct et assurer l'échappement des paramètres, sans quoi une vulnérabilité XSS peut apparaître !

Comment étendre Latte avec des filtres, des balises, etc.

Comment ajouter à Latte un filtre, une fonction, une balise personnalisée ? Vous le découvrirez au chapitre Étendre Latte. Si vous voulez réutiliser vos modifications dans différents projets ou les partager, vous devriez ensuite créer une extension.

Code arbitraire dans le template {php ...}

Seules des expressions PHP peuvent s'écrire dans la balise {do} ; vous ne pouvez donc pas y insérer par exemple des constructions comme if ... else ou des instructions terminées par un point-virgule.

Vous pouvez en revanche enregistrer l'extension RawPhpExtension, qui ajoute la balise {php ...}. Elle vous permet d'insérer n'importe quel code PHP. Elle n'est soumise à aucune règle du mode sandbox : son usage relève donc de la responsabilité de l'auteur du template.

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

Vérification du code généré

Latte compile les templates en code PHP. Il veille bien sûr à ce que le code généré soit syntaxiquement valide. En revanche, avec des extensions tierces ou RawPhpExtension, Latte ne peut pas garantir la correction du fichier généré. En PHP, vous pouvez d'ailleurs écrire du code syntaxiquement correct mais interdit (par exemple affecter une valeur à la variable $this), qui provoque une PHP Compile Error. Si vous écrivez une telle opération dans un template, elle se retrouvera aussi dans le code PHP généré. Comme PHP compte plus de deux cents opérations interdites différentes, Latte ne cherche pas à les détecter. PHP lui-même les signalera au moment du rendu, ce qui n'est généralement pas un problème.

Il existe cependant des situations où vous voulez savoir dès la compilation du template qu'il ne contient aucune PHP Compile Error. En particulier quand les templates peuvent être modifiés par des utilisateurs, ou quand vous utilisez le Sandbox. Dans ce cas, faites vérifier les templates pendant la compilation. Vous activez cette fonctionnalité avec la méthode Engine::enablePhpLinter(). Comme elle doit appeler le binaire PHP pour la vérification, passez son chemin en paramètre :

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

try {
	$latte->compile('home.latte');
} catch (Latte\CompileException $e) {
	// attrape les erreurs de Latte, mais aussi les Compile Error de PHP
	echo 'Error: ' . $e->getMessage();
}

Locale

Latte vous permet de définir la locale, qui influe sur le formatage des nombres, des dates et sur le tri. Elle se définit avec la méthode setLocale(). L'identifiant de locale suit le standard IETF language tag, qu'utilise l'extension PHP intl. Il se compose d'un code de langue et éventuellement d'un code de pays, par exemple en_US pour l'anglais des États-Unis, de_DE pour l'allemand d'Allemagne, etc.

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

Le réglage de la locale influe sur les filtres localDate, sort, number et bytes.

Nécessite l'extension PHP intl. Le réglage dans Latte n'affecte pas la locale globale de PHP.

Mode strict

En mode d'analyse strict, Latte vérifie l'absence de balises HTML fermantes manquantes et désactive en outre l'usage de la variable $this. Pour l'activer :

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

Pour générer des templates avec l'en-tête declare(strict_types=1), procédez ainsi :

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

Depuis Latte 3.1, les types stricts sont activés par défaut. Vous pouvez les désactiver avec $latte->setFeature(Latte\Feature::StrictTypes, false).

Avertissements de migration

Latte 3.1 modifie le comportement de certains attributs HTML. Par exemple, les valeurs null suppriment désormais l'attribut au lieu d'afficher une chaîne vide. Pour repérer facilement les endroits où ce changement touche vos templates, vous pouvez activer les avertissements de migration :

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

Une fois activés, Latte contrôle les attributs rendus et déclenche un avertissement utilisateur (E_USER_WARNING) si la sortie diffère de celle qu'aurait produite Latte 3.0. Quand un avertissement se présente, appliquez l'une des solutions :

  1. Si la nouvelle sortie convient à votre cas d'usage (vous préférez par exemple que l'attribut disparaisse quand il vaut null), supprimez l'avertissement en ajoutant le filtre |accept
  2. Si vous voulez que l'attribut soit rendu vide (par ex. title="") plutôt que supprimé quand la variable vaut null, fournissez une chaîne vide comme repli : title={$val ?? ''}
  3. Si vous tenez absolument à l'ancien comportement (afficher "1" pour true au lieu de "true", par exemple), convertissez explicitement la valeur en chaîne : data-foo={(string) $val}

Quand plus aucun avertissement ne subsiste, désactivez les avertissements de migration et supprimez tous les filtres |accept de vos templates : ils ne servent plus à rien.

Variables de boucle à portée limitée

Par défaut, les variables définies dans une boucle {foreach} (comme $key et $value) restent accessibles après la fin de la boucle, exactement comme en PHP. Cela peut conduire à des écrasements involontaires quand une variable de boucle porte le même nom qu'une variable existante du template.

La fonctionnalité ScopedLoopVariables limite la portée des variables de boucle au corps de la boucle. Une fois la boucle terminée, la valeur d'origine de la variable est restaurée (si elle existait auparavant) ou la variable est supprimée :

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

Exemple de la différence :

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

Sans ScopedLoopVariables : affiche 1, 2, 2 (la variable est écrasée) Avec ScopedLoopVariables : affiche 1, 2, original (la variable est restaurée)

Cela fonctionne aussi avec la syntaxe de décomposition, par ex. {foreach $array as [$a, $b]}.

Les variables de boucle utilisant des références ({foreach $array as &$value}) ou des affectations de propriétés ({foreach $array as $obj->prop}) ne sont pas limitées en portée, car cela irait à l'encontre de leur raison d'être.

Désindentation automatique

Avec les balises paires comme {if}, {foreach} ou {block}, vous indentez souvent le contenu imbriqué pour la lisibilité. Cette indentation se retrouve pourtant par défaut dans la sortie générée. La fonctionnalité Dedent la supprime automatiquement, si bien que la sortie reste propre quelle que soit la profondeur d'imbrication de vos balises Latte :

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

Exemple :

{if true}
	Hello
	World
{/if}

Sans Dedent, la sortie comprendrait l'indentation (\tHello\n\tWorld\n). Avec Dedent, l'indentation est retirée et la sortie devient Hello\nWorld\n.

Une indentation plus profonde à l'intérieur d'un bloc est conservée relativement à l'indentation de base :

{if true}
	Hello
		Indented
{/if}

Sortie : Hello\n\tIndented\n.

L'indentation à l'intérieur d'un bloc doit être homogène (tabulations ou espaces, mais pas les deux). Si elles sont mélangées, Latte lève une exception Inconsistent indentation.

Traduction dans les templates

Avec l'extension TranslatorExtension, vous ajoutez au template les balises {_...}, {translate} et le filtre translate. Ils servent à traduire des valeurs ou des parties du template dans d'autres langues. Le paramètre est le callable qui effectue la traduction, ou un objet de type Nette\Localization\Translator (passez null pour désactiver les traductions) :

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

	public function translate(string $original): string
	{
		// à partir de $original, nous créons $translated selon $this->lang
		return $translated;
	}
}

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

Le traducteur est appelé à l'exécution, lors du rendu du template. Latte peut cependant traduire tous les textes statiques dès la compilation du template. Cela économise des performances, car chaque chaîne n'est traduite qu'une seule fois et la traduction obtenue est écrite dans le fichier compilé. Plusieurs versions compilées du template sont ainsi créées dans le répertoire de cache, une par langue. Il suffit pour cela d'indiquer la langue en deuxième paramètre :

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

Par texte statique, on entend par exemple {_'hello'} ou {translate}hello{/translate}. Les textes non statiques, comme {_$foo}, continueront d'être traduits à l'exécution.

Le template peut également passer au traducteur des paramètres supplémentaires via {_$original, foo: bar} ou {translate foo: bar}, qu'il recevra sous forme de tableau $params :

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

Débogage et Tracy

Latte s'efforce de rendre le développement aussi agréable que possible. Pour le débogage, il existe trois balises : {dump}, {debugbreak} et {trace}.

Vous serez le plus à l'aise en installant le formidable outil de débogage Tracy et en activant le plugin Latte :

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

$latte = new Latte\Engine;
// active l'extension de Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);

Vous verrez désormais toutes les erreurs sur un joli écran rouge, y compris les erreurs de templates avec mise en évidence de la ligne et de la colonne (vidéo). En même temps, dans le coin inférieur droit, dans la Tracy Bar, apparaît un onglet pour Latte où vous voyez clairement tous les templates rendus et leurs relations (avec la possibilité de cliquer pour aller dans le template ou le code compilé), ainsi que les variables :

Comme Latte compile les templates en code PHP lisible, vous pouvez confortablement y avancer pas à pas dans votre IDE.

Linter : validation de la syntaxe des templates

L'outil Linter sert à valider tous les templates. Son but est de parcourir les fichiers indiqués et de s'assurer qu'ils ne contiennent ni erreur de syntaxe ni référence à des balises, filtres, fonctions, classes ou constructions inexistants.

Le Linter s'exécute depuis la ligne de commande :

vendor/bin/latte-lint <path>

Utilisez le paramètre --strict pour activer le mode strict. Le paramètre --debug affiche le nom de chaque fichier traité et le détail complet des exceptions, ce qui aide au diagnostic.

Si vous utilisez des balises, des filtres ou d'autres extensions Latte personnalisés, vous devez créer votre propre variante du Linter, par exemple custom-latte-lint. Dans ce script, vous enregistrez toutes les extensions nécessaires avant que la validation des templates n'ait lieu :

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

// indiquez le chemin réel du fichier autoload.php
require __DIR__ . '/vendor/autoload.php';

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

$linter = new Latte\Tools\Linter;
$latte = $linter->getEngine();
// ajoutez ici vos extensions particulières
$latte->addExtension(/* ... */);

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

Vous pouvez aussi passer au Linter votre propre objet Latte\Engine :

$latte = new Latte\Engine;
// nous configurons ici l'objet $latte
$linter = new Latte\Tools\Linter(engine: $latte);

Le linter ainsi personnalisé s'utilise alors comme l'outil standard, mais avec la pleine connaissance de toutes vos extensions.

Chargement de templates depuis une chaîne

Vous devez charger les templates depuis des chaînes plutôt que depuis des fichiers, pour des tests par exemple ? StringLoader vous aidera :

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

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

Gestionnaire d'exceptions

Vous pouvez définir votre propre gestionnaire pour les exceptions attendues. Les exceptions levées à l'intérieur de {try} et dans le sandbox lui sont transmises.

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

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

Recherche automatique de layout

Avec la balise {layout}, le template détermine son template parent. Il est aussi possible de faire chercher le layout automatiquement, ce qui simplifie l'écriture des templates puisqu'ils n'ont plus besoin d'inclure la balise {layout}.

Voici comment procéder :

// elle retourne le chemin du fichier du template parent
$finder = fn(Latte\Runtime\Template $template) => 'automatic.layout.latte';
$latte = new Latte\Engine;
$latte->addProvider('coreParentFinder', $finder);

Si le template ne doit pas avoir de layout, il l'indiquera avec la balise {layout none}.

version: 3.x