Шаблони
Nette використовує систему шаблонів Latte. По-перше, тому що це найбезпечніша система шаблонів для PHP, а по-друге, вона також є найінтуїтивнішою. Вам не потрібно вивчати багато нового, достатньо знань PHP та кількох тегів.
Зазвичай сторінка складається з шаблону layout + шаблону для конкретної
дії. Ось як може виглядати шаблон layout, зверніть увагу на блоки
{block}
та тег {include}
:
<!DOCTYPE html>
<html>
<head>
<title>{block title}My App{/block}</title>
</head>
<body>
<header>...</header>
{include content}
<footer>...</footer>
</body>
</html>
А це буде шаблон дії:
{block title}Homepage{/block}
{block content}
<h1>Homepage</h1>
...
{/block}
Він визначає блок content
, який буде вставлено замість
{include content}
у layout, а також перевизначає блок title
, який
перезапише {block title}
у layout. Спробуйте уявити результат.
Пошук шаблонів
Вам не потрібно вказувати в presenter'ах, який шаблон потрібно відобразити, фреймворк сам визначить шлях і заощадить вам час на написання коду.
Якщо ви використовуєте структуру каталогів, де кожен presenter має
власний каталог, просто розмістіть шаблон у цьому каталозі під назвою
дії (або view), тобто для дії default
використовуйте шаблон
default.latte
:
app/ └── Presentation/ └── Home/ ├── HomePresenter.php └── default.latte
Якщо ви використовуєте структуру, де presenter'и знаходяться разом в
одному каталозі, а шаблони — у папці templates
, збережіть його або у
файлі <Presenter>.<view>.latte
, або <Presenter>/<view>.latte
:
app/ └── Presenters/ ├── HomePresenter.php └── templates/ ├── Home.default.latte ← 1-й варіант └── Home/ └── default.latte ← 2-й варіант
Каталог templates
також може знаходитись на рівень вище, тобто на
тому ж рівні, що й каталог із класами presenter'ів.
Якщо шаблон не знайдено, presenter відповість помилкою 404 – сторінку не знайдено.
View можна змінити за допомогою $this->setView('іншийView')
. Також можна
безпосередньо вказати файл шаблону за допомогою
$this->template->setFile('/path/to/template.latte')
.
Файли, де шукаються шаблони, можна змінити, перевизначивши метод formatTemplateFiles(), який повертає масив можливих імен файлів.
Пошук шаблону layout
Nette також автоматично шукає файл layout.
Якщо ви використовуєте структуру каталогів, де кожен presenter має власний каталог, розмістіть layout або в папці з presenter'ом, якщо він специфічний лише для нього, або на рівень вище, якщо він спільний для кількох presenter'ів:
app/ └── Presentation/ ├── @layout.latte ← спільний layout └── Home/ ├── @layout.latte ← тільки для presenter'а Home ├── HomePresenter.php └── default.latte
Якщо ви використовуєте структуру, де presenter'и знаходяться разом в
одному каталозі, а шаблони — у папці templates
, layout очікуватиметься
в таких місцях:
app/ └── Presenters/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← спільний layout ├── Home.@layout.latte ← тільки для Home, 1-й варіант └── Home/ └── @layout.latte ← тільки для Home, 2-й варіант
Якщо presenter знаходиться в модулі, пошук буде здійснюватися також на вищих рівнях каталогів, відповідно до вкладеності модуля.
Назву layout можна змінити за допомогою $this->setLayout('layoutAdmin')
, і
тоді він очікуватиметься у файлі @layoutAdmin.latte
. Також можна
безпосередньо вказати файл шаблону layout за допомогою
$this->setLayout('/path/to/template.latte')
.
За допомогою $this->setLayout(false)
або тегу {layout none}
всередині
шаблону пошук layout вимикається.
Файли, де шукаються шаблони layout, можна змінити, перевизначивши метод formatLayoutTemplateFiles(), який повертає масив можливих імен файлів.
Змінні в шаблоні
Змінні передаються в шаблон шляхом запису їх у $this->template
,
після чого вони стають доступними в шаблоні як локальні змінні:
$this->template->article = $this->articles->getById($id);
Таким чином, ми можемо легко передавати будь-які змінні в шаблони. Однак при розробці надійних додатків корисніше обмежити себе. Наприклад, явно визначивши перелік змінних, які очікує шаблон, та їхні типи. Завдяки цьому PHP зможе перевіряти типи, IDE правильно підказуватиме, а статичний аналіз виявлятиме помилки.
А як визначити такий перелік? Просто у вигляді класу та його
властивостей. Назвемо його подібно до presenter'а, але з Template
на кінці:
/**
* @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;
// та інші змінні
}
Об'єкт $this->template
у presenter'і тепер буде екземпляром класу
ArticleTemplate
. Таким чином, PHP перевірятиме оголошені типи під час
запису. А починаючи з версії PHP 8.2, він також попереджатиме про запис у
неіснуючу змінну; у попередніх версіях цього можна досягти за
допомогою трейту Nette\SmartObject.
Анотація @property-read
призначена для IDE та статичного аналізу,
завдяки їй працюватиме автодоповнення, див. PhpStorm and code completion for
$this->template.

Розкішшю автодоповнення можна насолоджуватися і в шаблонах, достатньо встановити плагін для Latte в PhpStorm та вказати на початку шаблону назву класу, більше в статті Latte: як працювати з системою типів:
{templateType App\Presentation\Article\ArticleTemplate}
...
Так само працюють і шаблони в компонентах, достатньо дотримуватися
конвенції іменування і для компонента, наприклад, FifteenControl
створити клас шаблону FifteenTemplate
.
Якщо вам потрібно створити $template
як екземпляр іншого класу,
використовуйте метод createTemplate()
:
public function renderDefault(): void
{
$template = $this->createTemplate(SpecialTemplate::class);
$template->foo = 123;
// ...
$this->sendTemplate($template);
}
Змінні за замовчуванням
Presenter'и та компоненти автоматично передають у шаблони кілька корисних змінних:
$basePath
— це абсолютний URL-шлях до кореневого каталогу (наприклад,/eshop
)$baseUrl
— це абсолютний URL до кореневого каталогу (наприклад,http://localhost/eshop
)$user
— це об'єкт, що представляє користувача$presenter
— це поточний presenter$control
— це поточний компонент або presenter$flashes
— масив повідомлень, надісланих функцієюflashMessage()
Якщо ви використовуєте власний клас шаблону, ці змінні будуть передані, якщо ви створите для них властивості.
Створення посилань
У шаблоні посилання на інші presenter'и та дії створюються таким чином:
<a n:href="Product:show">деталі продукту</a>
Атрибут n:href
дуже зручний для HTML-тегів <a>
. Якщо ми
хочемо вивести посилання в іншому місці, наприклад, у тексті,
використовуємо {link}
:
Адреса: {link Home:default}
Більше інформації ви знайдете в розділі Створення URL-посилань.
Власні фільтри, теги тощо.
Систему шаблонів Latte можна розширити власними фільтрами, функціями,
тегами тощо. Це можна зробити безпосередньо в методі render<View>
або beforeRender()
:
public function beforeRender(): void
{
// додавання фільтра
$this->template->addFilter('foo', /* ... */);
// або конфігуруємо безпосередньо об'єкт Latte\Engine
$latte = $this->template->getLatte();
$latte->addFilterLoader(/* ... */);
}
Latte версії 3 пропонує більш просунутий спосіб, а саме створення extension для кожного веб-проекту. Приклад такого класу:
namespace App\Presentation\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()),
// ...
];
}
// ...
}
Зареєструємо його за допомогою конфігурації:
latte:
extensions:
- App\Presentation\Accessory\LatteExtension
Переклад
Якщо ви програмуєте багатомовний додаток, вам, швидше за все,
знадобиться виводити деякі тексти в шаблоні різними мовами. Для цього
Nette Framework визначає інтерфейс для перекладу Nette\Localization\Translator, який має
єдиний метод translate()
. Він приймає повідомлення $message
, яке
зазвичай є рядком, та будь-які інші параметри. Завдання полягає в тому,
щоб повернути перекладений рядок. У Nette немає реалізації за
замовчуванням, ви можете вибрати відповідно до своїх потреб з кількох
готових рішень, які можна знайти на Componette. У їхній документації ви
дізнаєтеся, як налаштувати перекладач.
Шаблонам можна встановити перекладач, який ми передамо, за допомогою
методу setTranslator()
:
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator);
}
Перекладач також можна налаштувати за допомогою конфігурації:
latte:
extensions:
- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
Після цього перекладач можна використовувати, наприклад, як фільтр
|translate
, включаючи додаткові параметри, які передаються методу
translate()
(див. foo, bar
):
<a href="basket">{='Кошик'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>
Або як тег з підкресленням:
<a href="basket">{_'Кошик'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>
Для перекладу частини шаблону існує парний тег {translate}
(з Latte 2.11,
раніше використовувався тег {_}
):
<a href="order">{translate}Замовлення{/translate}</a>
<a href="order">{translate foo, bar}Замовлення{/translate}</a>
Перекладач зазвичай викликається під час виконання при рендерингу шаблону. Однак Latte версії 3 може перекладати всі статичні тексти вже під час компіляції шаблону. Це економить продуктивність, оскільки кожен рядок перекладається лише один раз, а результат перекладу записується в скомпільовану форму. У каталозі кешу таким чином створюється кілька скомпільованих версій шаблону, по одній для кожної мови. Для цього достатньо лише вказати мову як другий параметр:
protected function beforeRender(): void
{
// ...
$this->template->setTranslator($translator, $lang);
}
Статичним текстом мається на увазі, наприклад, {_'hello'}
або
{translate}hello{/translate}
. Нестатичні тексти, такі як {_$foo}
,
продовжуватимуть перекладатися під час виконання.