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

Generate URLs

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.

Go back to the "show" page for a question. The logo on top is a link... that doesn't go anywhere yet. This should take us back to the homepage.

Because this is part of the layout, the link lives in base.html.twig. Here it is: navbar-brand with href="#".

... line 1
<html>
... lines 3 - 12
<body>
<nav class="navbar navbar-light bg-light" style="height: 100px;">
<a class="navbar-brand" href="#">
... lines 16 - 17
</a>
... line 19
</nav>
... lines 21 - 26
</body>
</html>

To make this link back to the homepage, we can just change this to /, right? You could do this, but in Symfony, a better way is to ask Symfony to generate the URL to this route. That way, if we decide to change this URL later, all our links will update automatically.

Each Route Has a Name!

To see how to do that, find your terminal and run:

php bin/console debug:router

This lists every route in the system... and hey! Since the last time we ran this, there are a bunch of new routes. These power the web debug toolbar and the profiler and are added automatically by the WebProfilerBundle when we're in dev mode.

Anyways, what I really want to look at is the "Name" column. Every route has an internal name, including the two routes that we made. Apparently their names are app_question_homepage and app_question_show. But... uh... where did those come from? I don't remember typing either of these!

So... every route must be given an internal name. But when you use annotation routes... it lets you cheat: it chooses a name for you based on the controller class and method... which is awesome!

But... as soon as you need to generate the URL to a route, I recommend giving it an explicit name, instead of relying on this auto-generated name, which could change suddenly if you rename your method. To give the route a name, add name="" and... how about: app_homepage.

... lines 1 - 8
class QuestionController extends AbstractController
{
/**
* @Route("/", name="app_homepage")
*/
public function homepage()
{
... line 16
}
... lines 18 - 34
}

I like to keep my route names short, but app_ makes it long enough that I could search my project for this string if I ever needed to.

Now if we run debug:router again:

php bin/console debug:router

Nice! We are in control of the route's name. Copy the app_homepage name and then go back to base.html.twig. The goal is simple, we want to say:

Hey Symfony! Can you please give me the URL to the app_homepage route?

To do that in Twig, use {{ path() }} and pass it the route name.

... line 1
<html>
... lines 3 - 12
<body>
<nav class="navbar navbar-light bg-light" style="height: 100px;">
<a class="navbar-brand" href="{{ path('app_homepage') }}">
... lines 16 - 17
</a>
... line 19
</nav>
... lines 21 - 26
</body>
</html>

That's it! When we move over and refresh... now this links to the homepage.

Linking to a Route with {Wildcards}

On the homepage, we have two hard-coded questions... and each has two links that currently go nowhere. Let's fix these!

Step one: now that we want to generate a URL to this route, find the route and add name="app_question_show".

... lines 1 - 8
class QuestionController extends AbstractController
{
... lines 11 - 18
/**
* @Route("/questions/{slug}", name="app_question_show")
*/
public function show($slug)
{
... lines 24 - 33
}
}

Copy this and open the template: templates/question/homepage.html.twig. Let's see... right below the voting stuff, here's the first link to a "Reversing a spell" question. Remove the pound sign, add {{ path() }} and paste in app_question_show.

But... we can't stop here. If we try the page now, a glorious error!

Some mandatory parameters are missing - "slug"

That makes sense! We can't just say "generate the URL to app_question_show" because that route has a wildcard! Symfony needs to know what value it should use for {slug}. How do we tell it? Add a second argument to path() with {}. The {} is a Twig associative array... again, just like JavaScript. Pass slug set to... let's see... this is a hardcoded question right now, so hardcode reversing-a-spell.

... lines 1 - 2
{% block body %}
... lines 4 - 9
<div class="container">
... lines 11 - 15
<div class="row">
<div class="col-12">
<div style="box-shadow: 2px 3px 9px 4px rgba(0,0,0,0.04);">
<div class="q-container p-4">
<div class="row">
... lines 21 - 27
<div class="col">
<a class="q-title" href="{{ path('app_question_show', { slug: 'reversing-a-spell' }) }}"><h2>Reversing a Spell</h2></a>
... lines 30 - 34
</div>
</div>
</div>
<a class="answer-link" href="{{ path('app_question_show', { slug: 'reversing-a-spell' }) }}" style="color: #fff;">
... lines 39 - 41
</a>
</div>
</div>
... lines 45 - 71
</div>
</div>
{% endblock %}
... lines 75 - 76

Copy that entire thing, because there's one other link down here for the same question. For the second question... paste again, but change it to pausing-a-spell to match the name. I'll copy that... find the last spot... and paste.

... lines 1 - 2
{% block body %}
... lines 4 - 9
<div class="container">
... lines 11 - 15
<div class="row">
... lines 17 - 45
<div class="col-12 mt-3">
<div class="q-container p-4">
<div class="row">
... lines 49 - 55
<div class="col">
<a class="q-title" href="{{ path('app_question_show', { slug: 'pausing-a-spell' }) }}"><h2>Pausing a Spell</h2></a>
... lines 58 - 62
</div>
</div>
</div>
<a class="answer-link" href="{{ path('app_question_show', { slug: 'pausing-a-spell' }) }}" style="color: #fff;">
... lines 67 - 69
</a>
</div>
</div>
</div>
{% endblock %}
... lines 75 - 76

Later, when we introduce a database, we'll make this fancier and avoid repeating ourselves so many times. But! If we move over, refresh... and click a link, it works! Both pages go to the same route, but with a different slug value.

Next, let's take our site to the next level by creating a JSON API endpoint that we will consume with JavaScript.

Leave a comment!

5
Login or Register to join the conversation
Joaquim P. Avatar
Joaquim P. Avatar Joaquim P. | posted 1 year ago

Hi! Great Tutorials! Congrats!
Just a notice: There is a typo in challenge#1 It should be @Route("/user/profile", name:"app_user_profile") instead of @Route("/user/profile", name="app_user_profile").
But well, I think everyone understood :)

Reply

Hey Joaquim P.!

Ah, actually I think it IS correct (but I appreciate you making sure anyway!). The tricky thing here is that, in this tutorial, we're using route annotations (the stuff that starts with an @ symbol). Lately, you're probably more accustomed to seeing route *attributes (the stuff that is surrounded by #[]). For annotations, the name="app_user_profile" is correct. For attributes, the name: would be correct. But, anyways, I'm happy to say goodbye to annotations in favor or attributes ;).

Cheers!

1 Reply
Joaquim P. Avatar

Thank you Ryan!
Oh yes, of course you're right! My bad. In fact, I was confusing attributes and annotations :)

Reply
Tanguy D. Avatar
Tanguy D. Avatar Tanguy D. | posted 2 years ago

Till now I love the course, thank you so much. But man, the last sentence, i'm scared. Let's continue.

Reply

Hey TanguyD,

Haha, I hope it won't scare you anymore after you finish this course ;) Thanks for your feedback!

Cheers!

Reply
Cat in space

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

This tutorial also works great for Symfony 6!

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