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 : argahora 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"usen: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-xxxyn:ifcontentno se pueden usar en elementos HTML vacíos - el atributo
n:inner-snippetdebe 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}o{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;
}
}