Migration de Latte 2 vers 3
Latte 3 a un compilateur entièrement réécrit et une grammaire formellement bien définie. Elle devrait correspondre autant que possible à Latte 2, mais certaines constructions demandent de légères retouches.
En pratique, il s'avère que la grande majorité des templates n'ont besoin d'aucune modification et fonctionnent de la même façon en Latte 2 et en Latte 3. Mais comment détecter les incompatibilités ?
Commencez par installer la version de transition Latte 2.11.
Cette version n'apporte aucune nouveauté : elle se contente d'émettre un avertissement E_USER_DEPRECATED pour les cas dont elle sait que le nouveau Latte ne les prendra pas en charge et, surtout, vous conseille sur la façon de les corriger. Pour parcourir tous les templates et vérifier leur compatibilité, vous pouvez utiliser l'outil Linter que vous lancez depuis la console :
vendor/bin/latte-lint <path>
Une fois les éventuelles incompatibilités résolues, passez à Latte 3.0. Et relancez le Linter pour vous assurer que le nouvel analyseur strict comprend réellement tous les templates.
Changements de l'API
Les changements de l'API ne concernent que l'ajout de balises personnalisées. Le reste de l'API demeure identique à la version 2 : même façon de rendre les templates, de passer des paramètres, d'enregistrer des filtres.
L'exception est le filtre dit dynamique Engine::addFilter(null, ...), désormais pris en charge par les filtres enregistrés via une classe à l'aide de la méthode
addFilter(). La méthode d'origine Engine::addFilterLoader() existe encore comme solution transitoire,
mais elle est dépréciée.
L'API d'ajout de balises personnalisées est complètement différente, si bien que les add-ons conçus pour Latte 2 ne fonctionneront pas avec elle. Voyez aussi Mise à jour des add-ons.
Changements de syntaxe
Les changements sont les suivants :
- les filtres utilisent la virgule comme séparateur de paramètres : l'ancien
|filter: arg : args'écrit désormais|filter: arg, arg - la balise
{label foo}...{/label}est toujours paire ; en version non paire, il faut écrire{label /} - à l'inverse, la balise
{_'text'}est toujours non paire ; la version paire{_}...{/}est remplacée par la nouvelle{translate}...{/translate} - les pseudo-chaînes comme
{block foo-$var}doivent s'écrire entre guillemets{block "foo-$var"}ou avec des accolades{block foo-{$var}} - cela vaut aussi pour les attributs : au lieu de
n:block="foo-$var", utilisezn:block="foo-{$var}". - en Latte 3, il faut respecter la casse des filtres
- la balise
{do ...}ou{php ...}ne peut contenir que des expressions ; pour utiliser du PHP quelconque, enregistrez RawPhpExtension.
Et quelques cas limites :
- les attributs
n:inner-xxx,n:tag-xxxetn:ifcontentne peuvent pas être utilisés sur les éléments HTML vides - l'attribut
n:inner-snippetdoit s'écrire sans inner- - les balises
</script>et</style>doivent être fermées - la variable magique
$iterationsa été supprimée (à ne pas confondre avec$iterator!) - remplacez la balise
{includeblock file.latte}par{include file.latte with blocks}ou{import} {include "abc"}devrait s'écrire{include file "abc"}, sauf si"abc"contient un point et qu'il est clair qu'il s'agit d'un fichier
Mise à jour des add-ons
Avec la réécriture complète de l'analyseur, la façon d'écrire des balises personnalisées a totalement changé. Si vous avez des balises personnalisées pour Latte, vous devrez les réécrire pour la version 3, voyez la documentation.
Si vous utilisez un add-on tiers qui ajoute des balises, vous devrez attendre que son auteur publie une version pour Latte
3. Les bibliothèques nette/application, nette/caching et nette/forms en version 3.1,
ainsi que Texy, sont déjà à jour et fonctionnent avec Latte 2 comme avec Latte 3.
nette/application
Avec une utilisation normale de Nette, cette extension est configurée automatiquement et il n'y a rien à changer.
Ancien code pour Latte 2 :
$latte->onCompile[] = function ($latte) {
Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};
$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
Nouveau code pour Latte 3 :
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
UIExtension ajoute n:href, {link}, {control}, {snippet}, etc. Les balises
des snippets passent donc de Latte lui-même à la bibliothèque nette/application. En Latte 3, la méthode
templatePrepareFilters() du presenter n'est plus appelée.
nette/forms
Avec une utilisation normale de Nette, cette extension est configurée automatiquement et il n'y a rien à changer.
Ancien code pour Latte 2 :
$latte->onCompile[] = function ($latte) {
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
Nouveau code pour Latte 3 :
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
nette/caching
Avec une utilisation normale de Nette, cette extension est configurée automatiquement et il n'y a rien à changer.
Ancien code pour Latte 2 :
$latte->onCompile[] = function ($latte) {
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};
$latte->addProvider('cacheStorage', $cacheStorage);
Nouveau code pour Latte 3 :
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
Tracy
Le panneau pour Tracy s'active désormais lui aussi comme une extension.
Ancien code pour Latte 2 :
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
Nouveau code pour Latte 3 :
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
Traductions
TranslatorExtension ajoute les balises de traduction {_'text'}, la nouvelle paire
{translate}...{/translate} et le filtre |translate.
Ancien code pour Latte 2 :
$latte->addFilter('translate', [$translator, 'translate']);
Nouveau code pour Latte 3 :
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
Dans les presenters, elle s'active automatiquement en définissant le traducteur sur le template par la méthode
$template->setTranslator($translator). Sans cela, les balises de traduction ne seront pas disponibles et vous
devrez enregistrer l'extension manuellement ou par le fichier de configuration.
Fichier de configuration
En Latte 2, il était possible d'enregistrer de nouvelles balises via le fichier de configuration, dans la section
latte › macros. En version 3, ce sont des extensions entières qui s'ajoutent ainsi :
latte:
extensions:
- App\Templating\LatteExtension
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Vous développez un add-on pour Latte ?
Votre bibliothèque peut prendre en charge les deux versions de Latte en même temps. Pour détecter la version, le mieux est
d'utiliser la constante Latte\Engine::VERSION afin de séparer l'usage de onCompile[] et
addMacro() du nouvel addExtension() :
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
// initialisation pour Latte 2
$this->latte->onCompile[] = function ($latte) {
$latte->addMacro(/* ... */);
};
} else {
// initialisation pour Latte 3
$this->latte->addExtension(/* ... */);
}
En guise d'exemple, essayons de réécrire pour Latte 3 le code suivant, destiné à Latte 2 :
// ancien code pour Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
$set = new Latte\Macros\MacroSet($latte->getCompiler());
$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};
Latte 3 s'étend à l'aide d'extensions. Une extension triviale ajoutant la balise
foo ressemblerait à ceci :
// nouveau code pour Latte 3
class FooExtension extends Latte\Extension
{
public function getTags(): array
{
return [
'foo' => [FooNode::class, 'create'], // nous ajouterons la classe FooNode dans un instant
];
}
}
// enregistrement
$this->latte->addExtension(new FooExtension);
Le nouveau compilateur est plus robuste, il ne comporte plus les raccourcis d'autrefois, si bien qu'écrire une macro demande un peu plus de lignes. Nous ne pouvons par exemple plus passer directement une chaîne de code PHP comme en Latte 2 ; nous créons une fonction à la place. Rappelons qu'en Latte 2, la fonction ressemblait à peu près à ceci :
// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});
Latte 3 procède malgré tout de façon très semblable, à ceci près que MacroNode s'appelle
Latte\Compiler\Tag et PhpWriter s'appelle Latte\Compiler\PrintContext. Mais surtout, il y a
une étape intermédiaire supplémentaire : la fonction ne retourne pas directement du code PHP, mais un nœud, c'est-à-dire un
descendant de StatementNode, qui fait ensuite partie de l'arbre AST. Et ce nœud possède une méthode
print(Latte\Compiler\PrintContext $context): string qui retourne le code PHP :
// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
public static function create(Latte\Compiler\Tag $tag): self
{
$node = new self;
return $node;
}
public function print(Latte\Compiler\PrintContext $context): string
{
return $context->format('echo ...'); // retourne du code PHP
}
}
De plus, le masque de $context->format() n'a plus les abréviations %node.*** : on suppose que
vous analysez d'abord le contenu de la balise. Nous utilisons
donc le parser pour analyser le contenu en variables (sous-nœuds), puis nous l'écrivons :
use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
public ExpressionNode $subject;
public ArrayNode $args;
public static function create(Latte\Compiler\Tag $tag): self
{
$node = new self;
// analyse du contenu de la balise
$node->subject = $tag->parser->parseUnquotedStringOrExpression();
$tag->parser->stream->tryConsume(',');
$node->args = $tag->parser->parseArguments();
return $node;
}
public function print(Latte\Compiler\PrintContext $context): string
{
return $context->format(
'echo %escape(MyClass:myFunc(%node, %node));',
$this->subject,
$this->args,
);
}
}
Pour finir, nous ajouterons la méthode getIterator() afin de permettre le parcours des sous-nœuds lors du parcours de l'arbre :
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
...
public function &getIterator(): \Generator
{
yield $this->subject;
yield $this->args;
}
}