Nette Documentation Preview

syntax
Migración de Latte 2 a 3
************************

.[perex]
Latte 3 tiene el compilador reescrito por completo y una gramática formalmente bien definida. Debería coincidir con Latte 2 todo lo posible, pero hay algunas construcciones que necesitan pequeños ajustes.

En la práctica resulta que la inmensa mayoría de las plantillas no necesita ninguna modificación y funciona igual en Latte 2 que en Latte 3. Pero ¿cómo detectar las incompatibilidades?

**Primero, instale la versión de transición Latte 2.11.**

Esta versión no trae funciones nuevas: solo emite una advertencia con E_USER_DEPRECATED en los casos que sabe que el nuevo Latte no admitirá y, sobre todo, le aconseja cómo corregirlos. Para recorrer todas las plantillas y comprobar si son compatibles, puede usar la herramienta [Linter |/develop#Linter], que se ejecuta desde la consola:

```shell
vendor/bin/latte-lint <path>
```

Una vez resueltas las posibles incompatibilidades, actualice a Latte 3.0. **Y ejecute el Linter otra vez** para asegurarse de que el nuevo analizador estricto entiende realmente todas las plantillas.


Cambios en la API
=================

Los cambios en la API afectan solo a la incorporación de etiquetas propias. El resto de la API sigue igual que en la versión 2, es decir, la misma forma de renderizar plantillas, pasar parámetros y registrar filtros.

La excepción es el llamado filtro dinámico `Engine::addFilter(null, ...)`, del que ahora se encargan los [filtros registrados mediante una clase |/custom-filters#Filters Using the Class] con el método `addFilter()`. El método original `Engine::addFilterLoader()` sigue existiendo como solución transitoria, pero está obsoleto.

La API para añadir etiquetas propias es completamente distinta, así que los complementos pensados para Latte 2 no funcionarán con ella. Vea también [#Updates to Add-Ons].


Cambios en la sintaxis
======================

Los cambios son estos:

- los filtros usan la coma como separador de parámetros: lo que antes era `|filter: arg : arg` ahora es `|filter: arg, arg`
- la etiqueta `{label foo}...{/label}` es siempre par; la impar debe escribirse `{label /}`
- en cambio, la etiqueta `{_'text'}` es siempre impar; la par `{_}...{/}` se sustituye por la nueva `{translate}...{/translate}`
- las pseudocadenas como `{block foo-$var}` deben escribirse entre comillas, `{block "foo-$var"}`, o añadir llaves compuestas, `{block foo-{$var}}`
- esto vale también para los atributos, es decir, en lugar de `n:block="foo-$var"` use `n:block="foo-{$var}"`.
- en Latte 3 hay que respetar las mayúsculas y minúsculas de los filtros
- la etiqueta `{do ...}` o `{php ...}` solo puede contener expresiones; para usar cualquier PHP, registre [RawPhpExtension |/develop#RawPhpExtension].

Y algunos casos límite más:

- los atributos `n:inner-xxx`, `n:tag-xxx` y `n:ifcontent` no se pueden usar en elementos HTML vacíos
- el atributo `n:inner-snippet` debe escribirse sin inner-
- las etiquetas `</script>` y `</style>` deben cerrarse
- se ha eliminado la variable mágica `$iterations` (¡no confundir con `$iterator`!)
- sustituya la etiqueta `{includeblock file.latte}` por [`{include file.latte with blocks}` |/tags#include] o [`{import}` |/template-inheritance#Horizontal Reuse]
- `{include "abc"}` debe escribirse como `{include file "abc"}`, salvo que `"abc"` contenga un punto y quede claro que es un archivo


Actualización de los complementos
=================================

Con la reescritura completa del analizador ha cambiado por completo la forma de escribir etiquetas propias. Si tiene etiquetas propias creadas para Latte, tendrá que reescribirlas para la versión 3, vea la [documentación|/custom-tags].

Si usa un complemento ajeno que añade etiquetas, tendrá que esperar a que su autor publique una versión para Latte 3. Las bibliotecas `nette/application`, `nette/caching` y `nette/forms` en la versión 3.1, así como Texy, ya están actualizadas y funcionan tanto con Latte 2 como con Latte 3.


nette/application
-----------------

.[note]
Con el uso normal de Nette, esta extensión se configura automáticamente y no hace falta cambiar nada.

Código antiguo para Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};

$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
```

Código nuevo para Latte 3:

```php
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
```

UIExtension añade `n:href`, `{link}`, `{control}`, `{snippet}`, etc. Las etiquetas de los snippets pasan así del propio Latte a la biblioteca `nette/application`. En Latte 3 ya no se llama al método `templatePrepareFilters()` del presenter.


nette/forms
-----------

.[note]
Con el uso normal de Nette, esta extensión se configura automáticamente y no hace falta cambiar nada.

Código antiguo para Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
```

Código nuevo para Latte 3:

```php
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
```


nette/caching
-------------

.[note]
Con el uso normal de Nette, esta extensión se configura automáticamente y no hace falta cambiar nada.

Código antiguo para Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};

$latte->addProvider('cacheStorage', $cacheStorage);
```

Código nuevo para Latte 3:

```php
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
```


Tracy
-----

El panel para Tracy también se activa ahora como extensión.

Código antiguo para Latte 2:

```php
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
```

Código nuevo para Latte 3:

```php
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
```


Traducciones
------------

TranslatorExtension añade las etiquetas de traducción `{_'text'}`, la nueva pareja `{translate}...{/translate}` y el filtro `|translate`.

Código antiguo para Latte 2:

```php
$latte->addFilter('translate', [$translator, 'translate']);
```

Código nuevo para Latte 3:

```php
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
```

En los presenters se activa automáticamente al asignar el traductor a la plantilla con el método `$template->setTranslator($translator)`. Sin eso, las etiquetas de traducción no estarán disponibles y tendrá que registrar la extensión a mano o mediante un archivo de configuración.


Archivo de configuración
========================

En Latte 2 se podían registrar etiquetas nuevas mediante el [archivo de configuración |application:configuration#Latte Templates], en la sección `latte › macros`. En la versión 3 se añaden así extensiones enteras:

```neon
latte:
	extensions:
		- App\Templating\LatteExtension
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```


¿Desarrolla un complemento para Latte?
======================================

Puede tener en su biblioteca soporte para ambas versiones de Latte a la vez. Para detectar la versión, lo mejor es usar la constante `Latte\Engine::VERSION` y separar el uso de `onCompile[]` y `addMacro()` del nuevo `addExtension()`:

```php
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
	// Inicialización de Latte 2
	$this->latte->onCompile[] = function ($latte) {
		$latte->addMacro(/* ... */);
	};
} else {
	// Inicialización de Latte 3
	$this->latte->addExtension(/* ... */);
}
```

Como ejemplo, probemos a reescribir el siguiente código pensado para Latte 2 en su forma para Latte 3:

```php
// código antiguo para Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
	$set = new Latte\Macros\MacroSet($latte->getCompiler());
	$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};
```

Latte 3 se extiende mediante [extensiones|/extending-latte]. Una extensión trivial que añada la etiqueta `foo` tendría este aspecto:

```php
// código nuevo para Latte 3
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // añadiremos la clase FooNode enseguida
		];
	}
}

