Funcionamiento interno de Twig

Twig es muy extensible y puedes hackearlo. Ten en cuenta que probablemente deberías intentar crear una extensión antes de hackear el núcleo, ya que la mayoría de las características y mejoras se pueden manejar con extensiones. Este capítulo también es útil para las personas que quieren entender cómo funciona Twig internamente.

¿Cómo funciona Twig?

El renderizado de una plantilla Twig se puede resumir en cuatro pasos clave:

  • Cargar la plantilla: Si la plantilla ya está compilada, cárgala y ve al paso de evaluación, de lo contrario:
    • Primero, el lexer tokeniza el código fuente de la plantilla en pequeñas piezas para un procesamiento más fácil;
    • Luego, el parser convierte el flujo de tokens en un árbol significativo de nodos (el Abstract Syntax Tree);
    • Finalmente, el compilador transforma el AST en código PHP.
  • Evaluar la plantilla: Significa llamar al método display() de la plantilla compilada y pasarle el contexto.

El Lexer

El lexer tokeniza un código fuente de plantilla en un flujo de tokens (cada token es una instancia de \Twig\Token, y el flujo es una instancia de \Twig\TokenStream). El lexer por defecto reconoce 15 tipos diferentes de tokens:

  • \Twig\Token::BLOCK_START_TYPE, \Twig\Token::BLOCK_END_TYPE: Delimitadores para bloques ({% %})
  • \Twig\Token::VAR_START_TYPE, \Twig\Token::VAR_END_TYPE: Delimitadores para variables ({{ }})
  • \Twig\Token::TEXT_TYPE: Un texto fuera de una expresión;
  • \Twig\Token::NAME_TYPE: Un nombre en una expresión;
  • \Twig\Token::NUMBER_TYPE: Un número en una expresión;
  • \Twig\Token::STRING_TYPE: Una cadena en una expresión;
  • \Twig\Token::OPERATOR_TYPE: Un operador;
  • \Twig\Token::ARROW_TYPE: Un operador de función flecha (=>);
  • \Twig\Token::SPREAD_TYPE: Un operador de propagación (...);
  • \Twig\Token::PUNCTUATION_TYPE: Un signo de puntuación;
  • \Twig\Token::INTERPOLATION_START_TYPE, \Twig\Token::INTERPOLATION_END_TYPE: Delimitadores para interpolación de cadenas;
  • \Twig\Token::EOF_TYPE: Fin de plantilla.

Puedes convertir manualmente un código fuente en un flujo de tokens llamando al método tokenize() de un entorno:

$stream = $twig->tokenize(new \Twig\Source($source, $identifier));

Como el flujo tiene un método __toString(), puedes obtener una representación textual del mismo haciendo echo del objeto:

echo $stream."\n";

Aquí está la salida para la plantilla Hello {{ name }}:

TEXT_TYPE(Hello )
VAR_START_TYPE()
NAME_TYPE(name)
VAR_END_TYPE()
EOF_TYPE()

Note

El lexer por defecto (\Twig\Lexer) se puede cambiar llamando al método setLexer():

$twig->setLexer($lexer);

El Parser

El parser convierte el flujo de tokens en un AST (Abstract Syntax Tree), o un árbol de nodos (una instancia de \Twig\Node\ModuleNode). La extensión core define los nodos básicos como: for, if, ... y los nodos de expresión.

Puedes convertir manualmente un flujo de tokens en un árbol de nodos llamando al método parse() de un entorno:

$nodes = $twig->parse($stream);

Hacer echo del objeto nodo te da una buena representación del árbol:

echo $nodes."\n";

Aquí está la salida para la plantilla Hello {{ name }}:

\Twig\Node\ModuleNode(
  \Twig\Node\TextNode(Hello )
  \Twig\Node\PrintNode(
    \Twig\Node\Expression\NameExpression(name)
  )
)

Note

El parser por defecto (\Twig\TokenParser\AbstractTokenParser) se puede cambiar llamando al método setParser():

$twig->setParser($parser);

El Compilador

El último paso lo realiza el compilador. Toma un árbol de nodos como entrada y genera código PHP utilizable para la ejecución en tiempo de ejecución de la plantilla.

Puedes compilar manualmente un árbol de nodos a código PHP con el método compile() de un entorno:

$php = $twig->compile($nodes);

La plantilla generada para una plantilla Hello {{ name }} se lee como sigue (la salida real puede diferir dependiendo de la versión de Twig que estés usando):

/* Hello {{ name }} */
class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Template
{
    protected function doDisplay(array $context, array $blocks = []): iterable
    {
        $macros = $this->macros;
        // line 1
        yield "Hello ";
        // line 2
        yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape((isset($context["name"]) || array_key_exists("name", $context) ? $context["name"] : (function () { throw new RuntimeError('Variable "name" does not exist.', 2, $this->source); })()), "html", null, true);
        return; yield '';
    }

    // some more code
}

Note

El compilador por defecto (\Twig\Compiler) se puede cambiar llamando al método setCompiler():

$twig->setCompiler($compiler);