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

- David Grudl
- Nette Core | 8299
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
#[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)