Praktyki dla programistów
Instalacja
Najlepszym sposobem instalacji Latte jest Composer:
composer require latte/latte
Obsługiwane wersje PHP (dotyczy najnowszych wersji patch Latte):
| wersja | zgodna z PHP |
|---|---|
| Latte 3.1 | PHP 8.2 – 8.5 |
| Latte 3.0 | PHP 8.0 – 8.5 |
Jak wyrenderować szablon
Jak wyrenderować szablon? Wystarczy ten prosty kod:
$latte = new Latte\Engine;
// katalog cache
$latte->setCacheDirectory('/path/to/tempdir');
$params = [ /* template variables */ ];
// albo $params = new TemplateParameters(/* ... */);
// renderowanie na wyjście
$latte->render('template.latte', $params);
// albo renderowanie do zmiennej
$output = $latte->renderToString('template.latte', $params);
Parametrami mogą być tablice albo, jeszcze lepiej, obiekt, który zapewni kontrolę typów i podpowiadanie w edytorze.
Przykłady użycia znajdziesz też w repozytorium Latte examples.
Wydajność i cache
Szablony Latte są niezwykle szybkie, bo Latte kompiluje je bezpośrednio do kodu PHP i przechowuje w cache na dysku. Nie mają więc żadnego dodatkowego narzutu w porównaniu z szablonami napisanymi w czystym PHP.
Cache jest automatycznie odświeżany za każdym razem, gdy zmienisz plik źródłowy. Podczas tworzenia możesz więc wygodnie edytować szablony Latte i od razu widzieć zmiany w przeglądarce. W środowisku produkcyjnym tę funkcję można wyłączyć i oszczędzić odrobinę wydajności:
$latte->setAutoRefresh(false);
Po wdrożeniu na serwer produkcyjny wygenerowanie cache po raz pierwszy, zwłaszcza przy większych aplikacjach, może zrozumiale chwilę potrwać. Latte ma wbudowaną ochronę przed cache stampede. To sytuacja, w której serwer otrzymuje dużą liczbę równoczesnych żądań i ponieważ cache Latte jeszcze nie istnieje, wszystkie zaczęłyby generować go naraz. Co powoduje skok obciążenia CPU. Latte jest sprytne i przy wielu równoczesnych żądaniach cache generuje tylko pierwszy wątek, a pozostałe czekają i potem z niego korzystają.
Cache możesz też wygenerować z wyprzedzeniem przy wdrożeniu (na przykład w skrypcie deploya) metodą
Engine::warmupCache(). Kompiluje ona podany szablon do cache zawczasu, dzięki czemu pierwszy odwiedzający nie musi
czekać: $latte->warmupCache('template.latte').
Sposoby rozszerzania Latte
Latte można dostosować na kilka sposobów, od prostych pomocników po zupełnie nowe konstrukcje językowe. Strona rozszerzanie Latte omawia je szczegółowo; tutaj szybki przegląd:
- Własne filtry: do formatowania lub przekształcania danych w
wyniku szablonu (np.
{$var|myFilter}). - Własne funkcje: do własnej logiki wywoływanej w
wyrażeniach szablonu (np.
{myFunction($arg)}). - Własne tagi: do zupełnie nowych konstrukcji językowych
(
{mytag}...{/mytag}albon:mytag). - Compiler passy: funkcje modyfikujące AST szablonu między parsowaniem a wygenerowaniem kodu PHP (na przykład optymalizacje albo kontrole bezpieczeństwa).
- Własne loadery: do zmiany sposobu, w jaki Latte odnajduje i wczytuje pliki szablonów.
Jeśli chcesz wykorzystywać swoje rozszerzenia w różnych projektach albo udostępnić je innym, spakuj je w klasę Latte Extension.
Parametry jako klasa
Lepiej niż przekazywać zmienne do szablonu jako tablice jest utworzyć klasę. Zyskujesz zapis bezpieczny typowo, wygodne podpowiadanie w IDE i możliwość rejestrowania filtrów oraz funkcji.
class MailTemplateParameters
{
public function __construct(
public string $lang,
public Address $address,
public string $subject,
public array $items,
public ?float $price = null,
) {}
}
$latte->render('mail.latte', new MailTemplateParameters(
lang: $this->lang,
subject: $title,
price: $this->getPrice(),
items: [],
address: $userAddress,
));
Wyłączenie automatycznego escapowania zmiennej
Jeśli zmienna zawiera łańcuch HTML, możesz ją oznaczyć tak, aby Latte nie escapowało jej automatycznie (a więc
podwójnie). Unikniesz dzięki temu podawania |noescape w szablonie.
Najprościej opakować łańcuch w obiekt Latte\Runtime\Html:
$params = [
'articleBody' => new Latte\Runtime\Html($article->htmlBody),
];
Latte nie escapuje też wszystkich obiektów implementujących interfejs Latte\Runtime\HtmlStringable. Możesz
więc utworzyć własną klasę, której metoda __toString() zwróci kod HTML, który nie zostanie automatycznie
zescapowany:
class Emphasis implements Latte\Runtime\HtmlStringable
{
public function __construct(
private string $str,
) {
}
public function __toString(): string
{
return '<em>' . htmlspecialchars($this->str) . '</em>';
}
}
$params = [
'foo' => new Emphasis('hello'),
];
Metoda __toString musi zwracać poprawny HTML i zapewniać escapowanie parametrów, w przeciwnym
razie może powstać podatność XSS!
Jak rozszerzyć Latte o filtry, tagi itd.
Jak dodać do Latte własny filtr, funkcję, tag itd.? Dowiesz się w rozdziale rozszerzanie Latte. Jeśli chcesz wykorzystywać swoje zmiany w różnych projektach albo udostępnić je innym, powinieneś następnie utworzyć rozszerzenie.
Dowolny kod w szablonie {php ...}
Wewnątrz tagu {do} można zapisywać wyłącznie wyrażenia
PHP, nie da się więc wstawić na przykład konstrukcji w rodzaju if ... else ani instrukcji zakończonych
średnikiem.
Możesz jednak zarejestrować rozszerzenie RawPhpExtension, które dodaje tag {php ...}. Za jego
pomocą wstawisz dowolny kod PHP. Nie podlega on żadnym regułom trybu sandbox, więc jego użycie jest na odpowiedzialność
autora szablonu.
$latte->addExtension(new Latte\Essential\RawPhpExtension);
Kontrola wygenerowanego kodu
Latte kompiluje szablony do kodu PHP. Oczywiście dba o to, aby wygenerowany kod był poprawny składniowo. Przy używaniu
rozszerzeń innych autorów albo RawPhpExtension Latte nie może jednak zagwarantować poprawności wygenerowanego
pliku. W PHP da się też napisać kod poprawny składniowo, ale zabroniony (na przykład przypisanie wartości do zmiennej
$this), powodujący PHP Compile Error. Jeśli zapiszesz taką operację w szablonie, trafi ona również do
wygenerowanego kodu PHP. Ponieważ różnych zabronionych operacji jest w PHP ponad dwieście, Latte nie stawia sobie za cel ich
wykrywania. Zgłosi je samo PHP przy renderowaniu, co zwykle nie stanowi problemu.
Bywają jednak sytuacje, w których chcesz już przy kompilacji szablonu wiedzieć, że nie zawiera on żadnych PHP Compile
Error. Zwłaszcza gdy szablony mogą edytować użytkownicy albo gdy używasz Sandboxa. W takim przypadku każ sprawdzać szablony podczas kompilacji. Tę
funkcjonalność włączysz metodą Engine::enablePhpLinter(). Ponieważ do sprawdzenia musi wywołać binarkę PHP,
przekaż jej ścieżkę jako parametr:
$latte = new Latte\Engine;
$latte->enablePhpLinter('/path/to/php');
try {
$latte->compile('home.latte');
} catch (Latte\CompileException $e) {
// wyłapuje błędy Latte, a także Compile Error w PHP
echo 'Error: ' . $e->getMessage();
}
Locale
Latte pozwala ustawić locale, które wpływa na formatowanie liczb, dat i sortowanie. Ustawia się je metodą
setLocale(). Identyfikator locale jest zgodny ze standardem IETF language tag, którego używa rozszerzenie PHP
intl. Składa się z kodu języka i ewentualnie kodu kraju, na przykład en_US dla angielskiego w
Stanach Zjednoczonych, de_DE dla niemieckiego w Niemczech itd.
$latte = new Latte\Engine;
$latte->setLocale('en_US');
Ustawienie locale wpływa na filtry localDate, sort, number i bytes.
Wymaga rozszerzenia PHP intl. Ustawienie w Latte nie wpływa na globalne ustawienie locale
w PHP.
Tryb ścisły
W trybie ścisłego parsowania Latte sprawdza brakujące zamykające tagi HTML, a także wyłącza możliwość używania
zmiennej $this. Aby go włączyć:
$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::StrictParsing);
Aby generować szablony z nagłówkiem declare(strict_types=1), zrób tak:
$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::StrictTypes);
Od Latte 3.1 ścisłe typy są włączone domyślnie. Możesz je wyłączyć przez
$latte->setFeature(Latte\Feature::StrictTypes, false).
Ostrzeżenia migracyjne
Latte 3.1 zmienia zachowanie niektórych atrybutów HTML. Na
przykład wartości null teraz usuwają atrybut zamiast wypisywać pusty łańcuch. Aby łatwo znaleźć miejsca, w
których ta zmiana dotyka Twoich szablonów, możesz włączyć ostrzeżenia migracyjne:
$latte->setFeature(Latte\Feature::MigrationWarnings);
Po włączeniu Latte sprawdza renderowane atrybuty i zgłasza ostrzeżenie użytkownika (E_USER_WARNING), jeśli
wynik różni się od tego, co wyprodukowałoby Latte 3.0. Gdy natkniesz się na ostrzeżenie, zastosuj jedno z rozwiązań:
- Jeśli nowy wynik jest w Twoim przypadku poprawny (np. wolisz, aby atrybut przy
nullznikał), wycisz ostrzeżenie, dodając filtr|accept - Jeśli chcesz, aby atrybut renderował się jako pusty (np.
title=""), zamiast znikać, gdy zmienna ma wartośćnull, podaj pusty łańcuch jako wartość awaryjną:title={$val ?? ''} - Jeśli koniecznie potrzebujesz starego zachowania (np. wypisywania
"1"dlatruezamiast"true"), rzutuj wartość jawnie na łańcuch:data-foo={(string) $val}
Po rozwiązaniu wszystkich ostrzeżeń wyłącz ostrzeżenia migracyjne i usuń wszystkie filtry |accept
z szablonów, bo nie są już potrzebne.
Zmienne pętli w zasięgu lokalnym
Domyślnie zmienne zdefiniowane w pętli {foreach} (jak $key i $value) pozostają
dostępne po jej zakończeniu – tak samo jak w samym PHP. Może to prowadzić do niezamierzonego nadpisania zmiennych, gdy
zmienna pętli ma taką samą nazwę jak istniejąca zmienna szablonu.
Funkcja ScopedLoopVariables ogranicza zasięg zmiennych pętli do jej ciała. Po zakończeniu pętli pierwotna
wartość zmiennej zostaje przywrócona (jeśli istniała wcześniej), a w przeciwnym razie zmienna zostaje usunięta:
$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::ScopedLoopVariables);
Przykład różnicy:
{var $item = 'original'}
{foreach [1, 2] as $item}{$item}, {/foreach}
{$item}
Bez ScopedLoopVariables: wypisze 1, 2, 2 (zmienna zostaje nadpisana) Z
ScopedLoopVariables: wypisze 1, 2, original (zmienna zostaje przywrócona)
Działa to również przy składni z destrukturyzacją, np. {foreach $array as [$a, $b]}.
Zmienne pętli używające referencji ({foreach $array as &$value}) albo przypisania do
właściwości ({foreach $array as $obj->prop}) nie są ograniczane zasięgiem, bo zniweczyłoby to ich zamierzone
działanie.
Automatyczne usuwanie wcięć
Przy używaniu tagów parzystych, takich jak {if}, {foreach} czy {block}, często
wcinasz zagnieżdżoną treść dla czytelności. Domyślnie to wcięcie trafia jednak do wygenerowanego wyniku. Funkcja
Dedent automatycznie je usuwa, dzięki czemu wynik pozostaje czysty niezależnie od tego, jak głęboko
zagnieżdżasz tagi Latte:
$latte = new Latte\Engine;
$latte->setFeature(Latte\Feature::Dedent);
Przykład:
{if true}
Hello
World
{/if}
Bez Dedent wynik zawierałby wcięcia (\tHello\n\tWorld\n). Z Dedent wcięcia są
usuwane, a wynikiem jest Hello\nWorld\n.
Głębsze wcięcie wewnątrz bloku jest zachowywane względem wcięcia bazowego:
{if true}
Hello
Indented
{/if}
Wynik: Hello\n\tIndented\n.
Wcięcia wewnątrz bloku muszą być spójne (albo tabulatory, albo spacje). Jeśli zostaną wymieszane, Latte zgłosi wyjątek
Inconsistent indentation.
Tłumaczenie w szablonach
Aby dodać do szablonu {_...}, {translate} i filtr translate, użyj rozszerzenia
TranslatorExtension. Służą one do tłumaczenia wartości lub części szablonu na inne języki. Parametrem jest
callable wykonujący tłumaczenie albo obiekt typu Nette\Localization\Translator (podaj null, aby
wyłączyć tłumaczenia):
class MyTranslator
{
public function __construct(private string $lang)
{}
public function translate(string $original): string
{
// tworzymy $translated z $original zgodnie z $this->lang
return $translated;
}
}
$translator = new MyTranslator($lang);
$extension = new Latte\Essential\TranslatorExtension(
$translator->translate(...), // [$translator, 'translate'] w PHP 8.0
);
$latte->addExtension($extension);
Translator jest wywoływany w czasie działania, przy renderowaniu szablonu. Latte potrafi jednak przetłumaczyć wszystkie teksty statyczne już podczas kompilacji szablonu. Oszczędza to wydajność, bo każdy łańcuch tłumaczony jest tylko raz, a powstałe tłumaczenie zapisywane jest do skompilowanego pliku. W katalogu cache powstaje wtedy kilka skompilowanych wersji szablonu, po jednej na język. Wystarczy do tego podać język jako drugi parametr:
$extension = new Latte\Essential\TranslatorExtension(
$translator->translate(...),
$lang,
);
Przez tekst statyczny rozumiemy na przykład {_'hello'} albo {translate}hello{/translate}. Teksty
niestatyczne, takie jak {_$foo}, nadal będą tłumaczone w czasie działania.
Szablon może też przekazać translatorowi dodatkowe parametry przez {_$original, foo: bar} albo
{translate foo: bar}, które otrzyma on jako tablicę $params:
public function translate(string $original, ...$params): string
{
// $params['foo'] === 'bar'
}
Debugowanie i Tracy
Latte stara się, aby tworzenie aplikacji było jak najprzyjemniejsze. Do celów debugowania służą trzy tagi: {dump}, {debugbreak} i {trace}.
Największy komfort uzyskasz, instalując świetne narzędzie do debugowania Tracy i aktywując plugin do Latte:
// włącza Tracy
Tracy\Debugger::enable();
$latte = new Latte\Engine;
// aktywuje rozszerzenie Tracy
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
Wszystkie błędy zobaczysz teraz na zgrabnym czerwonym ekranie, w tym błędy w szablonach z podświetleniem wiersza i kolumny (wideo). Jednocześnie w prawym dolnym rogu, w tzw. pasku Tracy, pojawi się zakładka Latte, gdzie przejrzyście zobaczysz wszystkie wyrenderowane szablony i ich zależności (łącznie z możliwością kliknięcia w szablon albo skompilowany kod), a także zmienne:

