Szablony
Nette wykorzystuje system szablonów Latte. Po pierwsze dlatego, że jest to najbezpieczniejszy system szablonowania dla PHP, a także najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy znajomość PHP i kilka tagów.
Często zdarza się, że strona składa się z szablonu układu + szablonu akcji. Na przykład tak może wyglądać szablon
układu, zauważ bloki {block}
i znacznik {include}
:
<!DOCTYPE html>
<html>
<head>
<title>{block title}My App{/block}</title>
</head>
<body>
<header>...</header>
{include content}
<footer>...</footer>
</body>
</html>
I to będzie szablon akcji:
{block title}Homepage{/block}
{block content}
<h1>Homepage</h1>
...
{/block}
Definiuje blok content
, który zostanie wstawiony w miejsce {include content}
w układzie, a także
redefiniuje blok title
, który zastąpi {block title}
w układzie. Spróbujcie sobie wyobrazić ten
rezultat.
Wyszukiwanie szablonów
W prezenterach nie trzeba określać, który szablon ma być renderowany; framework automatycznie określi ścieżkę, ułatwiając kodowanie.
Jeśli używasz struktury katalogów, w której każdy prezenter ma swój własny katalog, po prostu umieść szablon w tym
katalogu pod nazwą akcji (tj. widoku). Na przykład dla akcji default
należy użyć szablonu
default.latte
:
app/ └── UI/ └── Home/ ├── HomePresenter.php └── default.latte
Jeśli używasz struktury, w której prezenterzy znajdują się w jednym katalogu, a szablony w folderze
templates
, zapisz je w pliku <Presenter>.<view>.latte
lub
<Presenter>/<view>.latte
:
app/ └── Presenters/ ├── HomePresenter.php └── templates/ ├── Home.default.latte ← 1st variant └── Home/ └── default.latte ← 2nd variant
Katalog templates
może być również umieszczony o jeden poziom wyżej, na tym samym poziomie co katalog
z klasami prezenterów.
Jeśli szablon nie zostanie znaleziony, prezenter odpowie błędem 404 – nie znaleziono strony.
Widok można zmienić za pomocą $this->setView('anotherView')
. Możliwe jest również bezpośrednie
określenie pliku szablonu za pomocą $this->template->setFile('/path/to/template.latte')
.
Pliki, w których wyszukiwane są szablony, można zmienić, nadpisując metodę formatTemplateFiles(), która zwraca tablicę możliwych nazw plików.
Wyszukiwanie szablonu układu
Nette automatycznie wyszukuje również plik układu.
Jeśli używasz struktury katalogów, w której każdy prezenter ma swój własny katalog, umieść układ w folderze z prezenterem, jeśli jest on specyficzny tylko dla niego, lub poziom wyżej, jeśli jest wspólny dla wielu prezenterów:
app/ └── UI/ ├── @layout.latte ← common layout └── Home/ ├── @layout.latte ← only for Home presenter ├── HomePresenter.php └── default.latte
Jeśli używasz struktury, w której prezenterzy są zgrupowani w jednym katalogu, a szablony znajdują się w folderze
templates
, layout będzie oczekiwany w następujących miejscach:
app/ └── Presenters/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← common layout ├── Home.@layout.latte ← only for Home, 1st variant └── Home/ └── @layout.latte ← only for Home, 2nd variant
Jeśli prezenter znajduje się w module, będzie on również wyszukiwany dalej w drzewie katalogów zgodnie z zagnieżdżeniem modułu.
Nazwę layoutu można zmienić za pomocą $this->setLayout('layoutAdmin')
, a następnie będzie ona oczekiwana
w pliku @layoutAdmin.latte
. Można również bezpośrednio określić plik szablonu układu za pomocą
$this->setLayout('/path/to/template.latte')
.
Użycie $this->setLayout(false)
lub znacznika {layout none}
wewnątrz szablonu wyłącza
wyszukiwanie układu.
Pliki, w których przeszukiwane są szablony układu, można zmienić, nadpisując metodę formatLayoutTemplateFiles(), która zwraca tablicę możliwych nazw plików.
Zmienne w szablonie
Zmienne są przekazywane do szablonu poprzez zapisanie ich do $this->template
, a następnie są dostępne w
szablonie jako zmienne lokalne:
$this->template->article = $this->articles->getById($id);
W ten sposób możemy łatwo przekazać dowolne zmienne do szablonów. Jednak podczas tworzenia solidnych aplikacji często bardziej przydatne jest ograniczenie się. Na przykład poprzez jawne zdefiniowanie listy zmiennych, których oczekuje szablon i ich typów. Pozwoli to PHP na sprawdzanie typu, IDE na prawidłowe szeptanie, a analiza statyczna na wykrywanie błędów.
A jak zdefiniować taką wyliczankę? Po prostu w postaci klasy i jej właściwości. Nazwiemy go jak presenter, ale z
Template
na końcu:
/**
* @property-read ArticleTemplate $template
*/
class ArticlePresenter extends Nette\Application\UI\Presenter
{
}
class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
public Model\Article $article;
public Nette\Security\User $user;
// a další proměnné
}
Obiekt $this->template
w prezenterze będzie teraz instancją klasy ArticleTemplate
. Tak więc PHP
będzie sprawdzać zadeklarowane typy podczas pisania. A począwszy od PHP 8.2, będzie również ostrzegać przy zapisie do
nieistniejącej zmiennej; w poprzednich wersjach to samo można osiągnąć za pomocą cechy Nette\SmartObject.
Adnotacja @property-read
jest dla IDE i analizy statycznej, sprawi, że szeptanie będzie działać, zobacz PhpStorm i uzupełnianie kodu dla
$this->template.
Możesz również mieć luksus szeptania w szablonach, wystarczy zainstalować wtyczkę Latte w PhpStorm i umieścić nazwę klasy na początku szablonu, zobacz artykuł Latte: jak wpisać system, aby uzyskać więcej informacji:
{templateType App\UI\Article\ArticleTemplate}
...
W ten sam sposób działają szablony w komponentach, wystarczy zastosować konwencję nazewnictwa i stworzyć klasę szablonu
FifteenTemplate
dla komponentu np. FifteenControl
.
Jeśli potrzebujesz stworzyć $template
jako instancję innej klasy, użyj metody
createTemplate()
:
public function renderDefault(): void
{
$template = $this->createTemplate(SpecialTemplate::class);
$template->foo = 123;
// ...
$this->sendTemplate($template);
}
Zmienne domyślne
Prezentery i komponenty przekazują automatycznie kilka przydatnych zmiennych do szablonów:
$basePath
to bezwzględna ścieżka URL do katalogu głównego (np./eshop
)$baseUrl
to bezwzględny adres URL do katalogu głównego (np.http://localhost/eshop
)$user
jest obiektem reprezentującym użytkownika$presenter
jest obecnym prezenterem$control
jest bieżącym elementem lub prezenterem$flashes
jest tablicą wiadomości wysyłanych przez funkcjeflashMessage()
Jeśli używasz niestandardowej klasy szablonu, te zmienne zostaną przekazane, jeśli utworzysz dla nich właściwość.
Tworzenie linków
Szablon tworzy w ten sposób linki do innych prezenterów & wydarzeń:
<a n:href="Product:show">detail produktu</a>
Atrybut n:href
jest bardzo przydatny dla znaczników HTML <a>
. Jeśli chcemy wymienić link w
innym miejscu, na przykład w tekście, używamy {link}
:
Adresa je: {link Home:default}
Aby uzyskać więcej informacji, zobacz Tworzenie linków URL.
Niestandardowe filtry, tagi, itp.
System szablonów Latte może być rozszerzony o własne filtry, funkcje, tagi, itp. Można to zrobić bezpośrednio w
metodzie render<View>
lub beforeRender()
:
public function beforeRender(): void
{
// dodaj filtr
$this->template->addFilter('foo', /* ... */);
// lub skonfigurować bezpośrednio obiekt Latte\Engine
$latte = $this->template->getLatte();
$latte->addFilterLoader(/* ... */);
}
Latte w wersji 3 oferuje bardziej zaawansowany sposób tworzenia rozszerzenia dla każdego projektu internetowego. Oto krótki przykład takiej klasy:
namespace App\UI\Accessory;
final class LatteExtension extends Latte\Extension
{
public function __construct(
private App\Model\Facade $facade,
private Nette\Security\User $user,
// ...
) {
}
public function getFilters(): array
{
return [
'timeAgoInWords' => $this->filterTimeAgoInWords(...),
'money' => $this->filterMoney(...),
// ...
];
}
public function getFunctions(): array
{
return [
'canEditArticle' =>
fn($article) => $this->facade->canEditArticle($article, $this->user->getId()),
// ...
];
}
// ...
}
Rejestrujemy go za pomocą konfiguracji:
latte:
extensions:
- App\UI\Accessory\LatteExtension
Tłumaczenie
Jeśli programujesz wielojęzyczną aplikację, prawdopodobnie będziesz musiał wyprowadzić część tekstu w szablonie w
różnych językach. Aby to zrobić, Nette Framework definiuje interfejs tłumaczący Nette\Localization\Translator, który posiada
pojedynczą metodę translate()
. Przyjmuje ona komunikat $message
, który zwykle jest ciągiem znaków,
oraz dowolne inne parametry. Jej zadaniem jest zwrócenie przetłumaczonego ciągu znaków. W Nette nie ma domyślnej
implementacji, można wybrać według własnych potrzeb spośród kilku gotowych rozwiązań, które można znaleźć na Componette. Ich dokumentacja podpowiada, jak skonfigurować translator.
Szablony można skonfigurować z tłumaczem, którego będziemy mieli przekazanego, za pomocą metody
setTranslator()
:
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator);
}
Alternatywnie, tłumacz może być ustawiony za pomocą konfiguracji:
latte:
extensions:
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Translator może być wtedy użyty np. jako filtr |translate
, z dodatkowymi parametrami przekazywanymi do metody
translate()
(patrz foo, bar
):
<a href="basket">{='Basket'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>
Albo jako znacznik podkreślenia:
<a href="basket">{_'Basket'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>
Do tłumaczenia sekcji szablonu służy sparowany tag {translate}
(od wersji Latte 2.11, wcześniej używany był
tag {_}
):
<a href="order">{translate}Order{/translate}</a>
<a href="order">{translate foo, bar}Order{/translate}</a>
Translator jest domyślnie wywoływany w trybie runtime podczas renderowania szablonu. Latte w wersji 3 może jednak tłumaczyć cały tekst statyczny podczas kompilacji szablonu. Oszczędza to wydajność, ponieważ każdy ciąg jest tłumaczony tylko raz, a wynikowe tłumaczenie jest zapisywane do skompilowanej postaci. Tworzy to wiele skompilowanych wersji szablonu w katalogu cache, po jednej dla każdego języka. Aby to zrobić, musisz tylko określić język jako drugi parametr:
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator, $lang);
}
Przez tekst statyczny rozumiemy na przykład {_'hello'}
lub {translate}hello{/translate}
. Tekst
niestatyczny, taki jak {_$foo}
, będzie nadal kompilowany w locie.