Méně boilerplate, víc typů: co přináší #[TemplateVariable]

David Grudl
Nette Core | 8299
+
+10
-

Jsou situace, kdy presenter přirozeně operuje nad konkrétním doménovým objektem (např. článkem, objednávkou, uživatelem…) a typicky si ho drží v property. A současně ho potřebuješ i v Latte šabloně.

Doteď to znamenalo další řádek s $this->template->…

V Nette Framework v3.2.9 ale přibyla elegantní drobnost: atribut #[TemplateVariable]. Stačí označit property presenteru tímto atributem (nesmí být private) a ona se automaticky zpřístupní v šabloně pod stejným názvem:

<?php
declare(strict_types=1);

namespace App\Presentation\Article;

use Nette\Application\Attributes\TemplateVariable;
use Nette\Application\UI\Presenter;

final class ArticlePresenter extends Presenter
{
	#[TemplateVariable]
	public string $siteName = 'Můj blog';
	#[TemplateVariable]
	public ?Model\Article $article = null;

	public function actionShow(int $id): void
	{
		$this->article = $this->articleFacade->getById($id);
	}
}

V šabloně pak prostě použiješ $siteName a $article bez dalšího „drátování“.

Pokud ale do šablony vložíš proměnnou se stejným názvem přes $this->template->…, tak #[TemplateVariable] ji nepřepíše. Jinými slovy, když už v šabloně proměnná existuje, atribut ji nechá být.


Na #[TemplateVariable] je zajímavé i to, že přirozeně vede k používání typovaných properties jako zdroje dat pro šablonu. Typy kontroluje už samotné PHP (a IDE i statická analýza je vidí), takže místo „dynamického“ zapisování do $this->template pracuješ s normálními, typově jasnými hodnotami (Article, bool, string…).

Nette má pro typově bezpečné šablony plnohodnotnou cestu – vlastní třídu šablony, kde jsou typy proměnných definované explicitně a $this->template pak není „pytel“, ale objekt s jasným API.

Když to vezmeme čistě experimentálně, #[TemplateVariable] může být příjemná minimalistická varianta pro menší a střední případy: nechceš zavádět novou třídu šablony, ale chceš mít v kódu pořádek a typy pod kontrolou. A až projekt vyroste, můžeš kdykoli přejít na přísně typované šablony tou „velkou“ cestou.

Marek Bartoš
Nette Blogger | 1320
+
+5
-

#[TemplateVariable] je dost magická – PHPStan ani IDE nevidí, odkud se do Template třídy zapisuje a nedovedou zanalyzovat, že v property komponenty a šablony očekávám stejný typ. V případě typů které nelze zapsat nativně se to nemusím dozvědět ani v runtime.

To co #[TemplateVariable] přidává není moc odlišné od toho, co šlo už dřív

{varType App\Presentation\Article\ArticlePresenter $control}
{$control->article->title}

nesmí být private

Podle kódu ani protected

U nás jsme typovou bezpečnost vyřešili skrze phpat, které vynucuje použití našich poděděných template, control a presenter tříd a phpstan, který vynucuje generické anotace z poděděných tříd.
Problematická je plná inicializace template třídy – nelze použít konstruktor kvůli inicializaci dějící se v TemplateFactory. Též různé akce presenteru mohou mít v šabloně odlišné parametry a pak jedna template třída pro presenter způsobuje tentýž problém. Byl bych mnohem raději, kdyby logika z TemplateFactory s vytvářením template nebyla provázaná a my konstruktor mohli používat.

/**
 * @template T_Template of Template
 *
 * @property-read T_Template $template
 */
abstract class BaseControl extends Control
{
	/**
	 * @return T_Template
	 */
	protected function createTemplate(): Template
	{
		return parent::createTemplate();
	}
}
/**
 * @template C of Control
 */
abstract class UITemplate extends Template
{
	/** @var C */
	public Control $control;
}

Editoval Marek Bartoš (23. 12. 2025 19:01)