Ponieważ Latte kompiluje szablony do czytelnego kodu PHP, możesz wygodnie krokować po nich w swoim IDE.
Linter: walidacja składni szablonu
Narzędzie Linter służy do walidacji wszystkich szablonów. Jego celem jest przeskanowanie wskazanych plików i upewnienie się, że nie zawierają błędów składniowych ani odwołań do nieistniejących tagów, filtrów, funkcji, klas czy podobnych konstrukcji.
Linter uruchamia się z wiersza poleceń:
vendor/bin/latte-lint <path>
Parametrem --strict włączysz tryb ścisły. Parametr --debug
wypisuje nazwę każdego przetwarzanego pliku i pełne szczegóły wyjątku, co pomaga przy szukaniu problemów.
Jeśli używasz własnych tagów, filtrów albo innych rozszerzeń Latte, musisz utworzyć własny wariant Lintera, na
przykład custom-latte-lint. W tym skrypcie rejestrujesz wszystkie potrzebne rozszerzenia, zanim dojdzie do
właściwej walidacji szablonów:
#!/usr/bin/env php
<?php
// podaj rzeczywistą ścieżkę do pliku autoload.php
require __DIR__ . '/vendor/autoload.php';
$path = $argv[1] ?? '.';
$linter = new Latte\Tools\Linter;
$latte = $linter->getEngine();
// tutaj dodaj swoje własne rozszerzenia
$latte->addExtension(/* ... */);
$ok = $linter->scanDirectory($path);
exit($ok ? 0 : 1);
Alternatywnie możesz przekazać Linterowi własny obiekt Latte\Engine:
$latte = new Latte\Engine;
// tutaj konfigurujemy obiekt $latte
$linter = new Latte\Tools\Linter(engine: $latte);
Powstałego, dostosowanego lintera można potem używać tak samo jak standardowego narzędzia, ale z pełną znajomością wszystkich Twoich własnych rozszerzeń.
Wczytywanie szablonów z łańcucha
Potrzebujesz wczytywać szablony z łańcuchów zamiast z plików, choćby do celów testowych? Pomoże Ci StringLoader:
$latte->setLoader(new Latte\Loaders\StringLoader([
'main.file' => '{include other.file}',
'other.file' => '{if true} {$var} {/if}',
]));
$latte->render('main.file', $params);
Handler wyjątków
Możesz zdefiniować własny handler dla oczekiwanych wyjątków. Trafiają do niego wyjątki zgłoszone wewnątrz {try} oraz w sandboxie.
$loggingHandler = function (Throwable $e, Latte\Runtime\Template $template) use ($logger) {
$logger->log($e);
};
$latte = new Latte\Engine;
$latte->setExceptionHandler($loggingHandler);
Automatyczne wyszukiwanie layoutu
Za pomocą tagu {layout}
szablon określa swój szablon nadrzędny. Można też sprawić, aby layout był wyszukiwany automatycznie, co uprości pisanie
szablonów, bo nie będą musiały zawierać tagu {layout}.
Osiąga się to tak:
// zwraca ścieżkę do pliku szablonu nadrzędnego
$finder = fn(Latte\Runtime\Template $template) => 'automatic.layout.latte';
$latte = new Latte\Engine;
$latte->addProvider('coreParentFinder', $finder);
Jeśli szablon nie ma mieć layoutu, zasygnalizuje to tagiem {layout none}.