Template

Nette usa il sistema di template Latte. Latte viene usato perché è il sistema di template più sicuro per PHP e allo stesso tempo il più intuitivo. Non dovete imparare molto di nuovo: bastano la conoscenza di PHP e qualche tag.

È consuetudine che una pagina sia composta dal template del layout più il template dell'azione specifica. Ecco che aspetto potrebbe avere un template di layout; notate i blocchi {block} e il tag {include}:

<!DOCTYPE html>
<html>
<head>
	<title>{block title}My App{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

E questo sarebbe il template dell'azione:

{block title}Homepage{/block}

{block content}
<h1>Homepage</h1>
...
{/block}

Definisce il blocco content, che viene inserito al posto di {include content} nel layout, e ridefinisce inoltre il blocco title, che sovrascrive {block title} del layout. Provate a immaginare il risultato.

Ricerca dei template

Nei presenter non dovete indicare quale template va disegnato: il framework ne deduce automaticamente il percorso, risparmiandovi di scriverlo.

Se usate una struttura di directory in cui ogni presenter ha la propria directory, basta collocare il template in questa directory con il nome dell'azione (cioè della vista). Per esempio, per l'azione default usate il template default.latte:

app/
└── Presentation/
    └── Home/
        ├── HomePresenter.php
        └── default.latte

Se usate una struttura in cui i presenter stanno insieme in un'unica directory e i template in una cartella templates, salvatelo o nel file <Presenter>.<vista>.latte oppure in <Presenter>/<vista>.latte:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home/
        │   └── default.latte   ← 1ª variante
        └── Home.default.latte  ← 2ª variante

La directory templates si può collocare anche un livello più in alto, cioè allo stesso livello della directory con le classi dei presenter.

Se il template non viene trovato, il presenter risponde con un errore 404 – pagina non trovata.

Potete cambiare la vista con $this->setView('otherView'). È anche possibile indicare direttamente il file del template con $this->template->setFile('/path/to/template.latte').

I file in cui vengono cercati i template si possono cambiare sovrascrivendo il metodo formatTemplateFiles(), che restituisce un array dei possibili nomi di file.

Ricerca del template di layout

Nette cerca automaticamente anche il file del layout.

Se usate una struttura di directory in cui ogni presenter ha la propria directory, collocate il layout o nella cartella del presenter, se è specifico solo di quello, oppure un livello più in alto, se è comune a più presenter:

app/
└── Presentation/
    ├── @layout.latte           ← layout comune
    └── Home/
        ├── @layout.latte       ← solo per il presenter Home
        ├── HomePresenter.php
        └── default.latte

Se usate una struttura in cui i presenter sono raggruppati in un'unica directory e i template stanno in una cartella templates, il layout sarà atteso in queste posizioni:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── @layout.latte       ← layout comune
        ├── Home/
        │   └── @layout.latte   ← solo per Home, 1ª variante
        └── Home.@layout.latte  ← solo per Home, 2ª variante

Se il presenter si trova in un modulo, la ricerca prosegue anche verso i livelli superiori di directory, secondo l'annidamento dei moduli.

Il nome del layout si può cambiare con $this->setLayout('layoutAdmin'), e allora sarà atteso nel file @layoutAdmin.latte. Potete anche indicare direttamente il file del template di layout con $this->setLayout('/path/to/template.latte').

Con $this->setLayout(false) oppure con il tag {layout none} dentro il template si disattiva la ricerca del layout.

I file in cui vengono cercati i template di layout si possono cambiare sovrascrivendo il metodo formatLayoutTemplateFiles(), che restituisce un array dei possibili nomi di file.

Variabili del template

Le variabili si passano ai template scrivendole in $this->template. Diventano poi disponibili nel template come variabili locali:

$this->template->article = $this->articles->getById($id);

Per passare automaticamente al template il valore di una proprietà come variabile, contrassegnatela con l'attributo #[TemplateVariable] e con visibilità pubblica:

use Nette\Application\Attributes\TemplateVariable;

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	#[TemplateVariable]
	public string $siteName = 'My blog';
}

Se passate al template una variabile con lo stesso nome, #[TemplateVariable] non la sovrascriverà.

