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.
| // ... 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 | |
| { | |
| ('/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.
| // ... lines 1 - 4 | |
| use App\Repository\StarshipRepository; | |
| // ... lines 6 - 9 | |
| class StarshipController extends AbstractController | |
| { | |
| ('/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.
| // ... lines 1 - 9 | |
| class StarshipController extends AbstractController | |
| { | |
| ('/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 routeapp_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
idpart?
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.
| // ... 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.
10 Comments
at the 2nd minute and 9 seconds: you show a cheatcode for your code editor. For other viewers, if you dont see the two options with the yellow lightbulb in phpstorm, try this: File -> Settings -> PHP -> Symfony -> Enable plugin.
it was disabled in my editor and enabling it did the trick! :)
Hey Linda,
Thanks for this tip! :)
Cheers!
What if I wanted to add a 'former command'* listing that referenced one of the other ships? How would I modify StarshipController to allow that? Do I just write another find with the other ship id as the variable?
Hey @skribe!
Yep, you're on the right track. For the app in its current state, you'd need to add the former ship id to the model, then use the repository to grab it in your controller.
Once the database/Doctrine is setup, this will be much easier. This is a perfect example of a Doctrine relationship and that's the next course to come out in this track! Keep an eye out for that - it should be dropping in the next few weeks.
--Kevin
Hi!
Using the same regex
<\d+>twice in 2 different places (StarshipControllerandStarshipApiController) is code duplication, does Symfony allow to reuse regex in the#[Route ...]attribute with an existing functional, like a service?Thanks!
Hey @randomsymbols!
Good point. The Routing component includes a Requirement enum that includes common regexes (
Requirement::DIGITScould be used here). Check out this example to see how it works. I like using this as it makes your routes easier to read/understand.You can also add your own constants (or backed enums) for commonly used patterns in your application.
I personally prefer using the
requirementsattribute argument vs embedding the regex in the path (again, to ease readability).Hope that helps!
Kevin
Hi, at the 2nd minute and 9 seconds: https://symfonycasts.com/screencast/symfony/generate-urls?playAt=127 - you show a cheatcode for your code editor, is there an extension or similar for VSCODE? THANKS :)
Hey Vince,
I see you're referring to creating a template feature from the Symfony plugin. Unfortunately, I don't use VSCode so can't say you for sure, but as I see over the internet there's no something similar there. You just need to create that file manually. What I see is that you can create a "custom snippet" in VS Code that makes it easier to generate basic Twig templates. This won't automatically generate the file, but it can streamline repetitive tasks for you I suppose.
I hope this helps!
Cheers!
Thanks for your response :)
I found the same information on the Internet but the task is not automatic, you must to enter the path, name, ext... well.. create the file is most quick. ^^
Hey Vince,
Yeah, it would be great to have better integration. But so far, Symfony plugin just creates an empty template, so empty file creating should not be much longer :) Otherwise, consider switching to PhpStorm ;)
Cheers!
"Houston: no signs of life"
Start the conversation!