Migrazione da Latte 2 a 3
Latte 3 ha un compilatore completamente riscritto e una grammatica formalmente ben definita. L'obiettivo è coincidere il più possibile con Latte 2, ma alcuni costrutti richiedono piccoli ritocchi.
Nella pratica risulta che la stragrande maggioranza dei template non ha bisogno di alcuna modifica e funziona in Latte 3 esattamente come in Latte 2. Ma come individuare le incompatibilità?
Per prima cosa installate la versione di transizione Latte 2.11.
Questa versione non porta nuove funzionalità: si limita ad avvisare con E_USER_DEPRECATED nei casi in cui sa che il nuovo Latte non li supporterà e, cosa più importante, vi suggerisce come sistemarli. Per passare in rassegna tutti i template e verificare se sono compatibili potete usare lo strumento Linter, che si esegue dalla console:
vendor/bin/latte-lint <path>
Una volta risolte le eventuali incompatibilità, aggiornate a Latte 3.0. E lanciate di nuovo il Linter per assicurarvi che il nuovo parser rigoroso comprenda davvero tutti i template.
Modifiche all'API
Le modifiche all'API riguardano solo l'aggiunta di tag personalizzati. Il resto dell'API resta uguale alla versione 2, cioè lo stesso modo di fare il rendering dei template, di passare i parametri e di registrare i filtri.
Fa eccezione il cosiddetto filtro dinamico Engine::addFilter(null, ...), che ora è gestito dai filtri registrati tramite una classe con il
metodo addFilter(). Il metodo originale Engine::addFilterLoader() esiste ancora come soluzione
transitoria, ma è deprecato.
L'API per aggiungere tag personalizzati è completamente diversa, quindi le estensioni pensate per Latte 2 non funzioneranno. Vedi anche Aggiornamento delle estensioni.
Modifiche alla sintassi
Le modifiche sono le seguenti:
- i filtri usano la virgola come separatore dei parametri: dove prima si scriveva
|filter: arg : argora si scrive|filter: arg, arg - il tag
{label foo}...{/label}è sempre di tipo pari; la forma dispari va scritta{label /} - al contrario, il tag
{_'text'}è sempre dispari; la forma pari{_}...{/}è sostituita dal nuovo{translate}...{/translate} - le pseudo-stringhe come
{block foo-$var}vanno scritte tra apici,{block "foo-$var"}, oppure completate con le parentesi graffe,{block foo-{$var}} - lo stesso vale per gli attributi: al posto di
n:block="foo-$var"usaten:block="foo-{$var}" - in Latte 3 i filtri fanno distinzione tra maiuscole e minuscole
- il tag
{do ...}o{php ...}può contenere solo espressioni; per usare PHP qualsiasi registrate RawPhpExtension.
E altri casi limite:
- gli attributi
n:inner-xxx,n:tag-xxxen:ifcontentnon si possono usare sugli elementi HTML void - l'attributo
n:inner-snippetva scritto senza inner- - i tag
</script>e</style>devono essere chiusi - la variabile magica
$iterationsè stata rimossa (da non confondere con$iterator!) - sostituite il tag
{includeblock file.latte}con{include file.latte with blocks}oppure con{import} {include "abc"}va scritto come{include file "abc"}, a meno che"abc"non contenga un punto e sia quindi evidente che si tratta di un file
Aggiornamento delle estensioni
Con la riscrittura completa del parser è cambiato del tutto il modo di scrivere i tag personalizzati. Se avete tag personalizzati creati per Latte, dovrete riscriverli per la versione 3: vedi la documentazione.
Se usate un'estensione di terzi che aggiunge tag, dovrete aspettare che l'autore rilasci una versione per Latte 3. Le librerie
nette/application, nette/caching e nette/forms nella versione 3.1, così come Texy, sono
già state aggiornate e funzionano sia con Latte 2 sia con Latte 3.
nette/application
Nell'uso normale di Nette questa estensione viene impostata automaticamente e non serve cambiare nulla.
Vecchio codice per Latte 2:
$latte->onCompile[] = function ($latte) {
Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};
$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
Nuovo codice per Latte 3:
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
UIExtension aggiunge n:href, {link}, {control}, {snippet} e simili. I tag
per gli snippet passano quindi da Latte stesso alla libreria nette/application. In Latte 3 il metodo del presenter
templatePrepareFilters() non viene più chiamato.
nette/forms
Nell'uso normale di Nette questa estensione viene impostata automaticamente e non serve cambiare nulla.
Vecchio codice per Latte 2:
$latte->onCompile[] = function ($latte) {
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
Nuovo codice per Latte 3:
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
nette/caching
Nell'uso normale di Nette questa estensione viene impostata automaticamente e non serve cambiare nulla.
Vecchio codice per Latte 2:
$latte->onCompile[] = function ($latte) {
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};
$latte->addProvider('cacheStorage', $cacheStorage);
Nuovo codice per Latte 3:
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
Tracy
Anche il pannello per Tracy si attiva ora come estensione.
Vecchio codice per Latte 2:
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
Nuovo codice per Latte 3:
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
Traduzioni
TranslatorExtension aggiunge i tag di traduzione {_'text'}, il nuovo tag di tipo pari
{translate}...{/translate} e il filtro |translate.
Vecchio codice per Latte 2:
$latte->addFilter('translate', [$translator, 'translate']);
Nuovo codice per Latte 3:
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
Nei presenter si attiva automaticamente impostando il traduttore nel template con il metodo
$template->setTranslator($translator). Senza questo i tag di traduzione non saranno disponibili e dovrete
registrare l'estensione manualmente o tramite un file di configurazione.
File di configurazione
In Latte 2 era possibile registrare nuovi tag tramite il file di configurazione, nella sezione
latte › macros. Nella versione 3 si aggiungono in questo modo intere estensioni:
latte:
extensions:
- App\Templating\LatteExtension
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Sviluppate un'estensione per Latte?
Nella vostra libreria potete supportare entrambe le versioni di Latte contemporaneamente. Per rilevare la versione conviene
usare la costante Latte\Engine::VERSION, così da separare l'uso di onCompile[] e
addMacro() dal nuovo addExtension():
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
// inizializzazione per Latte 2
$this->latte->onCompile[] = function ($latte) {
$latte->addMacro(/* ... */);
};
} else {
// inizializzazione per Latte 3
$this->latte->addExtension(/* ... */);
}
Come esempio, proviamo a riscrivere per Latte 3 il codice seguente, pensato per Latte 2:
// vecchio codice per 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 si estende tramite le estensioni. Un'estensione minimale che
aggiunge il tag foo avrebbe questo aspetto:
// nuovo codice per Latte 3
class FooExtension extends Latte\Extension
{
public function getTags(): array
{
return [
'foo' => [FooNode::class, 'create'], // la classe FooNode la aggiungiamo tra un attimo
];
}
}
// registrazione
$this->latte->addExtension(new FooExtension);
Il nuovo compilatore è più solido e non contiene le scorciatoie di prima, quindi scrivere una macro richiede qualche riga di codice in più. Per esempio non possiamo passare direttamente una stringa di codice PHP come in Latte 2: creiamo invece una funzione. Ricordiamo che in Latte 2 la funzione avrebbe avuto più o meno questo aspetto:
// 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 procede comunque in modo molto simile, solo che MacroNode si chiama Latte\Compiler\Tag e
PhpWriter si chiama Latte\Compiler\PrintContext. Soprattutto, però, c'è un passaggio intermedio in
più: la funzione non restituisce direttamente codice PHP, ma un nodo, cioè un discendente di StatementNode, che
entra poi a far parte dell'albero AST. E questo nodo ha un metodo print(Latte\Compiler\PrintContext $context): string
che restituisce il codice 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 ...'); // restituisce codice PHP
}
}
Inoltre la maschera in $context->format() non ha più le abbreviazioni %node.***: si presuppone
che prima abbiate analizzato il contenuto del
tag. Usiamo quindi il parser per analizzare il contenuto in variabili (sottonodi) e poi lo stampiamo:
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;
// analisi del contenuto del tag
$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,
);
}
}
Infine aggiungeremo il metodo getIterator() per permettere di attraversare i sottonodi durante la visita dell'albero:
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
...
public function &getIterator(): \Generator
{
yield $this->subject;
yield $this->args;
}
}