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.

Symfony controller classes do not need to extend a base class. As long as your controller function returns a Response object, Symfony doesn't care what your controller looks like. But usually, you will extend a class called AbstractController.

Why? Because it gives us shortcut methods.

Rendering a Template

And the first shortcut is render(): the method for rendering a template. So return $this->render() and pass it two things. The first is the name of the template. How about vinyl/homepage.html.twig.

It's not required, but it's common to have a directory with the same name as your controller class and filename that's the same as your method, but you can do whatever. The second argument is an array of any variables that you want to pass into the template. Let's pass in a variable called title and set it to our mix tape title: "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

Done in here. Oh, but pop quiz! What do you think the render() method returns? Yea, it's the thing I keep repeating: a controller must always return a Response object. render() is just a shortcut to render a template, get that string and put it into a Response object. render() returns a Response.

Creating the Template

We know from earlier that when you render a template, Twig looks in the templates/ directory. So create a new vinyl/ sub-directory... and inside of that, a file called homepage.html.twig. To start, add an h1 and then print the title variable with a special Twig syntax: {{ title }}. And... I'll add some hardcoded TODO text.

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

Let's... go see if this works! We were working on our homepage, so go there and... hello Twig!

Twigs 3 Syntax

Twig is one of the nicest parts of Symfony, and also one of the easiest. We're going to go through everything you need to know... in basically the next ten minutes.

Twig has exactly three different syntaxes. If you need to print something, use {{. I call this the "say something" syntax. If I say {{ saySomething }} that would print a variable called saySomething. Once you're inside Twig, it looks a lot like JavaScript. For example, if I surround this in quotes, now I'm printing the string saySomething. Twig has functions... so that would call the function and print the result.

So syntax #1 - the "say something" syntax - is {{

The second syntax... doesn't really count. It's {# to create a comment... and that's it.

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

The third and final syntax I call the "do something" syntax. This is when you're not printing, your doing something in the language. Examples of "doing something" would be if statements, for loops or setting variables.

The for Loop

Let's try a for loop. Go back to the controller. I'm going to paste in a tracks list... and then pass a tracks variable into the template set to that array.

<?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
}

Now, unlike title, tracks is an array... so we can't just print it. But, we can try! Ha! That gives us an array to string conversion. Nope, we need to loop over tracks.

Add a header and a ul. To loop, we'll use the "do something" syntax, which is {% and then the thing that you want to do, like for, if or set. I'll show you the full list of do something tags in a minute. A for loop looks like this: for track in tracks, where tracks is the variable we're looping over and track will be the variable inside the loop.

After this, add {% endfor %}: most "do something" tags have an end tag. Inside the loop, add an li and then use the say something syntax to print track.

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

Using Sub.keys

When we try it... nice! Oh, but let's get trickier. Back in the controller, instead of using a simple array, I'll restructure this to make each track an associative array with song and artist keys. I'll paste in that same change for the rest.

<?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
}

What happens if we try it? Ah, we're back to the "array to string" conversion. When we loop, each track itself is now an array. How can we read the song and artist keys?

Remember when I said that Twig looks a lot like JavaScript? Well then, it shouldn't be a surprise that the answer is track.song and track.artist.

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

And... that gets our list working.

Now that we have the basics of Twig down, next, let's look at the full list of "do something" tags, learn about Twig "filters" and tackle the all-important template inheritance system.

Leave a comment!

13
Login or Register to join the conversation
SamuelVicent Avatar
SamuelVicent Avatar SamuelVicent | posted 1 month ago

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
SamuelVicent Avatar SamuelVicent | MolloKhan | posted 1 month ago

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
    }
}