12.

Generating URLs

|

Share this awesome video!

|

Let's create a "show page" for ships: a page that displays the details for just one ship. The homepage lives in MainController. And so we could add another route and method here. But as my site grows, I'm probably going to have multiple pages related to starships: maybe to edit and delete them. So instead, in the Controller/ directory, create a new class. Call it StarshipController, and, as usual, extend AbstractController.

Creating the Show Page

Inside, let's get to work! Add a public function called show(), I'll add the Response return type, then the route, with /starships/ and a wildcard called {id}. And again, it's optional, but I'll be fancy and add the \d+ so the wildcard only matches a number.

Now, because we have an {id} wildcard, we are allowed to have an $id argument down here. dd($id) to see how we're doing so far.

17 lines | src/Controller/StarshipController.php
// ... lines 1 - 2
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class StarshipController extends AbstractController
{
#[Route('/starships/{id<\d+>}')]
public function show(int $id): Response
{
dd($id);
}
}

Try it. Head to /starships/2. Lovely!

Now we're going to do something familiar: take this $id and query our imaginary database for the matching Starship. The key to doing this is our StarshipRepository service and its helpful find() method.

In the controller, add a StarshipRepository $repository argument... then say $ship equals $repository->find($id). And if not $ship, trigger a 404 page with throw $this->createNotFoundException() and starship not found.

Cool! At the bottom, instead of returning JSON, render a template: return $this->render() and follow the standard naming convention for templates: starship/show.html.twig. Pass this one variable: $ship.

25 lines | src/Controller/StarshipController.php
// ... lines 1 - 4
use App\Repository\StarshipRepository;
// ... lines 6 - 9
class StarshipController extends AbstractController
{
#[Route('/starships/{id<\d+>}')]
public function show(int $id, StarshipRepository $repository): Response
{
$ship = $repository->find($id);
if (!$ship) {
throw $this->createNotFoundException('Starship not found');
}
return $this->render('starship/show.html.twig', [
'ship' => $ship,
]);
}
}

Creating the Template

Controller, check! Next, in the templates/ directory, we could create a starship/ directory and show.html.twig inside. But I want to show you a shortcut from the Symfony PhpStorm plugin. Click on the template name, press Alt+Enter and... check it out! On top it says "Twig: Create Template". Confirm the path and boom! We've got our new template! It's... hiding over here. There it is: starship/show.html.twig.

Pretty much every template starts the same: {% extend 'base.html.twig' %}... then override some blocks! Override title... and this time, let's use that ship variable: ship.name. Finish with endblock.

And for the main content, add the block body... endblock and put an h1 inside. Print ship.name again and... I'll paste in a table with some info.

{% extends 'base.html.twig' %}
{% block title %}{{ ship.name }}{% endblock %}
{% block body %}
<h1>{{ ship.name }}</h1>
<table>
<tbody>
<tr>
<th>Class</th>
<td>{{ ship.class }}</td>
</tr>
<tr>
<th>Captain</th>
<td>{{ ship.captain }}</td>
</tr>
</tbody>
</table>
{% endblock %}

Nothing special here: we're just printing basic ship data.

When we try the page... it's alive!

Linking Between Pages

Next question: from the homepage, how could we add a link to the new ship show page? The most obvious option is to hardcode the URL, like /starships/ then the id. But there's a better way. Instead, we're going to tell Symfony:

Hey, I want to generate a URL to this route.

The advantage is that if we decide later to change the URL of this route, every link to this will update automatically.

Let me show you. Find your terminal and run:

php bin/console debug:router

I haven't mentioned it yet, but every route has an internal name. Right now, they're being auto-generated by Symfony, which is fine. But as soon as you want to generate a URL to a route, we should take control of that name to make sure it never changes.

Find the show page route and add a name key. I'll use app_starship_show.

25 lines | src/Controller/StarshipController.php
// ... lines 1 - 9
class StarshipController extends AbstractController
{
#[Route('/starships/{id<\d+>}', name: 'app_starship_show')]
public function show(int $id, StarshipRepository $repository): Response
// ... lines 14 - 23
}

The name could be anything, but this is the convention I follow: app because it's a route that I'm making in my app, then the controller class name and method name.

Naming a route doesn't change how it works. But it does let us generate a URL to it. Open up templates/main/homepage.html.twig. Down here, turn the ship name into a link. I'll put this onto multiple lines and add an a tag with href="". To generate the URL, say {{ path() }} and pass it the name of the route. I'll put the closing tag on the other side.

If we stop now, this won't quite work. On the homepage:

Some mandatory parameters are missing - id - to generate a URL for route app_starship_show.

That makes sense! We're telling Symfony:

Howdy! I want to generate a URL to this route.

Symfony then responds:

Cool... except that this route has a wildcard in it. So like... what do you want me to put in the URL for the id part?

When there's a wildcard in the route, we need to add a second argument to path() with {}. This is Twig's associative array syntax. So it's exactly like JavaScript: it's a key-value pair list. Pass id set to myShip.id.

48 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
// ... lines 6 - 20
<div>
// ... lines 22 - 23
<table>
<tr>
<th>Name</th>
<td>
<a href="{{ path('app_starship_show', {
id: myShip.id
}) }}">{{ myShip.name }}</a>
</td>
</tr>
// ... lines 33 - 44
</table>
</div>
{% endblock %}

And now... got it! Look at that URL: /starships/3.

Alrighty, our site is still ugly. It's time to start fixing that by bringing in Tailwind CSS and learning about Symfony's AssetMapper component.