Migrace z Latte 2 na 3
Latte 3 má kompletně přepsaným kompilátor a formálně přesně definovanou gramatiku. Ta by měla co nejvíce odpovídat Latte 2, ale existují konstrukce, které je třeba drobně upravit.
V praxi se ukazuje, že naprostou většinu šablon není potřeba nijak upravovat a fungují stejně v Latte 2 jako ve verzi 3. Jak ale odhalit nekompatibility?
Nejprve si nainstalujte přechodovou verzi Latte 2.11.
Tato verze nepřináší žádné novinky, jen pomocí E_USER_DEPRECATED upozorňuje na případy, u kterých ví, že je nové Latte nebude podporovat, a hlavně poradí, jak je upravit. Projít všechny šablony a otestovat, jestli jsou kompatibilní, vám pomůže nástroj Linter, který spustíte z konzole:
vendor/bin/latte-lint <cesta>
Když vyřešíte možné nekompatibility, aktualizujte na Latte 3.0. A opět pusťte Linter, abyste se ujistili, že nový striktní parser všem šablonám opravdu rozumí.
Změny v API
Změny v API se týkají jen přidávaní vlastních značek. Zbytek API zůstává stejný jako u verze 2, tj. stejným způsobem se vykreslují šablony, předávají parametry, registrují filtry.
Výjimkou je nahrazení tzv. dynamického filtru Engine::addFilter(null, ...)
za zavaděč filtrů, který si liší tím, že vrací vždy callable a registruje
se metodou Engine::addFilterLoader()
.
API pro přidávání vlastních značek je úplně jiné, takže doplňky určené pro Latte 2 s ním nebudou fungovat. Dále viz aktualizace doplňků.
Změny syntaxe
Změny jsou následující:
- ve filtrech se jako oddělovač parametrů používá čárka, dříve
|filtr: arg : arg
je nyní|filtr: arg, arg
- značka
{label foo}...{/label}
je vždy párová, nepárovou je potřeba psát{label /}
- naopak značka
{_'text'}
je vždy nepárová, párovou{_}...{/}
nahrazuje nové{translate}...{/translate}
- pseudořetězce jako
{block foo-$var}
je potřeba psát v uvozovkách{block "foo-$var"}
nebo doplnit složené závorky{block foo-{$var}}
- to se týká i atributů, tj. místo
n:block="foo-$var"
použijten:block="foo-{$var}"
. - je třeba dodržet velikost písmenek u filtrů, které jsou v Latte 3 case sensitive
- značka
{do ...}
nebo{php ...}
může obsahovat pouze výrazy, pro použití libovolného PHP zaregistrujte RawPhpExtension.
A ještě okrajové případy:
- atributy
n:inner-xxx
,n:tag-xxx
an:ifcontent
nelze použít u nepárových HTML elementů - atribut
n:inner-snippet
musí být psán bez inner- - musí být ukončené značky
</script>
a</style>
- odstranění magické proměnné
$iterations
(neplést s$iterator
!) - značku
{includeblock file.latte}
nahrazuje{include file.latte with blocks}
nebo{import}
{include "abc"}
by mělo být psáno jako{include file "abc"}
, pokud"abc"
neobsahuje tečku a není tak jasné, že jde o soubor
Aktualizace doplňků
S kompletním přepsáním parseru se zcela změnil i způsob psaní vlastních značek. Pokud máte pro Latte vytvořené vlastní značky, bude třeba je napsat znovu pro verzi 3, viz dokumentace.
Pokud používáte cizí doplněk, který přidává značky, je potřeba počkat, až autor vydá verzi pro Latte 3. Knihovny
nette/application
, nette/caching
a nette/forms
ve verzi 3.1 a také Texy již
aktualizovány jsou a fungují jak s Latte 2, tak i 3.
nette/application
Při obvyklém použití Nette se toto rozšíření nastavuje automaticky a není třeba nic měnit.
Starý kód pro Latte 2:
$latte->onCompile[] = function ($latte) {
Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};
$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
Nový kód pro Latte 3:
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
UIExtension přidává značky n:href
, {link}
, {control}
, {snippet}
, atd.
Značky pro snippety se tak přesouvají ze samotného Latte do knihovny nette/application
. V Latte 3 se už
nevolá metoda presenteru templatePrepareFilters()
.
nette/forms
Při obvyklém použití Nette se toto rozšíření nastavuje automaticky a není třeba nic měnit.
Starý kód pro Latte 2:
$latte->onCompile[] = function ($latte) {
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
Nový kód pro Latte 3:
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
nette/caching
Při obvyklém použití Nette se toto rozšíření nastavuje automaticky a není třeba nic měnit.
Starý kód pro Latte 2:
$latte->onCompile[] = function ($latte) {
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};
$latte->addProvider('cacheStorage', $cacheStorage);
Nový kód pro Latte 3:
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
Tracy
Panel pro Tracy se nyní aktivuje také jako rozšíření.
Starý kód pro Latte 2:
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
Nový kód pro Latte 3:
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
Překlady
TranslatorExtension přidává značky pro překlad {_'text'}
, nové párové
{translate}...{/translate}
a filtr |translate
.
Starý kód pro Latte 2:
$latte->addFilter('translate', [$translator, 'translate']);
Nový kód pro Latte 3:
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
V presenterech se automaticky aktivuje tím, že nastavíte šabloně translator metodou
$template->setTranslator($translator)
. Bez toho značky pro překlad nebudou k dispozici a je potřeba
rozšíření zaregistrovat ručně, nebo pomocí konfiguračního souboru, viz dále.
Konfigurační soubor
V Latte 2 bylo možné registrovat nové tagy pomocí konfiguračního souboru v sekci
latte › macros
. Ve verzi 3 se přidávají rozšíření tímto způsobem:
latte:
extensions:
- App\Templating\LatteExtension
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Vyvíjíte doplněk pro Latte?
Můžete ve své knihovně mít současně podporu obou verzí Latte. K detekci verze je nejlepší použít konstantu
Latte\Engine::VERSION
a oddělit tak použití onCompile[]
a addMacro()
od nového
addExtension()
:
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
// inicializace Latte 2
$this->latte->onCompile[] = function ($latte) {
$latte->addMacro(/* ... */);
};
} else {
// inicializace Latte 3
$this->latte->addExtension(/* ... */);
}
Jako příklad zkusíme přepsat následující kód určený pro Latte 2 do podoby pro Latte 3:
// starý kód pro 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 se rozšiřuje pomocí tzv. extensions. Triviální extension přidávající
značku foo
by vypadalo takto:
// nový kód pro Latte 3
class FooExtension extends Latte\Extension
{
public function getTags(): array
{
return [
'foo' => [FooNode::class, 'create'], // třídu FooNode doplníme za chvíli
];
}
}
// registrace
$this->latte->addExtension(new FooExtension);
Nový kompilátor je robustnější, neobsahuje dřívější zkratky, takže napsat makro vyžaduje o něco víc řádků kódu. Nelze například předat přímo řetězec s PHP kódem jako v Latte 2, místo toho vytvoříme funkci. Připomeňme, že v Latte 2 by ona funkce vypadala nějak takto:
// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});
Přesto na to Latte 3 jde v podstatě podobně, jen MacroNode
se jmenuje Latte\Compiler\Tag
a
PhpWriter
je Latte\Compiler\PrintContext
. Ale především je tam navíc mezikrok, tedy že funkce
nevrací přímo PHP kód, ale vrací uzel, tj. potomka StatementNode
, který je pak součástí AST stromu. A tento
uzel má metodu print(Latte\Compiler\PrintContext $content): string
, která vrací PHP kód:
// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
public static function create(Latte\Compiler\Tag $tag): self
{
$node = new self;
return $node; // vrací uzel
}
public function print(Latte\Compiler\PrintContext $context): string
{
return $context->format('echo ...'); // vrací PHP kód
}
}
Dále maska v $context->format()
už nemá zkratky %node.***
, předpokládá se, že obsah značky
nejprve naparsujete. Takže využijeme parser a naparsujeme obsah
do proměnných (poduzlů), a poté vypíšeme:
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;
// parsování obsahu značky
$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,
);
}
}
A nakonec doplníme metodu getIterator()
, aby bylo možné poduzly procházet při tzv. traversování:
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
...
public function &getIterator(): \Generator
{
yield $this->subject;
yield $this->args;
}
}