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-search-for-templates]
-----------------------------------------------

Пътят към шаблоните се определя от главния модул с помощта на проста логика. Той ще се опита да провери дали има някой от тези файлове, разположен спрямо главната директория на класа, където `<Presenter>` е името на текущия главен модул, а `<view>` е името на текущото събитие:

- `templates/<Presenter>/<view>.latte`
- `templates/<Presenter>.<view>.latte`

Ако шаблонът не бъде намерен, ще се опита да търси в директорията `templates` едно ниво по-нагоре, т.е. на същото ниво като директорията с класа на водещия.

Ако шаблонът не бъде намерен и там, отговорът ще бъде [грешка 404 |presenters#Error 404 etc.].

Можете също така да промените изгледа с помощта на `$this->setView('jineView')`. Или вместо да търсите директно, посочете името на файла с шаблона, като използвате `$this->template->setFile('/path/to/template.latte')`.

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

В тези файлове се очаква оформление:

- `templates/<Presenter>/@<layout>.latte`
- `templates/<Presenter>.@<layout>.latte`
- `templates/@<layout>.latte` Разположение, общо за няколко високоговорителя

Къде: `<Presenter>` е името на текущия водещ, а `<layout>` е името на оформлението, което по подразбиране е `'layout'`. Името може да бъде променено с помощта на `$this->setLayout('jinyLayout')`, така че ще бъдат изпробвани файлове `@jinyLayout.latte`.

Можете също така директно да посочите името на файла на шаблона за оформление, като използвате `$this->setLayout('/path/to/template.latte')`. Използването на `$this->setLayout(false)` деактивира проследяването на оформлението.

.[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 да шепне правилно, а на статичния анализ да открива грешки.

И как да определим такова изброяване? Просто под формата на клас и неговите свойства. Ще го наречем като презентатор, но с `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 ще проверява за декларирани типове при писане. От версия 8.2 на PHP тя ще предупреждава и при запис на променлива, която не съществува; в предишните версии това може да се постигне със свойството [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/bg/latte-kak-da-izpolzvame-sistemata-ot-tipove:

```latte
{templateType App\Presenters\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(/* ... */);
}
```

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

```php
namespace App\Templating;

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\Templating\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>
```

Преводачът се извиква по подразбиране по време на изпълнение, когато се визуализира шаблонът. 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} в оформлението. Опитайте се да си представите резултата.

Търсене на шаблони

Пътят към шаблоните се определя от главния модул с помощта на проста логика. Той ще се опита да провери дали има някой от тези файлове, разположен спрямо главната директория на класа, където <Presenter> е името на текущия главен модул, а <view> е името на текущото събитие:

  • templates/<Presenter>/<view>.latte
  • templates/<Presenter>.<view>.latte

Ако шаблонът не бъде намерен, ще се опита да търси в директорията templates едно ниво по-нагоре, т.е. на същото ниво като директорията с класа на водещия.

Ако шаблонът не бъде намерен и там, отговорът ще бъде грешка 404.

Можете също така да промените изгледа с помощта на $this->setView('jineView'). Или вместо да търсите директно, посочете името на файла с шаблона, като използвате $this->template->setFile('/path/to/template.latte').

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

В тези файлове се очаква оформление:

  • templates/<Presenter>/@<layout>.latte
  • templates/<Presenter>.@<layout>.latte
  • templates/@<layout>.latte Разположение, общо за няколко високоговорителя

Къде: <Presenter> е името на текущия водещ, а <layout> е името на оформлението, което по подразбиране е 'layout'. Името може да бъде променено с помощта на $this->setLayout('jinyLayout'), така че ще бъдат изпробвани файлове @jinyLayout.latte.

Можете също така директно да посочите името на файла на шаблона за оформление, като използвате $this->setLayout('/path/to/template.latte'). Използването на $this->setLayout(false) деактивира проследяването на оформлението.

Файловете, в които се търсят шаблоните за оформление, могат да се променят чрез наслагване на метода 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 в presenter вече ще бъде инстанция на класа ArticleTemplate. По този начин PHP ще проверява за декларирани типове при писане. От версия 8.2 на PHP тя ще предупреждава и при запис на променлива, която не съществува; в предишните версии това може да се постигне със свойството Nette\SmartObject.

Анотацията @property-read е предназначена за IDE и статичен анализ, тя ще накара шепота да работи, вж. PhpStorm и завършване на кода за $this->template.

Можете също така да си позволите лукса да шепнете в шаблоните, просто инсталирайте плъгина Latte в PhpStorm и поставете името на класа в началото на шаблона, за повече информация вижте статията Latte: как да въведем системата:

{templateType App\Presenters\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(/* ... */);
}

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

namespace App\Templating;

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\Templating\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>

Преводачът се извиква по подразбиране по време на изпълнение, когато се визуализира шаблонът. Latte версия 3 обаче може да превежда целия статичен текст по време на компилирането на шаблона. Това спестява производителност, тъй като всеки низ се превежда само веднъж и полученият превод се записва в компилирания формуляр. По този начин се създават няколко компилирани версии на шаблона в кеш директорията, по една за всеки език. За да направите това, трябва само да посочите езика като втори параметър:

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

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