Migracja z Latte 2 do 3
Latte 3 ma całkowicie przepisany kompilator i formalnie dobrze zdefiniowaną gramatykę. Powinna ona odpowiadać Latte 2 tak wiernie, jak to możliwe, ale istnieje kilka konstrukcji, które wymagają drobnych poprawek.
W praktyce okazuje się, że zdecydowana większość szablonów nie wymaga żadnych zmian i działa w Latte 3 tak samo jak w Latte 2. Jak jednak wykryć niezgodności?
Najpierw zainstaluj wersję przejściową Latte 2.11.
Ta wersja nie wnosi żadnych nowych funkcji, tylko za pomocą E_USER_DEPRECATED ostrzega przed przypadkami, o których wie, że nowe Latte ich nie obsłuży, a co ważniejsze, podpowiada, jak je poprawić. Aby przejrzeć wszystkie szablony i sprawdzić, czy są zgodne, możesz użyć narzędzia Linter, które uruchamiasz z konsoli:
vendor/bin/latte-lint <path>
Gdy rozwiążesz ewentualne niezgodności, przejdź na Latte 3.0. I uruchom Linter ponownie, aby upewnić się, że nowy, ścisły parser naprawdę rozumie wszystkie szablony.
Zmiany w API
Zmiany w API dotyczą tylko dodawania własnych tagów. Reszta API pozostaje taka sama jak w wersji 2, czyli ten sam sposób renderowania szablonów, przekazywania parametrów i rejestrowania filtrów.
Wyjątkiem jest tzw. dynamiczny filtr Engine::addFilter(null, ...), którym teraz zajmują się filtry rejestrowane przez klasę za pomocą metody
addFilter(). Pierwotna metoda Engine::addFilterLoader() nadal istnieje jako rozwiązanie przejściowe,
ale jest przestarzała.
API do dodawania własnych tagów jest całkowicie inne, więc dodatki napisane dla Latte 2 nie będą z nim działać. Zobacz też Aktualizacja dodatków.
Zmiany w składni
Zmiany są następujące:
- filtry używają przecinka jako separatora parametrów, dotychczasowe
|filter: arg : argto teraz|filter: arg, arg - tag
{label foo}...{/label}jest zawsze parzysty, nieparzysty należy zapisywać{label /} - odwrotnie, tag
{_'text'}jest zawsze nieparzysty, parzysty{_}...{/}zastępuje nowy{translate}...{/translate} - pseudołańcuchy takie jak
{block foo-$var}trzeba zapisywać w cudzysłowach{block "foo-$var"}albo dodać klamry{block foo-{$var}} - dotyczy to również atrybutów, czyli zamiast
n:block="foo-$var"użyjn:block="foo-{$var}". - w Latte 3 trzeba zwracać uwagę na wielkość liter w nazwach filtrów
- Tag
{do ...}lub{php ...}może zawierać wyłącznie wyrażenia; aby użyć dowolnego kodu PHP, zarejestruj RawPhpExtension.
I dalsze przypadki brzegowe:
- atrybutów
n:inner-xxx,n:tag-xxxin:ifcontentnie można używać na pustych elementach HTML - atrybut
n:inner-snippettrzeba zapisywać bez inner- - tagi
</script>i</style>muszą być zamknięte - magiczna zmienna
$iterationszostała usunięta (nie mylić z$iterator!) - tag
{includeblock file.latte}zastąp przez{include file.latte with blocks}albo{import} {include "abc"}należy zapisywać jako{include file "abc"}, chyba że"abc"zawiera kropkę i widać, że chodzi o plik
Aktualizacja dodatków
Wraz z całkowitym przepisaniem parsera zupełnie zmienił się sposób pisania własnych tagów. Jeśli masz własne tagi napisane dla Latte, będziesz musiał napisać je na nowo dla wersji 3, zobacz dokumentację.
Jeśli używasz obcego dodatku, który dodaje tagi, będziesz musiał poczekać, aż autor wyda wersję dla Latte
3. Biblioteki nette/application, nette/caching i nette/forms w wersji 3.1, a także Texy,
zostały już zaktualizowane i działają zarówno z Latte 2, jak i 3.
nette/application
Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.
Stary kod dla Latte 2:
$latte->onCompile[] = function ($latte) {
Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};
$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
Nowy kod dla Latte 3:
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
UIExtension dodaje n:href, {link}, {control}, {snippet} itd. Tagi do
snippetów przenoszą się więc z samego Latte do biblioteki nette/application. W Latte 3 metoda presentera
templatePrepareFilters() nie jest już wywoływana.
nette/forms
Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.
Stary kod dla Latte 2:
$latte->onCompile[] = function ($latte) {
Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
Nowy kod dla Latte 3:
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
nette/caching
Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.
Stary kod dla Latte 2:
$latte->onCompile[] = function ($latte) {
$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};
$latte->addProvider('cacheStorage', $cacheStorage);
Nowy kod dla Latte 3:
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
Tracy
Panel dla Tracy jest teraz również aktywowany jako rozszerzenie.
Stary kod dla Latte 2:
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
Nowy kod dla Latte 3:
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
Tłumaczenia
TranslatorExtension dodaje tagi tłumaczeń {_'text'}, nowy parzysty {translate}...{/translate} oraz
filtr |translate.
Stary kod dla Latte 2:
$latte->addFilter('translate', [$translator, 'translate']);
Nowy kod dla Latte 3:
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
W presenterach jest aktywowane automatycznie przez ustawienie translatora w szablonie metodą
$template->setTranslator($translator). Bez tego tagi tłumaczeń nie będą dostępne i trzeba zarejestrować
rozszerzenie ręcznie albo w pliku konfiguracyjnym.
Plik konfiguracyjny
W Latte 2 można było rejestrować nowe tagi w pliku konfiguracyjnym w sekcji
latte › macros. W wersji 3 dodaje się w ten sposób całe rozszerzenia:
latte:
extensions:
- App\Templating\LatteExtension
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Tworzysz dodatek do Latte?
W swojej bibliotece możesz jednocześnie wspierać obie wersje Latte. Do wykrycia wersji najlepiej użyć stałej
Latte\Engine::VERSION i rozdzielić użycie onCompile[] i addMacro() od nowego
addExtension():
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
// inicjalizacja Latte 2
$this->latte->onCompile[] = function ($latte) {
$latte->addMacro(/* ... */);
};
} else {
// inicjalizacja Latte 3
$this->latte->addExtension(/* ... */);
}
Jako przykład spróbujmy przepisać następujący kod przeznaczony dla Latte 2 do postaci dla Latte 3:
// stary kod dla 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 rozszerza się za pomocą rozszerzeń. Trywialne rozszerzenie
dodające tag foo wyglądałoby tak:
// nowy kod dla Latte 3
class FooExtension extends Latte\Extension
{
public function getTags(): array
{
return [
'foo' => [FooNode::class, 'create'], // klasę FooNode dodamy za chwilę
];
}
}
// rejestracja
$this->latte->addExtension(new FooExtension);
Nowy kompilator jest solidniejszy, nie zawiera dotychczasowych skrótów, więc napisanie makra zajmuje nieco więcej wierszy kodu. Na przykład nie możemy bezpośrednio przekazać łańcucha z kodem PHP jak w Latte 2, zamiast tego tworzymy funkcję. Przypomnijmy, że w Latte 2 funkcja wyglądałaby mniej więcej tak:
// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});
Mimo to Latte 3 postępuje bardzo podobnie, tylko MacroNode nazywa się Latte\Compiler\Tag, a
PhpWriter to Latte\Compiler\PrintContext. Co jednak najważniejsze, dochodzi jeden krok pośredni:
funkcja nie zwraca kodu PHP bezpośrednio, lecz zwraca węzeł, czyli potomka StatementNode, który następnie jest
częścią drzewa AST. A ten węzeł ma metodę print(Latte\Compiler\PrintContext $context): string, która zwraca
kod 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 ...'); // zwraca kod PHP
}
}
Ponadto maska w $context->format() nie ma już skrótów %node.***, zakłada się, że najpierw sparsujesz zawartość tagu. Używamy więc parsera,
aby rozłożyć zawartość na zmienne (podwęzły), a potem ją wypisujemy:
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;
// parsowanie zawartości tagu
$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,
);
}
}
Na koniec dodamy metodę getIterator(), aby umożliwić przechodzenie po podwęzłach przy trawersowaniu:
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
...
public function &getIterator(): \Generator
{
yield $this->subject;
yield $this->args;
}
}