Nette Documentation Preview

syntax
Шаблоны
*******

.[perex]
Nette использует систему шаблонов [Latte |latte:]. Latte используется потому, что это самая безопасная система шаблонов для PHP, и в то же время самая интуитивно понятная. Вам не нужно изучать много нового, достаточно знать PHP и несколько тегов Latte.

Обычно страница заполняется из шаблона макета + шаблона действия. Вот как может выглядеть шаблон макета, обратите внимание на блоки `{block}` и тег `{include}`:

```latte
<!DOCTYPE html>
<html>
<head>
	<title>{block title}Мое приложение{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>
```

А это может быть шаблоном действий:

```latte
{block title}Главная страница{/block}

{block content}
<h1>Главная страница</h1>
...
{/block}
```

Он определяет блок `content`, который вставляется вместо `{include content}` в макете, а также переопределяет блок `title`, который перезаписывает `{block title}` в макете. Попытайтесь представить себе результат.


Поиск шаблонов .[#toc-template-lookup]
--------------------------------------

В презентаторах вам не нужно указывать, какой шаблон должен быть отображен; фреймворк автоматически определит путь, облегчая вам кодирование.

Если вы используете структуру каталогов, в которой у каждого ведущего есть своя директория, просто поместите шаблон в эту директорию под именем действия (т. е. представления). Например, для действия `default` используйте шаблон `default.latte`:

/--pre
app/
└── UI/
    └── Home/
        ├── HomePresenter.php
        └── <b>default.latte</b>
\--

Если вы используете структуру, в которой ведущие находятся в одном каталоге, а шаблоны - в папке `templates`, сохраните их либо в файле `<Presenter>.<view>.latte` или `<Presenter>/<view>.latte`:

/--pre
app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── <b>Home.default.latte</b>  ← 1st variant
        └── <b>Home/</b>
            └── <b>default.latte</b>   ← 2nd variant
\--

Каталог `templates` можно также разместить на один уровень выше, на том же уровне, что и каталог с классами ведущих.

Если шаблон не найден, ведущий отвечает [ошибкой 404 - страница не найдена |presenters#Error 404 etc].

Изменить вид можно с помощью `$this->setView('anotherView')`. Также можно напрямую указать файл шаблона с помощью `$this->template->setFile('/path/to/template.latte')`.

.[note]
Файлы, в которых производится поиск шаблонов, можно изменить, переопределив метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], который возвращает массив возможных имен файлов.


Поиск шаблонов макета .[#toc-layout-template-lookup]
----------------------------------------------------

Nette также автоматически ищет файл макета.

Если вы используете структуру каталогов, в которой у каждого ведущего есть своя директория, поместите макет либо в папку с ведущим, если он предназначен только для него, либо на уровень выше, если он общий для нескольких ведущих:

/--pre
app/
└── UI/
    ├── <b>@layout.latte</b>           ← common layout
    └── Home/
        ├── <b>@layout.latte</b>       ← only for Home presenter
        ├── HomePresenter.php
        └── default.latte
\--

Если вы используете структуру, в которой ведущие сгруппированы в одном каталоге, а шаблоны находятся в папке `templates`, макет будет находиться в следующих местах:

/--pre
app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── <b>@layout.latte</b>       ← common layout
        ├── <b>Home.@layout.latte</b>  ← only for Home, 1st variant
        └── <b>Home/</b>
            └── <b>@layout.latte</b>   ← only for Home, 2nd variant
\--

Если ведущий находится в [модуле |modules], он также будет искать дальше по дереву каталогов в соответствии с вложенностью модуля.

Имя макета можно изменить с помощью `$this->setLayout('layoutAdmin')`, и тогда оно будет ожидаться в файле `@layoutAdmin.latte`. Вы также можете напрямую указать файл шаблона макета с помощью `$this->setLayout('/path/to/template.latte')`.

Использование тега `$this->setLayout(false)` или `{layout none}` внутри шаблона отключает поиск макета.

.[note]
Файлы, в которых производится поиск шаблонов макетов, можно изменить, переопределив метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], который возвращает массив возможных имен файлов.


Переменные в шаблоне .[#toc-variables-in-the-template]
------------------------------------------------------

Переменные передаются в шаблон путем записи их в `$this->template`, а затем они доступны в шаблоне как локальные переменные:

```php
$this->template->article = $this->articles->getById($id);
```

Таким образом, мы можем легко передавать любые переменные в шаблоны. Однако при разработке надежных приложений часто полезнее ограничить себя. Например, путем явного определения списка переменных, которые ожидает шаблон, и их типов. Это позволит PHP проверять типы, IDE - правильно шептать, а статический анализ - обнаруживать ошибки.

И как определить такое перечисление? Просто в виде класса и его свойств. Мы назовем его как presenter, но с `Template` в конце:

```php
/**
 * @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 |utils:smartobject].

Аннотация `@property-read` предназначена для IDE и статического анализа, она заставит работать шепот, см. "PhpStorm и завершение кода для $this->template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template.

[* phpstorm-completion.webp *]

Вы также можете позволить себе роскошь шептать в шаблонах, просто установите плагин Latte в PhpStorm и поместите имя класса в начало шаблона, более подробную информацию смотрите в статье "Latte: как набирать систему":https://blog.nette.org/ru/latte-kak-ispol-zovat-sistemu-tipov:

```latte
{templateType App\UI\Article\ArticleTemplate}
...
```

Таким же образом шаблоны работают в компонентах, просто следуйте соглашению об именовании и создайте класс шаблона `FifteenTemplate` для компонента, например, `FifteenControl`.

Если вам нужно создать `$template` как экземпляр другого класса, используйте метод `createTemplate()`:

```php
public function renderDefault(): void
{
	$template = $this->createTemplate(SpecialTemplate::class);
	$template->foo = 123;
	// ...
	$this->sendTemplate($template);
}
```


Переменные по умолчанию .[#toc-default-variables]
-------------------------------------------------

Презентаторы и компоненты автоматически передают шаблонам несколько полезных переменных:

- `$basePath` - абсолютный URL-путь к корневому каталогу (например, `/eshop`).
- `$baseUrl` - это абсолютный URL-адрес корневого каталога (например. `http://localhost/eshop`)
- `$user` - это объект, [представляющий пользователя |security:authentication].
- `$presenter` - нынешний ведущий
- `$control` - текущий компонент или ведущий
- `$flashes` - это массив [сообщений |presenters#flash-messages], отправленных функциями `flashMessage()`

Если вы используете пользовательский класс шаблона, эти переменные будут переданы, если вы создадите для них свойство.


Создание ссылок .[#toc-creating-links]
--------------------------------------

Шаблон создает таким образом ссылки на других ведущих и мероприятия:

```latte
<a n:href="Product:show">detail produktu</a>
```

Атрибут `n:href` очень удобен для HTML-тегов. `<a>`. Если мы хотим указать ссылку в другом месте, например, в тексте, мы используем `{link}`:

```latte
Adresa je: {link Home:default}
```

Дополнительные сведения см. в разделе [Создание ссылок URL |creating-links].


Пользовательские фильтры, теги и т.д. .[#toc-custom-filters-tags-etc]
---------------------------------------------------------------------

Система шаблонов Latte может быть расширена с помощью пользовательских фильтров, функций, тегов и т.д. Это можно сделать непосредственно в методе `render<View>` или `beforeRender()`:

```php
public function beforeRender(): void
{
	// přidání filtru
	$this->template->addFilter('foo', /* ... */);

	// nebo konfigurujeme přímo objekt Latte\Engine
	$latte = $this->template->getLatte();
	$latte->addFilterLoader(/* ... */);
}
```

Latte версии 3 предлагает более продвинутый способ создания [расширения |latte:creating-extension] для каждого веб-проекта. Вот краткий пример такого класса:

```php
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()),
			// ...
		];
	}

	// ...
}
```

Мы регистрируем его с помощью [конфигурации |configuration#Latte]:

```neon
latte:
	extensions:
		- App\UI\Accessory\LatteExtension
```


Перевод .[#toc-translating]
---------------------------

Если вы программируете многоязычное приложение, вам, вероятно, потребуется вывести часть текста в шаблоне на разных языках. Для этого в Nette Framework определен интерфейс перевода [api:Nette\Localization\Translator], который имеет единственный метод `translate()`. Он принимает сообщение `$message`, которое обычно является строкой, и любые другие параметры. Задача состоит в том, чтобы вернуть переведенную строку.
В Nette нет реализации по умолчанию, вы можете выбрать в соответствии с вашими потребностями из нескольких готовых решений, которые можно найти на [Componette |https://componette.org/search/localization]. В их документации описано, как настроить транслятор.

Шаблоны могут быть настроены с переводчиком, который [мы передадим нам |dependency-injection:passing-dependencies], используя метод `setTranslator()`:

```php
protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator);
}
```

В качестве альтернативы переводчик можно установить с помощью [конфигурации |configuration#Latte]:

```neon
latte:
	extensions:
		- Latte\Essential\TranslatorExtension
```

Транслятор можно использовать, например, в качестве фильтра `|translate`, передав дополнительные параметры в метод `translate()` (см. `foo, bar`):

```latte
<a href="basket">{='Basket'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>
```

Или как тег подчеркивания:

```latte
<a href="basket">{_'Basket'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>
```

Для перевода раздела шаблона существует парный тег `{translate}` (начиная с версии Latte 2.11, ранее использовался тег `{_}` ):

```latte
<a href="order">{translate}Order{/translate}</a>
<a href="order">{translate foo, bar}Order{/translate}</a>
```

Translator по умолчанию вызывается во время выполнения при рендеринге шаблона. Latte версии 3, однако, может переводить весь статический текст во время компиляции шаблона. Это экономит производительность, поскольку каждая строка переводится только один раз, а результирующий перевод записывается в скомпилированную форму. При этом в каталоге кэша создается несколько скомпилированных версий шаблона, по одной для каждого языка. Для этого достаточно указать язык в качестве второго параметра:

```php
protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator, $lang);
}
```

Под статическим текстом подразумевается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатический текст, такой как `{_$foo}`, будет продолжать компилироваться на лету.

Шаблоны

Nette использует систему шаблонов Latte. Latte используется потому, что это самая безопасная система шаблонов для PHP, и в то же время самая интуитивно понятная. Вам не нужно изучать много нового, достаточно знать PHP и несколько тегов Latte.

Обычно страница заполняется из шаблона макета + шаблона действия. Вот как может выглядеть шаблон макета, обратите внимание на блоки {block} и тег {include}:

<!DOCTYPE html>
<html>
<head>
	<title>{block title}Мое приложение{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

А это может быть шаблоном действий:

{block title}Главная страница{/block}

{block content}
<h1>Главная страница</h1>
...
{/block}

Он определяет блок content, который вставляется вместо {include content} в макете, а также переопределяет блок title, который перезаписывает {block title} в макете. Попытайтесь представить себе результат.

Поиск шаблонов

В презентаторах вам не нужно указывать, какой шаблон должен быть отображен; фреймворк автоматически определит путь, облегчая вам кодирование.

Если вы используете структуру каталогов, в которой у каждого ведущего есть своя директория, просто поместите шаблон в эту директорию под именем действия (т. е. представления). Например, для действия default используйте шаблон default.latte:

app/
└── UI/
    └── Home/
        ├── HomePresenter.php
        └── default.latte

Если вы используете структуру, в которой ведущие находятся в одном каталоге, а шаблоны – в папке templates, сохраните их либо в файле <Presenter>.<view>.latte или <Presenter>/<view>.latte:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home.default.latte  ← 1st variant
        └── Home/
            └── default.latte   ← 2nd variant

Каталог templates можно также разместить на один уровень выше, на том же уровне, что и каталог с классами ведущих.

Если шаблон не найден, ведущий отвечает ошибкой 404 – страница не найдена.

Изменить вид можно с помощью $this->setView('anotherView'). Также можно напрямую указать файл шаблона с помощью $this->template->setFile('/path/to/template.latte').

Файлы, в которых производится поиск шаблонов, можно изменить, переопределив метод formatTemplateFiles(), который возвращает массив возможных имен файлов.

Поиск шаблонов макета

Nette также автоматически ищет файл макета.

Если вы используете структуру каталогов, в которой у каждого ведущего есть своя директория, поместите макет либо в папку с ведущим, если он предназначен только для него, либо на уровень выше, если он общий для нескольких ведущих:

app/
└── UI/
    ├── @layout.latte           ← common layout
    └── Home/
        ├── @layout.latte       ← only for Home presenter
        ├── HomePresenter.php
        └── default.latte

Если вы используете структуру, в которой ведущие сгруппированы в одном каталоге, а шаблоны находятся в папке templates, макет будет находиться в следующих местах:

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

Если ведущий находится в модуле, он также будет искать дальше по дереву каталогов в соответствии с вложенностью модуля.

Имя макета можно изменить с помощью $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 – правильно шептать, а статический анализ – обнаруживать ошибки.

И как определить такое перечисление? Просто в виде класса и его свойств. Мы назовем его как 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 и завершение кода для $this->template.

Вы также можете позволить себе роскошь шептать в шаблонах, просто установите плагин Latte в PhpStorm и поместите имя класса в начало шаблона, более подробную информацию смотрите в статье Latte: как набирать систему:

{templateType App\UI\Article\ArticleTemplate}
...

Таким же образом шаблоны работают в компонентах, просто следуйте соглашению об именовании и создайте класс шаблона FifteenTemplate для компонента, например, FifteenControl.

Если вам нужно создать $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">detail produktu</a>

Атрибут n:href очень удобен для HTML-тегов. <a>. Если мы хотим указать ссылку в другом месте, например, в тексте, мы используем {link}:

Adresa je: {link Home:default}

Дополнительные сведения см. в разделе Создание ссылок URL.

Пользовательские фильтры, теги и т.д.

Система шаблонов Latte может быть расширена с помощью пользовательских фильтров, функций, тегов и т.д. Это можно сделать непосредственно в методе render<View> или beforeRender():

public function beforeRender(): void
{
	// přidání filtru
	$this->template->addFilter('foo', /* ... */);

	// nebo konfigurujeme přímo objekt Latte\Engine
	$latte = $this->template->getLatte();
	$latte->addFilterLoader(/* ... */);
}

Latte версии 3 предлагает более продвинутый способ создания расширения для каждого веб-проекта. Вот краткий пример такого класса:

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()),
			// ...
		];
	}

	// ...
}

Мы регистрируем его с помощью конфигурации:

latte:
	extensions:
		- App\UI\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

Транслятор можно использовать, например, в качестве фильтра |translate, передав дополнительные параметры в метод translate() (см. foo, bar):

<a href="basket">{='Basket'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>

Или как тег подчеркивания:

<a href="basket">{_'Basket'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>

Для перевода раздела шаблона существует парный тег {translate} (начиная с версии Latte 2.11, ранее использовался тег {_} ):

<a href="order">{translate}Order{/translate}</a>
<a href="order">{translate foo, bar}Order{/translate}</a>

Translator по умолчанию вызывается во время выполнения при рендеринге шаблона. Latte версии 3, однако, может переводить весь статический текст во время компиляции шаблона. Это экономит производительность, поскольку каждая строка переводится только один раз, а результирующий перевод записывается в скомпилированную форму. При этом в каталоге кэша создается несколько скомпилированных версий шаблона, по одной для каждого языка. Для этого достаточно указать язык в качестве второго параметра:

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator, $lang);
}

Под статическим текстом подразумевается, например, {_'hello'} или {translate}hello{/translate}. Нестатический текст, такой как {_$foo}, будет продолжать компилироваться на лету.