Variabili predefinite

I presenter e i componenti passano automaticamente ai template alcune variabili utili:

  • $basePath è il percorso URL assoluto della directory radice (per esempio /eshop)
  • $baseUrl è l'URL assoluto della directory radice (per esempio http://localhost/eshop)
  • $user è un oggetto che rappresenta l'utente
  • $presenter è il presenter corrente
  • $control è il componente o il presenter corrente
  • $flashes è un array dei messaggi inviati dalla funzione flashMessage()

Se usate una classe di template personalizzata, queste variabili vengono passate se create per esse una proprietà.

Template con tipi sicuri

Sviluppando applicazioni solide, è utile definire esplicitamente quali variabili il template si aspetta e di che tipo sono. Questo offre il controllo dei tipi in PHP, suggerimenti intelligenti nell'IDE e permette all'analisi statica di individuare gli errori.

Come si definisce un elenco del genere? Semplicemente come una classe con proprietà che rappresentano le variabili del template. Chiamatela come il presenter, aggiungendo alla fine 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;

	// e altre variabili
}

L'oggetto $this->template nel presenter sarà ora un'istanza della classe ArticleTemplate. PHP controllerà quindi i tipi dichiarati durante la scrittura.

Nette sceglie automaticamente la classe del template. Cerca prima una classe chiamata <Presenter><Azione>Template, per esempio ArticleEditTemplate per l'azione edit, e solo se non esiste ripiega su <Presenter>Template.

L'annotazione @property-read è destinata all'IDE e all'analisi statica e abilita il completamento del codice, vedi PhpStorm e il completamento del codice per $this⁠-⁠>⁠template.

Potete usare il completamento del codice anche direttamente nei template. Basta installare il plugin Latte per PhpStorm e indicare all'inizio del template il nome della classe dei parametri, maggiori informazioni nel capitolo Latte: sistema di tipi:

{templateType App\Presentation\Article\ArticleTemplate}
...

Lo stesso vale per i componenti. Basta seguire la convenzione di denominazione e creare una classe dei parametri FifteenTemplate per un componente come FifteenControl.

Se vi serve usare una classe dei parametri diversa, usate il metodo createTemplate():

public function renderDefault(): void
{
	$template = $this->createTemplate(SpecialTemplate::class);
	$template->foo = 123;
	// ...
	$this->sendTemplate($template);
}

Se vi serve influire su come il template viene completato prima del rendering, per esempio per aggiungere variabili condivise da tutte le azioni, potete sovrascrivere nel presenter il metodo completeTemplate(). Viene chiamato subito prima che il template venga disegnato:

protected function completeTemplate(Nette\Application\UI\Template $template): void
{
	parent::completeTemplate($template);
	$template->siteName = 'My blog';
}

Nel template i link verso altri presenter e azioni si creano così:

<a n:href="Product:show">dettaglio del prodotto</a>

L'attributo n:href è molto comodo per i tag HTML <a>. Se vogliamo stampare il link altrove, per esempio nel testo, usiamo {link}:

L'URL è: {link Home:default}

Maggiori informazioni si trovano nel capitolo Creazione di link URL.

Filtri, tag e altro personalizzati

Il sistema di template Latte si può estendere con filtri, funzioni, tag e altri elementi personalizzati. Ci sono tre approcci disponibili, che vanno dalle soluzioni rapide ad hoc ai modelli architetturali per intere applicazioni.

Ad hoc nei metodi del presenter

L'approccio più rapido è aggiungere filtri o funzioni direttamente nel codice del presenter o del componente. Nei presenter si prestano bene i metodi beforeRender() o render<Vista>():

protected function beforeRender(): void
{
	// aggiunta di un filtro
	$this->template->addFilter('money', fn($val) => '$' . number_format($val, 2));

	// aggiunta di una funzione
	$this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6);
}

Nel template:

<p>Prezzo: {$price|money}</p>

{if isWeekend($now)} ... {/if}

Per una logica più complessa potete configurare direttamente l'oggetto Latte\Engine:

protected function beforeRender(): void
{
	$latte = $this->template->getLatte();
	$latte->setFeature(Latte\Feature::MigrationWarnings);
}

