Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Twig ❤️

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Hagamos que la acción show() del controlador renderé código HTML usando un template. Tan pronto como quieras representar un template, necesitarás que tu controlador herede del AbstractController. No olvides permitir que PhpStorm lo autocomplete para que pueda agregar el import necesario.

Ahora, obviamente, un controlador no necesita heredar esta clase base - A Symfony no le interesa eso. Pero, es usual heredar del AbstractController por una simple razón: nos brinda métodos útiles!

... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
... lines 6 - 8
class QuestionController extends AbstractController
{
... lines 11 - 27
}

Rendereando un Template

El primer método útil es render. Podemos decir: return this->render() y pasar dos argumentos. El primero es el nombre del archivo del template: podemos poner lo que sea aquí, pero usualmente - porque valoramos nuestra cordura - lo nombramos igual que el controlador: question/show.html.twig.

El segundo argumento es un array de todas las variables que queremos pasar al template. Eventualmente, vamos a hacer una query específica a la base de datos y pasaremos los datos al template. Por el momento, hay que fingirlo. Voy a copiar la linea ucwords() y borrar el código viejo. Pasemos una variable al template llamada - que tal: question - asignada a este string.

... lines 1 - 8
class QuestionController extends AbstractController
{
... lines 11 - 21
public function show($slug)
{
return $this->render('question/show.html.twig', [
'question' => ucwords(str_replace('-', ' ', $slug))
]);
}
}

Es hora de una pregunta! Qué valor crees que regresa el método render()? Un string? alguna otra cosa? La respuesta es: un objeto Response... conteniendo HTML. Porque recuerda: la única regla de un controlador es que siempre debe de regresar un objeto tipo Response.

Tip

Un controlador puede regresar algo distinto a un objeto Response, pero no te preocupes por eso ahorita... o tal vez nunca.

Creando el Template

Entonces, creemos ese template! Dentro de templates/, crea el subdirectorio question, luego un nuevo archivo llamado show.html.twig. Empecemos sencillo: un <h1> y luego {{ question }} para representar la variable question. Y... voy a poner un poco más de sintaxis.

<h1>{{ question }}</h1>
<div>
Eventually, we'll print the full question here!
</div>

Las 3 Sintaxis de Twig!

Acabamos de escribir nuestro primer código de Twig! Twig es muy amigable: es un simple archivo HTML hasta que escribes una de sus dos sintaxis.

La primera es la sintaxis "imprime algo". {{, lo que quieres imprimir, luego }}. Dentro de las llaves, estás escribiendo código en Twig... el cual es muy similar a JavaScript. Esto imprime la variable question. Si pones comillas alrededor, imprimirá la palabra question. Y claro, puedes hacer cosas mas complejas - como el operador terneario. Es decir, es muy similar a JavaScrip.

