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

Роскошью автодополнения можно наслаждаться и в шаблонах, достаточно установить в PhpStorm плагин для Latte и указать в начале шаблона имя класса, подробнее в статье 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);
}
Переменные по умолчанию
Презентеры и компоненты автоматически передают в шаблоны несколько полезных переменных:
$basePath
— абсолютный URL-путь к корневому каталогу (например,/eshop
)$baseUrl
— абсолютный URL к корневому каталогу (например,http://localhost/eshop
)$user
— объект, представляющий пользователя$presenter
— текущий презентер$control
— текущий компонент или презентер$flashes
— массив сообщений, отправленных функциейflashMessage()
Если вы используете собственный класс шаблона, эти переменные передадутся, если вы создадите для них свойства.
Создание ссылок
В шаблоне ссылки на другие презентеры и действия создаются следующим образом:
<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 предлагает более продвинутый способ — создание расширения для каждого веб-проекта. Пример такого класса:
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}
,
по-прежнему будут переводиться во время выполнения.