Modelos

A Nette utiliza o sistema de modelos Latte. O Latte é usado porque é o sistema de modelo mais seguro para PHP e, ao mesmo tempo, o sistema mais intuitivo. Você não precisa aprender muito de novo, você só precisa conhecer PHP e algumas tags de Latte.

É normal que a página seja completada a partir do modelo de layout + o modelo de ação. Isto é o que um modelo de layout pode parecer, observe os blocos {block} e a etiqueta {include}:

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

E este poderia ser o modelo de ação:

{block title}Homepage{/block}

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

Ele define o bloco content, que é inserido no lugar de {include content} no layout, e também redefine o bloco title, que sobrescreve {block title} no layout. Tente imaginar o resultado.

Pesquisa de modelos

Nos apresentadores, você não precisa especificar qual modelo deve ser renderizado; a estrutura determinará automaticamente o caminho, facilitando a codificação para você.

Se você usar uma estrutura de diretórios em que cada apresentador tenha seu próprio diretório, basta colocar o modelo nesse diretório com o nome da ação (ou seja, visualização). Por exemplo, para a ação default, use o modelo default.latte:

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

Se você usar uma estrutura em que os apresentadores estejam juntos em um diretório e os modelos em uma pasta templates, salve-os em um arquivo <Presenter>.<view>.latte ou <Presenter>/<view>.latte:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home.default.latte  ← 1st variant
        └── Home/
            └── default.latte   ← 2nd variant

O diretório templates também pode ser colocado um nível acima, no mesmo nível do diretório com as classes de apresentador.

Se o modelo não for encontrado, o apresentador responderá com o erro 404 – página não encontrada.

Você pode alterar a visualização usando $this->setView('anotherView'). Também é possível especificar diretamente o arquivo de modelo com $this->template->setFile('/path/to/template.latte').

Os arquivos onde os modelos são pesquisados podem ser alterados substituindo o método formatTemplateFiles(), que retorna uma matriz de possíveis nomes de arquivos.

Pesquisa de modelos de layout

O Nette também procura automaticamente o arquivo de layout.

Se você usar uma estrutura de diretórios em que cada apresentador tenha seu próprio diretório, coloque o layout na pasta com o apresentador, se for específico apenas para ele, ou em um nível superior, se for comum a vários apresentadores:

app/
└── UI/
    ├── @layout.latte           ← common layout
    └── Home/
        ├── @layout.latte       ← only for Home presenter
        ├── HomePresenter.php
        └── default.latte

Se você usar uma estrutura em que os apresentadores estejam agrupados em um diretório e os modelos estejam em uma pasta templates, o layout será esperado nos seguintes locais:

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

Se o apresentador estiver em um módulo, ele também pesquisará mais acima na árvore de diretórios, de acordo com o aninhamento do módulo.

O nome do layout pode ser alterado usando $this->setLayout('layoutAdmin') e, em seguida, ele será esperado no arquivo @layoutAdmin.latte. Você também pode especificar diretamente o arquivo de modelo de layout usando $this->setLayout('/path/to/template.latte').

O uso de $this->setLayout(false) ou da tag {layout none} dentro do modelo desativa a pesquisa de layout.

Os arquivos em que os modelos de layout são pesquisados podem ser alterados substituindo o método formatLayoutTemplateFiles(), que retorna uma matriz de possíveis nomes de arquivos.

Variáveis no modelo

As variáveis são passadas para o modelo, escrevendo-as para $this->template e depois estão disponíveis no modelo como variáveis locais:

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

Desta forma, podemos facilmente passar qualquer variável para os modelos. Entretanto, ao desenvolver aplicações robustas, muitas vezes é mais útil nos limitarmos. Por exemplo, ao definir explicitamente uma lista de variáveis que o modelo espera e seus tipos. Isto permitirá que o PHP verifique a digitação, que a IDE se autocomplete corretamente, e que a análise estática detecte erros.

E como definimos tal enumeração? Simplesmente sob a forma de uma classe e suas propriedades. Damos o mesmo nome ao apresentador, mas com Template no final:

/**
 * @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 outras variáveis
}

O objeto $this->template do apresentador será agora uma instância da classe ArticleTemplate. Portanto, o PHP verificará os tipos declarados quando eles forem escritos. E a partir do PHP 8.2 ele também alertará sobre a escrita de uma variável inexistente, nas versões anteriores o mesmo pode ser alcançado usando o traço Nette\SmartObject.

A anotação @property-read é para IDE e análise estática, fará com que o auto-completar funcione, veja PhpStorm e preenchimento de código por $this->template.

Você também pode se dar ao luxo de sussurrar nos modelos, basta instalar o plugin Latte no PhpStorm e especificar o nome da classe no início do modelo, veja o artigo Latte: como digitar sistema:

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

Também é assim que os modelos funcionam nos componentes, basta seguir a convenção de nomenclatura e criar uma classe de modelos FifteenTemplate para o componente, por exemplo FifteenControl.

Se você precisar criar um $template como instância de outra classe, use o método createTemplate():

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

Variáveis padrão

Apresentadores e componentes passam várias variáveis úteis para os modelos automaticamente:

  • $basePath é um caminho absoluto de URL para o dir raiz (por exemplo /CD-collection)
  • $baseUrl é um URL absoluto para o dir raiz (por exemplo http://localhost/CD-collection)
  • $user é um objeto que representa o usuário
  • $presenter é o atual apresentador
  • $control é o atual componente ou apresentador
  • $flashes lista de mensagens enviadas por método flashMessage()

Se você usar uma classe de modelo personalizada, estas variáveis são passadas se você criar uma propriedade para elas.

No modelo, criamos links para outros apresentadores e ações da seguinte forma:

<a n:href="Product:show">detail</a>

O atributo n:href é muito útil para tags HTML <a>. Se quisermos imprimir o link em outro lugar, por exemplo, no texto, usamos {link}:

URL is: {link Home:default}

Para mais informações, consulte Criação de links.

Filtros personalizados, Etiquetas, etc.

O sistema de modelos Latte pode ser ampliado com filtros personalizados, funções, tags, etc. Isto pode ser feito diretamente no render<View> ou beforeRender() método:

public function beforeRender(): void
{
	// adicionando um filtro
	$this->template->addFilter('foo', /* ... */);

	// ou configurar diretamente o objeto Latte\Engine
	$latte = $this->template->getLatte();
	$latte->addFilterLoader(/* ... */);
}

A versão 3 do Latte oferece uma maneira mais avançada, criando uma extensão para cada projeto web. Aqui está um exemplo rudimentar de tal classe:

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

	// ...
}

Registramos usando a configuração#Latte:

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

Traduzindo

Se você estiver programando uma aplicação multilíngüe, provavelmente precisará produzir parte do texto no modelo em diferentes idiomas. Para fazer isto, o Nette Framework define uma interface de tradução Nette\Localization\Translator, que tem um único método translate(). Isto aceita a mensagem $message, que geralmente é uma string, e quaisquer outros parâmetros. A tarefa é devolver a string traduzida. Não há uma implementação padrão em Nette, você pode escolher de acordo com suas necessidades entre várias soluções prontas que podem ser encontradas na Componette. Sua documentação lhe diz como configurar o tradutor.

Os modelos podem ser criados com um tradutor, que teremos passado para nós, usando o método setTranslator():

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

Alternativamente, o tradutor pode ser definido usando a configuração:

latte:
	extensions:
		- Latte\Essential\TranslatorExtension

O tradutor pode então ser usado, por exemplo, como um filtro |translate, com parâmetros adicionais passados para o método translate() (ver foo, bar):

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

Ou como uma etiqueta de sublinhado:

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

Para a tradução da seção de modelos, há uma etiqueta emparelhada {translate} (desde Latte 2.11, anteriormente foi usada a etiqueta {_} ):

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

O tradutor é chamado por padrão em tempo de execução ao renderizar o modelo. Latte versão 3, no entanto, pode traduzir todo o texto estático durante a compilação do modelo. Isto economiza desempenho porque cada string é traduzida apenas uma vez e a tradução resultante é escrita na forma compilada. Isto cria múltiplas versões compiladas do modelo no diretório do cache, uma para cada idioma. Para fazer isto, basta especificar o idioma como segundo parâmetro:

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

Por texto estático entendemos, por exemplo, {_'hello'} ou {translate}hello{/translate}. Textos não estáticos, como {_$foo}, continuarão a ser compilados em tempo real.