// registro
$this->latte->addExtension(new FooExtension);
```

El nuevo compilador es más robusto y no incluye los atajos anteriores, así que escribir una macro cuesta unas cuantas líneas más de código. Por ejemplo, no podemos pasar directamente una cadena de código PHP como en Latte 2: en su lugar creamos una función. Recuerde que en Latte 2 la función tendría más o menos este aspecto:

```php
// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
	return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});
```

Aun así, Latte 3 lo aborda de forma muy parecida, solo que `MacroNode` se llama `Latte\Compiler\Tag` y `PhpWriter` es `Latte\Compiler\PrintContext`. Pero, sobre todo, hay un paso intermedio adicional: la función no devuelve el código PHP directamente, sino un nodo, es decir, un descendiente de `StatementNode`, que pasa a formar parte del árbol AST. Y ese nodo tiene un método `print(Latte\Compiler\PrintContext $context): string` que devuelve el código PHP:

```php
// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format('echo ...'); // devuelve código PHP
	}
}
```

Además, la máscara de `$context->format()` ya no tiene las abreviaturas `%node.***`: se da por hecho que primero [analiza el contenido de la etiqueta |/custom-tags#Tag Parsing Function]. Así que usamos el analizador para volcar el contenido en variables (subnodos) y después lo imprimimos:

```php
use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public ExpressionNode $subject;
	public ArrayNode $args;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		// parseo del contenido de la etiqueta
		$node->subject = $tag->parser->parseUnquotedStringOrExpression();
		$tag->parser->stream->tryConsume(',');
		$node->args = $tag->parser->parseArguments();
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format(
			'echo %escape(MyClass:myFunc(%node, %node));',
			$this->subject,
			$this->args,
		);
	}
}
```

Por último, añadiremos el método `getIterator()` para permitir recorrer los subnodos durante el [recorrido |/custom-tags#Implementing getIterator for Subnodes]:

```php
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	...

	public function &getIterator(): \Generator
	{
		yield $this->subject;
		yield $this->args;
	}
}
```

{{priority: -1}}

Migración de Latte 2 a 3

Latte 3 tiene el compilador reescrito por completo y una gramática formalmente bien definida. Debería coincidir con Latte 2 todo lo posible, pero hay algunas construcciones que necesitan pequeños ajustes.

En la práctica resulta que la inmensa mayoría de las plantillas no necesita ninguna modificación y funciona igual en Latte 2 que en Latte 3. Pero ¿cómo detectar las incompatibilidades?

Primero, instale la versión de transición Latte 2.11.

Esta versión no trae funciones nuevas: solo emite una advertencia con E_USER_DEPRECATED en los casos que sabe que el nuevo Latte no admitirá y, sobre todo, le aconseja cómo corregirlos. Para recorrer todas las plantillas y comprobar si son compatibles, puede usar la herramienta Linter, que se ejecuta desde la consola:

vendor/bin/latte-lint <path>

Una vez resueltas las posibles incompatibilidades, actualice a Latte 3.0. Y ejecute el Linter otra vez para asegurarse de que el nuevo analizador estricto entiende realmente todas las plantillas.

Cambios en la API

Los cambios en la API afectan solo a la incorporación de etiquetas propias. El resto de la API sigue igual que en la versión 2, es decir, la misma forma de renderizar plantillas, pasar parámetros y registrar filtros.

La excepción es el llamado filtro dinámico Engine::addFilter(null, ...), del que ahora se encargan los filtros registrados mediante una clase con el método addFilter(). El método original Engine::addFilterLoader() sigue existiendo como solución transitoria, pero está obsoleto.

La API para añadir etiquetas propias es completamente distinta, así que los complementos pensados para Latte 2 no funcionarán con ella. Vea también Updates to Add-Ons.

Cambios en la sintaxis

Los cambios son estos:

  • los filtros usan la coma como separador de parámetros: lo que antes era |filter: arg : arg ahora es |filter: arg, arg
  • la etiqueta {label foo}...{/label} es siempre par; la impar debe escribirse {label /}
  • en cambio, la etiqueta {_'text'} es siempre impar; la par {_}...{/} se sustituye por la nueva {translate}...{/translate}
  • las pseudocadenas como {block foo-$var} deben escribirse entre comillas, {block "foo-$var"}, o añadir llaves compuestas, {block foo-{$var}}
  • esto vale también para los atributos, es decir, en lugar de n:block="foo-$var" use n:block="foo-{$var}".
  • en Latte 3 hay que respetar las mayúsculas y minúsculas de los filtros
  • la etiqueta {do ...} o {php ...} solo puede contener expresiones; para usar cualquier PHP, registre RawPhpExtension.

Y algunos casos límite más:

  • los atributos n:inner-xxx, n:tag-xxx y n:ifcontent no se pueden usar en elementos HTML vacíos
  • el atributo n:inner-snippet debe escribirse sin inner-
  • las etiquetas </script> y </style> deben cerrarse
  • se ha eliminado la variable mágica $iterations (¡no confundir con $iterator!)
  • sustituya la etiqueta {includeblock file.latte} por {include file.latte with blocks}{import}
  • {include "abc"} debe escribirse como {include file "abc"}, salvo que "abc" contenga un punto y quede claro que es un archivo

Actualización de los complementos

Con la reescritura completa del analizador ha cambiado por completo la forma de escribir etiquetas propias. Si tiene etiquetas propias creadas para Latte, tendrá que reescribirlas para la versión 3, vea la documentación.

Si usa un complemento ajeno que añade etiquetas, tendrá que esperar a que su autor publique una versión para Latte 3. Las bibliotecas nette/application, nette/caching y nette/forms en la versión 3.1, así como Texy, ya están actualizadas y funcionan tanto con Latte 2 como con Latte 3.

nette/application

Con el uso normal de Nette, esta extensión se configura automáticamente y no hace falta cambiar nada.

Código antiguo para Latte 2:

$latte->onCompile[] = function ($latte) {
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};

$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());

Código nuevo para Latte 3:

$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));

UIExtension añade n:href, {link}, {control}, {snippet}, etc. Las etiquetas de los snippets pasan así del propio Latte a la biblioteca nette/application. En Latte 3 ya no se llama al método templatePrepareFilters() del presenter.

nette/forms

Con el uso normal de Nette, esta extensión se configura automáticamente y no hace falta cambiar nada.

Código antiguo para Latte 2:

$latte->onCompile[] = function ($latte) {
	Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};

Código nuevo para Latte 3:

$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);

nette/caching

Con el uso normal de Nette, esta extensión se configura automáticamente y no hace falta cambiar nada.

Código antiguo para Latte 2:

$latte->onCompile[] = function ($latte) {
	$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};

$latte->addProvider('cacheStorage', $cacheStorage);

Código nuevo para Latte 3:

$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));

Tracy

El panel para Tracy también se activa ahora como extensión.

Código antiguo para Latte 2:

$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);

Código nuevo para Latte 3:

$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);

Traducciones

TranslatorExtension añade las etiquetas de traducción {_'text'}, la nueva pareja {translate}...{/translate} y el filtro |translate.

Código antiguo para Latte 2:

$latte->addFilter('translate', [$translator, 'translate']);

Código nuevo para Latte 3:

$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));

En los presenters se activa automáticamente al asignar el traductor a la plantilla con el método $template->setTranslator($translator). Sin eso, las etiquetas de traducción no estarán disponibles y tendrá que registrar la extensión a mano o mediante un archivo de configuración.

Archivo de configuración

En Latte 2 se podían registrar etiquetas nuevas mediante el archivo de configuración, en la sección latte › macros. En la versión 3 se añaden así extensiones enteras:

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

¿Desarrolla un complemento para Latte?

Puede tener en su biblioteca soporte para ambas versiones de Latte a la vez. Para detectar la versión, lo mejor es usar la constante Latte\Engine::VERSION y separar el uso de onCompile[] y addMacro() del nuevo addExtension():

if (version_compare(Latte\Engine::VERSION, '3', '<')) {
	// Inicialización de Latte 2
	$this->latte->onCompile[] = function ($latte) {
		$latte->addMacro(/* ... */);
	};
} else {
	// Inicialización de Latte 3
	$this->latte->addExtension(/* ... */);
}

Como ejemplo, probemos a reescribir el siguiente código pensado para Latte 2 en su forma para Latte 3:

// código antiguo para Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
	$set = new Latte\Macros\MacroSet($latte->getCompiler());
	$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};

Latte 3 se extiende mediante extensiones. Una extensión trivial que añada la etiqueta foo tendría este aspecto:

// código nuevo para Latte 3
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // añadiremos la clase FooNode enseguida
		];
	}
}

// registro
$this->latte->addExtension(new FooExtension);

El nuevo compilador es más robusto y no incluye los atajos anteriores, así que escribir una macro cuesta unas cuantas líneas más de código. Por ejemplo, no podemos pasar directamente una cadena de código PHP como en Latte 2: en su lugar creamos una función. Recuerde que en Latte 2 la función tendría más o menos este aspecto:

// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
	return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});

Aun así, Latte 3 lo aborda de forma muy parecida, solo que MacroNode se llama Latte\Compiler\Tag y PhpWriter es Latte\Compiler\PrintContext. Pero, sobre todo, hay un paso intermedio adicional: la función no devuelve el código PHP directamente, sino un nodo, es decir, un descendiente de StatementNode, que pasa a formar parte del árbol AST. Y ese nodo tiene un método print(Latte\Compiler\PrintContext $context): string que devuelve el código PHP:

// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format('echo ...'); // devuelve código PHP
	}
}

Además, la máscara de $context->format() ya no tiene las abreviaturas %node.***: se da por hecho que primero analiza el contenido de la etiqueta. Así que usamos el analizador para volcar el contenido en variables (subnodos) y después lo imprimimos:

use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public ExpressionNode $subject;
	public ArrayNode $args;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		// parseo del contenido de la etiqueta
		$node->subject = $tag->parser->parseUnquotedStringOrExpression();
		$tag->parser->stream->tryConsume(',');
		$node->args = $tag->parser->parseArguments();
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format(
			'echo %escape(MyClass:myFunc(%node, %node));',
			$this->subject,
			$this->args,
		);
	}
}

Por último, añadiremos el método getIterator() para permitir recorrer los subnodos durante el recorrido:

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	...

	public function &getIterator(): \Generator
	{
		yield $this->subject;
		yield $this->args;
	}
}