Con gli attributi

Un approccio più elegante è definire filtri e funzioni come metodi direttamente nella classe dei parametri del template del presenter o del componente, contrassegnandoli con degli attributi:

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	#[Latte\Attributes\TemplateFilter]
	public function money(float $val): string
	{
		return '$' . number_format($val, 2);
	}

	#[Latte\Attributes\TemplateFunction]
	public function isWeekend(DateTimeInterface $date): bool
	{
		return $date->format('N') >= 6;
	}
}

Latte individua e registra automaticamente i metodi contrassegnati con questi attributi. Il nome del filtro o della funzione nei template coincide con il nome del metodo. Questi metodi devono essere pubblici.

Globalmente, con le estensioni

Gli approcci precedenti si prestano a filtri e funzioni necessari solo in determinati presenter o componenti, non nell'intera applicazione. Per tutta l'applicazione la soluzione migliore è creare un'estensione. Questa classe centralizza tutte le estensioni di Latte del vostro progetto. Un breve esempio:

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()),
			// ...
		];
	}

	private function filterTimeAgoInWords(DateTimeInterface $time): string
	{
		// ...
	}

	// ...
}

Registrate l'estensione tramite la configurazione:

latte:
	extensions:
		- App\Presentation\Accessory\LatteExtension

Le estensioni offrono diversi vantaggi: il supporto della dependency injection, l'accesso allo strato del modello della vostra applicazione e la gestione centralizzata di tutte le estensioni. Supportano inoltre tag personalizzati, provider, compiler pass e altro ancora.

Configurare tutti i template

Il servizio TemplateFactory, che crea tutti i template, offre un array pubblico di callback $onCreate. Vengono chiamate ogni volta che viene creato un template qualsiasi, così potete impostare filtri, funzioni o variabili per tutti i template dell'applicazione da un unico punto. Ogni callback riceve il template appena creato. Fatevi iniettare il servizio TemplateFactory e registrate le callback, per esempio all'avvio dell'applicazione:

$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void {
	$template->addFilter('money', fn($val) => '$' . number_format($val, 2));
};

Traduzione

Se programmate un'applicazione multilingue, probabilmente avrete bisogno di stampare nel template alcuni testi in lingue diverse. Nette Framework definisce a questo scopo l'interfaccia di traduzione Nette\Localization\Translator, che ha un unico metodo translate(). Accetta il messaggio $message, di norma una stringa, e qualsiasi altro parametro. Il compito è restituire la stringa tradotta. Nette non ha un'implementazione predefinita; potete scegliere tra diverse soluzioni già pronte disponibili su Componette, secondo le vostre esigenze. La loro documentazione spiega come configurare il traduttore.

Ai template si può impostare un traduttore, che ci facciamo passare, con il metodo setTranslator():

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

In alternativa il traduttore si può impostare tramite la configurazione:

latte:
	extensions:
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)

Il traduttore si può poi usare, per esempio, come filtro |translate, compresi i parametri aggiuntivi che vengono passati al metodo translate() (vedi foo, bar):

<a href="basket">{='Carrello'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>

Oppure come tag con il trattino basso:

<a href="basket">{_'Carrello'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>

Per tradurre una porzione di template esiste il tag di tipo pari {translate} (da Latte 2.11, prima si usava il tag {_}):

<a href="order">{translate}Ordine{/translate}</a>
<a href="order">{translate foo, bar}Ordine{/translate}</a>

Il traduttore viene normalmente chiamato in fase di esecuzione, durante il rendering del template. Latte versione 3 è però in grado di tradurre tutti i testi statici già durante la compilazione del template. Questo fa risparmiare prestazioni, perché ogni stringa viene tradotta una sola volta e la traduzione risultante viene scritta nella forma compilata. Nella directory della cache nascono così più versioni compilate del template, una per ogni lingua. Per ottenerlo basta indicare la lingua come secondo parametro:

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

Per testo statico si intende, per esempio, {_'hello'} oppure {translate}hello{/translate}. I testi non statici, come {_$foo}, continueranno a essere tradotti in fase di esecuzione.

versione: 4.x