gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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.
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 | |
{ | |
'/') ( | |
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
.
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!
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.
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.
... lines 2 - 8 | |
class VinylController extends AbstractController | |
{ | |
'/') ( | |
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> |
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.
... lines 2 - 8 | |
class VinylController extends AbstractController | |
{ | |
'/') ( | |
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.
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!
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.
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!
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);
}
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!
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!
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.
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!
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!
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)
// 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
}
}
Hey, could I ask, how did you implement the changes for each string in the list when adding a song and an artist attribute?