La segunda sintaxis es la que yo llamo "haz algo". Va de esta forma {% seguido por lo que quieres hacer, por ejemplo un if o un for. Hablaremos más de esto en un momento.

Y... eso es todo! O estás imprimiendo algo con {{ o haciendo algo, como un if, con {%.

Ok, una pequeña mentira, existe una tercera sintaxis... pero es solo para comentarios: {#, el comentario... luego #}.

<h1>{{ question }}</h1>
{# oh, I'm just a comment hiding here #}
<div>
Eventually, we'll print the full question here!
</div>

Veamos si funciona! Abre la página, refresca y... Lo tenemos! Si miras el código fuente, puedes notar que no hay una estructura HTML aun. Es literalmente la estructura de nuestro template y nada mas. Le vamos a agregar una estructura base en algunos minutos.

Haciendo Bucles con el Tag {%

Ok: tenemos una pregunta falsa. Creo que se merece algunas respuestas falsas! De regreso al controlador, en la acción show(), voy a pegar 3 respuestas falsas.

... lines 1 - 8
class QuestionController extends AbstractController
{
... lines 11 - 21
public function show($slug)
{
$answers = [
'Make sure your cat is sitting purrrfectly still ?',
'Honestly, I like furry shoes better than MY cat',
'Maybe... try saying the spell backwards?',
];
... lines 29 - 33
}
}

Como he dicho, una vez que hayamos hablado sobre base de datos, vamos a hacer un query en lugar de esto. Pero para comenzar, esto va a funcionar de maravilla. Pasalas al template como la segunda variable llamada answers.

... lines 1 - 8
class QuestionController extends AbstractController
{
... lines 11 - 21
public function show($slug)
{
$answers = [
'Make sure your cat is sitting purrrfectly still ?',
'Honestly, I like furry shoes better than MY cat',
'Maybe... try saying the spell backwards?',
];
return $this->render('question/show.html.twig', [
'question' => ucwords(str_replace('-', ' ', $slug)),
'answers' => $answers,
]);
}
}

De regreso al template. Como las podríamos imprimir? No podemos solo decir {{ answers }}... porque es un array. Lo que realmente queremos hacer es recorrer el array e imprimir cada respuesta individual. Para poder hacer esto, tenemos que hacer uso de nuestra primer función "haz algo"! Se vería algo así: {% for answer in answers %}. y la mayoría de las etiquetas "haz algo" también tienen una etiqueta de cierre: {% endfor %}.

Ponle una etiqueta ul alrededor y, dentro del ciclo, di <li> y {{ answer }}.

... lines 1 - 8
<h2>Answers</h2>
<ul>
{% for answer in answers %}
<li>{{ answer }}</li>
{% endfor %}
</ul>
... lines 16 - 17

Me fascina! Ok navegador, refresca! Funciona! Digo, está muy, muy feo... pero lo vamos a arreglar pronto.

La Referencia de Twig: Tags, Filtros, Funciones

Dirígete a https://twig.symfony.com. Twig es su propia librería con su propia documentación. Aquí hay un montón de cosas útiles... Pero lo que realmente me gusta está aquí abajo: la Referencia de Twig.

Ves esas "Etiquetas" a la izquierda? Esas son todas las etiquetas "Haz algo" que existen. Asi es, siempre será {% y luego una de estas palabras - por ejemplo, for, if o {% set. Si intentas {% pizza, yo voy a pensar que es gracioso, pero Twig te va a gritar.

Twig también tiene funciones... como cualquier lenguaje... y una agradable funcionalidad llamada "tests", la cual es algo única. Esto te permite decir cosas como: if foo is defined o if number is even.

Pero la mayor y mejor sección es la de los "filtros". Los filtros son básicamente funciones... pero más hipster. Mira el filtro length. Los filtros funcionan como las "cadenas" en la linea de comandos: solo que aquí unimos la variable users en el filtro length, el cual solo los cuenta. El valor va de izquierda a derecha. Los filtros son en realidad funciones... con una sintaxis más amigable.

Usemos este filtro para imprimir el número de respuestas. Voy a poner algunos paréntesis, luego {{ answer|length }} Cuando lo probamos... de lujo!

... lines 1 - 7
<h2>Answers {{ answers|length }}</h2>
... lines 11 - 17

Herencia con Templates de Twig: extends

En este punto, ya eres apto para convertirte en un profesional de Twig. Solo hay una funcionalidad importante más de la cual hablar. y es una buena: herencia de templates.

La mayoría de nuestras páginas van a compartir una estructura HTML. Actualmente, no contamos con ninguna estructura HTML. Para hacer una, arriba del template, escribe {% extends 'base.html.twig' %}.

{% extends 'base.html.twig' %}
<h1>{{ question }}</h1>
... lines 4 - 19

Esto le dice a Twig que queremos usar el template base.html.twig como la estructura base. En este momento, este archivo es muy básico, pero es nuestro para modificarlo - y lo haremos pronto.

Pero si refrescas la página... huye! Un gran error!

Un template que hereda de otro no puede incluir contenido fuera de los bloques de Twig.

Cuando heredas de un template, estás diciendo que quieres que el contenido de este template vaya dentro de base.html.twig. Pero... donde? debería Twig ponerlo arriba? Abajo? En algún lugar del medio? Twig no lo sabe!

Estoy seguro que ya habías notado estos bloques, como stylesheets, title y body. Los bloques son "hoyos" donde un template hijo puede agregar contenido. No podemos simplemente heredar de base.html.twig: necesitamos decirle en cuál bloque debe ir el contenido. El bloque body es el lugar perfecto.

Como hacemos esto? Arriba del contenido agrega {% block body %}, y después, {% endblock %}.

{% extends 'base.html.twig' %}
{% block body %}
... lines 4 - 18
{% endblock %}
... lines 20 - 21

Ahora intentalo. Funciona! No pareciera que es mucho... porque la estructura base es tan simple, pero si revisas el código fuente de la página, tenemos la estructura HTML básica.

Agregar, Remover, Cambiar Bloques?

Por cierto, estos bloques en base.html.twig no son especiales: los puedes renombrar, moverlos de lugar, agregar o remover. Entre más bloques agregues, más flexible es tu template "hijo" para agregar contenido en lugares diferentes.

La mayoría de los bloques existentes están vacíos... pero el bloque puede definir contenido por defecto. Como el bloque title. Ves ese Welcome? No es sorpresa, ese es el título actual de la página.

Como se encuentra dentro de un bloque, podemos sobreescribirlo en cualquier template. Mira esto: en donde sea dentro de show.html.twig, escribe {% block title %}, Question, imprime la pregunta, luego {% endblock %}.

{% extends 'base.html.twig' %}
{% block title %}Question: {{ question }}{% endblock %}
{% block body %}
... lines 6 - 20
{% endblock %}
... lines 22 - 23

Esta vez cuando recargamos... tenemos un nuevo título!

Ok, con Twig en nuestras espaldas, vamos a ver una de las funcionalidades más útiles de Symfony... y tu nuevo mejor amigo para depurar: Symfony profiler.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "easycorp/easy-log-handler": "^1.0.7", // v1.0.9
        "sensio/framework-extra-bundle": "^6.0", // v6.2.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/profiler-pack": "*", // v1.0.5
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/twig-pack": "^1.0", // v1.0.1
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.7", // v1.8.0
        "symfony/yaml": "5.0.*" // v5.0.11
    },
    "require-dev": {
        "symfony/profiler-pack": "^1.0" // v1.0.5
    }
}