Nette Documentation Preview

syntax
Latte d'extension
*****************

.[perex]
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 |creating-extension].


Combien de routes mènent à Rome ? .[#toc-how-many-roads-lead-to-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 :

```latte
{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 :

```latte
{=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 :

```latte
{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 :

```latte
{var $text = lipsum(40)}
```


Filtres .[#toc-filters]
=======================

Créez un filtre en enregistrant son nom et tout appelable en PHP, comme une fonction :

```php
$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 :

```php
$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));
```

Nous l'utilisons dans un modèle comme celui-ci :

```latte
<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 |#Contextual Filters].

```php
$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));
```

.[note]
Dans ce cas, le filtre doit assurer l'échappement correct des données.


Filtres utilisant la classe .[#toc-filters-using-the-class]
-----------------------------------------------------------

La deuxième façon de définir un filtre est d'[utiliser la classe |develop#Parameters as a class]. Nous créons une méthode avec l'attribut `TemplateFilter`:

```php
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 .[#toc-filter-loader]
----------------------------------------

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.

```php
$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 .[#toc-contextual-filters]
----------------------------------------------

Un filtre contextuel est un filtre qui accepte un objet [api: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 :

```php
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 :

```php
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>";
});
```

.[note]
Dans ce cas, le filtre doit assurer un échappement correct des données.

Tous les filtres qui sont utilisés sur des [blocs |tags#block] (par exemple, en tant que `{block|foo}...{/block}`) doivent être contextuels.


Fonctions .[#toc-functions]
===========================

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 :

```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 :

```latte
{random(apple, orange, lemon)} // prints for example: apple
```


Fonctions utilisant la classe .[#toc-functions-using-the-class]
---------------------------------------------------------------

La deuxième façon de définir une fonction est d'[utiliser la classe |develop#Parameters as a class]. Nous créons une méthode avec l'attribut `TemplateFunction`:

```php
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 .[#toc-loaders]
=========================

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()`:

```php
$latte->setLoader(new MyLoader);
```

Les chargeurs intégrés sont :


FileLoader .[#toc-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 :

```php
$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');
```


StringLoader .[#toc-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.

```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 :

```php
$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);
```


Création d'un chargeur personnalisé .[#toc-creating-a-custom-loader]
--------------------------------------------------------------------

Loader est une classe qui implémente l'interface [api:Latte\Loader].


Tags .[#toc-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 |#functions]
- s'il s'agit de modifier une entrée et de la renvoyer, utilisez plutôt le [filtre |#filters]
- s'il s'agit d'éditer une zone de texte, il faut l'entourer d'une balise [`{block}` |tags#block] et utiliser un [filtre |#Contextual Filters]
- si elle n'est pas censée produire quelque chose mais juste appeler une fonction, appelez-la avec [`{do}` |tags#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 |creating-extension].


Passes du compilateur .[#toc-compiler-passes]
=============================================

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 |creating-extension].

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.