Шаблоны

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}, по-прежнему будут переводиться во время выполнения.