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

И как да определим такова изброяване? Просто под формата на клас и неговите свойства. Ще го наречем като презентатор, но с `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\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(/* ... */);
}
```

Версия 3 на Latte предлага по-усъвършенстван начин за създаване на [разширение за |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>
```

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

И как да определим такова изброяване? Просто под формата на клас и неговите свойства. Ще го наречем като презентатор, но с 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\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(/* ... */);
}

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

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>

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

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

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