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/uk/latte-yak-koristuvatisya-sistemoyu-tipiv:

```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
{
	// додати фільтр
	$this->template->addFilter('foo', /* ... */);

	// або налаштувати об'єкт 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>
```

Перекладач викликається за замовчуванням під час виконання шаблону під час рендерингу. Однак у версії 3 Latte може перекладати весь статичний текст під час компіляції шаблону. Це економить продуктивність, оскільки кожен рядок перекладається лише один раз, а результат перекладу записується до скомпільованої форми. Це створює кілька скомпільованих версій шаблону в кеш-пам'яті, по одній для кожної мови. Для цього вам потрібно лише вказати мову як другий параметр:

```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
{
	// додати фільтр
	$this->template->addFilter('foo', /* ... */);

	// або налаштувати об'єкт 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>

Перекладач викликається за замовчуванням під час виконання шаблону під час рендерингу. Однак у версії 3 Latte може перекладати весь статичний текст під час компіляції шаблону. Це економить продуктивність, оскільки кожен рядок перекладається лише один раз, а результат перекладу записується до скомпільованої форми. Це створює кілька скомпільованих версій шаблону в кеш-пам'яті, по одній для кожної мови. Для цього вам потрібно лише вказати мову як другий параметр:

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

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