Шаблони

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 – page not found.

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 ще предупреждава и за запис в несъществуваща променлива, в предишните версии същото може да се постигне с използването на trait 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 предлага по-напреднал начин, а именно създаването на extension за всеки уеб проект. Частичен пример за такъв клас:

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}, ще продължат да се превеждат по време на изпълнение.

версия: 4.0