Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

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.

Las clases de controlador de Symfony no necesitan extender una clase base. Mientras tu función de controlador devuelva un objeto Response, a Symfony no le importa el aspecto de tu controlador. Pero normalmente, extenderás una clase llamadaAbstractController.

¿Por qué? Porque nos da métodos de acceso directo.

Renderización de una plantilla

Y el primer atajo es render(): el método para renderizar una plantilla. Así que devuelve $this->render() y le pasa dos cosas. La primera es el nombre de la plantilla. ¿Qué tal vinyl/homepage.html.twig.

No es necesario, pero es habitual tener un directorio con el mismo nombre que la clase de tu controlador y un nombre de archivo que sea el mismo que el de tu método, pero puedes hacer lo que quieras. El segundo argumento es un array con las variables que quieras pasar a la plantilla. Vamos a pasar una variable llamadatitle y a ponerle el título de nuestra cinta de mezclas: "PB and Jams".

... lines 1 - 4
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
... lines 6 - 8
class VinylController extends AbstractController
{
#[Route('/')]
public function homepage(): Response
{
return $this->render('vinyl/homepage.html.twig', [
'title' => 'PB & Jams',
]);
}
... lines 19 - 34

Hecho aquí. Ah, pero, ¡examen sorpresa! ¿Qué crees que devuelve el método render()? Sí, es lo que siempre repito: un controlador siempre debe devolver un objeto Response. render() es sólo un atajo para renderizar una plantilla, obtener esa cadena y ponerla en un objeto Response. render() devuelve un objeto Response.

Crear la plantilla

Sabemos por lo que hemos dicho antes, que cuando renderizas una plantilla, Twig busca en el directorio templates/. Así que crea un nuevo subdirectorio vinyl/... y dentro de él, un archivo llamado homepage.html.twig. Para empezar, añade un h1 y luego imprime la variable title con una sintaxis especial de Twig: {{ title }}. Y... Añadiré un texto TODO codificado.

<h1>{{ title }}</h1>
{# TODO: add an image of the record #}
<div>
Our schweet track list: TODO
</div>

¡Vamos a ver si esto funciona! Estábamos trabajando en nuestra página web, así que ve allí y... ¡hola Twig!

Sintaxis de Twigs 3

Twig es una de las partes más bonitas de Symfony, y también una de las más fáciles. Vamos a repasar todo lo que necesitas saber... básicamente en los próximos diez minutos.

Twig tiene exactamente tres sintaxis diferentes. Si necesitas imprimir algo, utiliza {{. A esto lo llamo la sintaxis "decir algo". Si digo {{ saySomething }}se imprimiría una variable llamada saySomething. Una vez que estás dentro de Twig, se parece mucho a JavaScript. Por ejemplo, si lo encierro entre comillas, ahora estoy imprimiendo la cadena saySomething. Twig tiene funciones... por lo que llamaría a la función e imprimiría el resultado.

Así que la sintaxis nº 1 -la de "decir algo"- es {{

La segunda sintaxis... no cuenta realmente. Es {# para crear un comentario... y ya está.

<h1>{{ title }}</h1>
{# TODO: add an image of the record #}
<div>
Our schweet track list: TODO
</div>

La tercera y última sintaxis la llamo "hacer algo". Esto es cuando no estás imprimiendo, estás haciendo algo en el lenguaje. Ejemplos de "hacer algo" serían las sentencias if, los bucles for o la configuración de variables.

El bucle for

Vamos a probar un bucle for. Vuelve al controlador. Voy a pegar una lista de pistas... y luego pasaré una variable tracks a la plantilla ajustada a esa matriz.

<?php
... lines 2 - 8
class VinylController extends AbstractController
{
#[Route('/')]
public function homepage(): Response
{
$tracks = [
'Gangsta\'s Paradise - Coolio',
'Waterfalls - TLC',
'Creep - Radiohead',
'Kiss from a Rose - Seal',
'On Bended Knee - Boyz II Men',
'Fantasy - Mariah Carey',
];
return $this->render('vinyl/homepage.html.twig', [
'title' => 'PB & Jams',
'tracks' => $tracks,
]);
}
... lines 29 - 42
}

Ahora, a diferencia de title, tracks es una matriz... así que no podemos imprimirla. Pero, ¡podemos intentarlo! ¡Ja! Eso nos da una conversión de matriz a cadena. No, tenemos que hacer un bucle sobre las pistas.

Añade una cabecera y un ul. Para hacer el bucle, usaremos la sintaxis "hacer algo", que es{% y luego la cosa que quieras hacer, como for, if o set. Te mostraré la lista completa de etiquetas de hacer algo en un minuto. Un bucle for tiene este aspecto:for track in tracks, donde pistas es la variable sobre la que hacemos el bucle y trackserá la variable dentro del bucle.

Después de esto, añade {% endfor %}: la mayoría de las etiquetas "hacer algo" tienen una etiqueta de fin. Dentro del bucle, añade un li y luego utiliza la sintaxis de decir algo para imprimir track.

<h1>{{ title }}</h1>
{# TODO: add an image of the record #}
<div>
Tracks:
<ul>
{% for track in tracks %}
<li>
{{ track }}
</li>
{% endfor %}
</ul>
</div>

Uso de Sub.keys

Cuando lo probemos... ¡qué bien! Pero vamos a ponernos más complicados. De vuelta en el controlador, en lugar de utilizar un simple array, lo reestructuraré para que cada pista sea un array asociativo con las claves song y artist. Pondré ese mismo cambio para el resto.

<?php
... lines 2 - 8
class VinylController extends AbstractController
{
#[Route('/')]
public function homepage(): Response
{
$tracks = [
['song' => 'Gangsta\'s Paradise', 'artist' => 'Coolio'],
['song' => 'Waterfalls', 'artist' => 'TLC'],
['song' => 'Creep', 'artist' => 'Radiohead'],
['song' => 'Kiss from a Rose', 'artist' => 'Seal'],
['song' => 'On Bended Knee', 'artist' => 'Boyz II Men'],
['song' => 'Fantasy', 'artist' => 'Mariah Carey'],
];
... lines 23 - 27
}
... lines 29 - 42
}

¿Qué ocurre si lo probamos? Ah, volvemos a la conversión de "matriz a cadena". Cuando hacemos el bucle, cada pista es ahora una matriz. ¿Cómo podemos leer las claves songy artist?

¿Recuerdas cuando dije que Twig se parece mucho a JavaScript? Pues bien, no debería sorprender que la respuesta sea track.song y track.artist.

... lines 1 - 7
<ul>
{% for track in tracks %}
<li>
{{ track.song }} - {{ track.artist }}
</li>
{% endfor %}
</ul>
... lines 15 - 16

Y... eso hace que nuestra lista funcione.

Ahora que ya tenemos lo básico de Twig, vamos a ver la lista completa de etiquetas "hacer algo", a conocer los "filtros" de Twig y a abordar el importantísimo sistema de herencia de plantillas.

Leave a comment!

15
Login or Register to join the conversation
ivangogh Avatar
ivangogh Avatar ivangogh | posted hace 6 meses

Hey, could I ask, how did you implement the changes for each string in the list when adding a song and an artist attribute?

Reply

Hey Ivangogh,

Ryan cheated a little bit by copy-pasting the new array, but he converted a simple array with many string items inside into an array of arrays where each item is an array containing two keys, song, and artist.

[
    [
        'song' => ...,
        'artist' => ...
    ],
    ... // more items here
]

Cheers!

Reply
SamuelVicent Avatar
SamuelVicent Avatar SamuelVicent | posted hace 10 meses

Hi
We are upgrading from SF5.4 to SF6.0 and we get this error when visiting any page:

You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".

We already have that bundle installed, looking to the renderView method, it seems like "twig" service is not being injected to the Controller.
Injecting Environment $twig to constructor of the controller works ( new Response($this->twig->render(...))) but this limitates us, because can not use the shortcuts provided by AbstractController and would have to refactor a lot of code.

Any idea? help on this would be very appreciated :). Thanks a lot.

Reply

Hey Samuel,

That's unexpected :) - Do you have the autoconfigure and autowire features enabled? If that's the case, try injecting the Twig service into any service class. My guess is the Twig service is being removed from the container because nothing is using it

Cheers!

Reply
SamuelVicent Avatar

Hi MolloKhan,
First of all thank you very much for your quick response.
Yep, really unexpected :/
The same project downgraded to SF5.4 works fine, but fails when changed the version to SF6.1 in composer at this point.
We have some services with twig injected, so no idea why fails at this point:

  
    # AbstractController
    # namespace Symfony\Bundle\FrameworkBundle\Controller;
    
    /**
     * Returns a rendered view.
     */
    protected function renderView(string $view, array $parameters = []): string
    {
        if (!$this->container->has('twig')) {
            throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
        }

        return $this->container->get('twig')->render($view, $parameters);
    }
Reply
SamuelVicent Avatar

Hi,

We finnaly found the issue in services.yaml
We had to change the bind parameter => $container: '@service_container'
To the service alias => Symfony\Component\DependencyInjection\ContainerInterface: '@service_container'

We have an auto authentication functionallity in our Behat Context tests, so we needed the $container to be bind for ease of configuration.

Replacing the bind by the alias fixed the issue.

Thank you for your help.
Regards!

Reply

Ohh, so the container service injected into your controllers was been modified due to your binding config, that's interesting!
I'm glad to know you solved your problem. Cheers!

Reply

Upon extending the AbstractController class, the following exception is occurring:
"Controller\VinylController" has no container set, did you forget to define it as a service subscriber?

I tried clearing up the caches with bin/console cache:clear, even stopped the server and restarting it, but the exception is still showing up. What's the solution to this annoying problem, as I cannot proceed further with the tutorial?

Thank you.

Reply

Hey @roenfeldt!

Ah, sorry about the trouble - that's super weird! Hmm. So, the reason why this made happen is a bit technical... your controllers should be (via config/services.yaml) auto-registered as services and autoconfigured (but things we talk about in the next tutorial). It seems that one of those two things isn't happening... but I can't imagine why. Have you modified your services.yaml file at all? What is the namespace inside VinylController? It should be namespace App\Controller - based on the error, are you missing the App\ part at the beginning?

Let me know if any of this helps :). This is basically an error that shouldn't be happening - so there is likely some small typo (and a non-friendly error for the typo) or something else weird happened.

Cheers!

1 Reply

Hello Ryan,

Thank you for the very fast reply, as well as for the solution provided. It was, as you correctly suspected, the namespace which was missing the App\ part for some reason. In any case, now everything's working as it should.

Amazing level of support, I am definitely going to become a paid subscriber at SymfonyCasts very soon :)

Six stars out of five!

Reply

Yay! Cheers and keep up the good work ❤️

Reply
Default user avatar

Typo suggestion: common to have a directory with the same know name as your controller class (can't edit it on github for some reason atm)

Reply

Hey Mark!

Thank you for reporting this! I fixed it in https://github.com/SymfonyC...

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/asset": "6.0.*", // v6.0.3
        "symfony/console": "6.0.*", // v6.0.3
        "symfony/dotenv": "6.0.*", // v6.0.3
        "symfony/flex": "^2", // v2.1.5
        "symfony/framework-bundle": "6.0.*", // v6.0.4
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.3
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-turbo": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/twig": "^2.12|^3.0" // v3.3.8
    },
    "require-dev": {
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/stopwatch": "6.0.*", // v6.0.3
        "symfony/web-profiler-bundle": "6.0.*" // v6.0.3
    }
}
userVoice