Latte d'extension
Latte est très flexible et peut être étendu de nombreuses façons : vous pouvez ajouter des filtres, des fonctions, des balises, des chargeurs, etc. personnalisés. Nous allons vous montrer comment le faire.
Ce chapitre décrit les différentes façons d'étendre Latte. Si vous voulez réutiliser vos modifications dans différents projets ou si vous voulez les partager avec d'autres, vous devez alors créer ce que l'on appelle une extension.
Combien de routes mènent à Rome ?
Puisque certaines des façons d'étendre Latte peuvent être mélangées, essayons d'abord d'expliquer les différences entre elles. À titre d'exemple, essayons d'implémenter un générateur de Lorem ipsum, auquel on transmet le nombre de mots à générer.
La principale construction du langage Latte est la balise. Nous pouvons implémenter un générateur en étendant Latte avec une nouvelle balise :
{lipsum 40}
La balise fonctionnera parfaitement. Cependant, le générateur sous la forme d'une balise peut ne pas être assez flexible car il ne peut pas être utilisé dans une expression. D'ailleurs, dans la pratique, vous avez rarement besoin de générer des balises ; et c'est une bonne nouvelle, car les balises sont un moyen plus compliqué d'étendre.
Bon, essayons de créer un filtre au lieu d'une balise :
{=40|lipsum}
Encore une fois, une option valide. Mais le filtre doit transformer la valeur passée en quelque chose d'autre. Ici, nous
utilisons la valeur 40
, qui indique le nombre de mots générés, comme argument de filtre, et non comme la valeur
que nous voulons transformer.
Essayons donc d'utiliser la fonction :
{lipsum(40)}
C'est ça ! Pour cet exemple particulier, la création d'une fonction est le point d'extension idéal à utiliser. Vous pouvez l'appeler partout où une expression est acceptée, par exemple :
{var $text = lipsum(40)}
Filtres
Créez un filtre en enregistrant son nom et tout appelable en PHP, comme une fonction :
$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // raccourcit le texte à 10 caractères.
Dans ce cas, il serait préférable que le filtre obtienne un paramètre supplémentaire :
$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));
Nous l'utilisons dans un modèle comme celui-ci :
<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>
Comme vous pouvez le voir, la fonction reçoit le côté gauche du filtre avant le pipe |
as the first argument
and the arguments passed to the filter after :
comme arguments suivants.
Bien sûr, la fonction représentant le filtre peut accepter n'importe quel nombre de paramètres, et les paramètres variadiques sont également supportés.
Si le filtre renvoie une chaîne en HTML, vous pouvez la marquer pour que Latte ne l'échappe pas automatiquement (et donc
doublement). Cela évite d'avoir à spécifier |noescape
dans le modèle. La méthode la plus simple consiste à
envelopper la chaîne dans un objet Latte\Runtime\Html
, l'autre méthode étant celle des filtres contextuels.
$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));
Dans ce cas, le filtre doit assurer l'échappement correct des données.
Filtres utilisant la classe
La deuxième façon de définir un filtre est d'utiliser
la classe. Nous créons une méthode avec l'attribut TemplateFilter
:
class TemplateParameters
{
public function __construct(
// parameters
) {}
#[Latte\Attributes\TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
Chargeur de filtre
Au lieu d'enregistrer des filtres individuels, vous pouvez créer un “chargeur”, qui est une fonction appelée avec le nom du filtre comme argument et qui renvoie son appelable PHP, ou null.
$latte->addFilterLoader([new Filters, 'load']);
class Filters
{
public function load(string $filter): ?callable
{
if (in_array($filter, get_class_methods($this))) {
return [$this, $filter];
}
return null;
}
public function shortify($s, $len = 10)
{
return mb_substr($s, 0, $len);
}
// ...
}
Filtres contextuels
Un filtre contextuel est un filtre qui accepte un objet Latte\Runtime\FilterInfo en premier paramètre, suivi d'autres paramètres comme dans le cas des filtres classiques. Il est enregistré de la même manière, Latte reconnaît lui-même que le filtre est contextuel :
use Latte\Runtime\FilterInfo;
$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
// ...
});
Les filtres contextuels peuvent détecter et modifier le type de contenu qu'ils reçoivent dans la variable
$info->contentType
. Si le filtre est appelé classiquement sur une variable (par exemple {$var|foo}
),
la variable $info->contentType
contiendra null.
Le filtre doit d'abord vérifier si le type de contenu de la chaîne d'entrée est pris en charge. Il peut également le modifier. Exemple d'un filtre qui accepte du texte (ou null) et renvoie du HTML :
use Latte\Runtime\FilterInfo;
$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
// Nous vérifions d'abord si le type de contenu de l'entrée est du texte.
if (!in_array($info->contentType, [null, ContentType::Text])) {
throw new Exception("Filter |money used in incompatible content type $info->contentType.");
}
// changez le type de contenu en HTML
$info->contentType = ContentType::Html;
return "<i>$amount EUR</i>";
});
Dans ce cas, le filtre doit assurer un échappement correct des données.
Tous les filtres qui sont utilisés sur des blocs (par exemple, en tant
que {block|foo}...{/block}
) doivent être contextuels.
Fonctions
Par défaut, toutes les fonctions natives de PHP peuvent être utilisées dans Latte, sauf si la sandbox le désactive. Mais vous pouvez aussi définir vos propres fonctions. Elles peuvent remplacer les fonctions natives.
Créez une fonction en enregistrant son nom et tout appelable PHP :
$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
return $args[array_rand($args)];
});
L'utilisation est alors la même que lors de l'appel de la fonction PHP :
{random(apple, orange, lemon)} // prints for example: apple
Fonctions utilisant la classe
La deuxième façon de définir une fonction est d'utiliser la classe. Nous créons une méthode avec
l'attribut TemplateFunction
:
class TemplateParameters
{
public function __construct(
// parameters
) {}
#[Latte\Attributes\TemplateFunction]
public function random(...$args)
{
return $args[array_rand($args)];
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
Chargeurs
Les chargeurs sont responsables du chargement des modèles à partir d'une source, comme un système de fichiers. Ils sont
définis à l'aide de la méthode setLoader()
:
$latte->setLoader(new MyLoader);
Les chargeurs intégrés sont :
FileLoader
Chargeur par défaut. Charge les modèles à partir du système de fichiers.
L'accès aux fichiers peut être restreint en définissant le répertoire de base :
$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');
StringLoader
Charge les modèles à partir de chaînes de caractères. Ce chargeur est très utile pour les tests unitaires. Il peut également être utilisé pour les petits projets où il peut être judicieux de stocker tous les modèles dans un seul fichier PHP.
$latte->setLoader(new Latte\Loaders\StringLoader([
'main.file' => '{include other.file}',
'other.file' => '{if true} {$var} {/if}',
]));
$latte->render('main.file');
Utilisation simplifiée :
$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);
Création d'un chargeur personnalisé
Loader est une classe qui implémente l'interface Latte\Loader.
Tags
L'une des fonctionnalités les plus intéressantes du moteur de création de modèles est la possibilité de définir de nouvelles constructions linguistiques à l'aide de balises. Il s'agit également d'une fonctionnalité plus complexe et vous devez comprendre le fonctionnement interne de Latte.
Dans la plupart des cas, cependant, la balise n'est pas nécessaire :
- si elle doit générer une sortie, utilisez plutôt la fonction
- s'il s'agit de modifier une entrée et de la renvoyer, utilisez plutôt le filtre
- s'il s'agit d'éditer une zone de texte, il faut l'entourer d'une balise
{block}
et utiliser un filtre - si elle n'est pas censée produire quelque chose mais juste appeler une fonction, appelez-la avec
{do}
Si vous souhaitez toujours créer une balise, c'est parfait ! Vous trouverez tous les éléments essentiels dans la section Créer une extension.
Passes du compilateur
Les passes du compilateur sont des fonctions qui modifient les AST ou collectent des informations dans ces derniers. Dans Latte, par exemple, un bac à sable est implémenté de cette manière : il parcourt tous les noeuds d'un AST, trouve les appels de fonctions et de méthodes, et les remplace par des appels contrôlés.
Comme pour les balises, il s'agit d'une fonctionnalité plus complexe et vous devez comprendre comment Latte fonctionne sous le capot. Vous trouverez tous les éléments essentiels dans le chapitre Création d'une extension.