Login to bookmark this video
Buy Access to Course
12.

High-Tech Controllers: Auto-inject Entities

|

Share this awesome video!

|

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

This Chapter isn't quite ready...

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com

When we drill into a ship from our homepage and see its show page, this URL isn't very pretty or memorable. It's just the ID of the ship. Imagine if Jean-Luc Picard announced he was the captain of the USS 43 instead of the Enterprise. Lame!

Let's change this to use our new slug field instead. Like id, it's unique, so we can use it to find a single ship in the database.

First though, I want to show you something super cool. Open StarshipController::show(). We are injecting the $id from our route parameter and the StarshipRepository service to find the ship from this ID. Then we have logic to throw a 404 if the ship isn't found.

Inject Starship Directly

Replace all the arguments with just Starship $ship, then, delete all this finding and not found logic:

20 lines | src/Controller/StarshipController.php
// ... lines 1 - 9
class StarshipController extends AbstractController
{
#[Route('/starships/{id<\d+>}', name: 'app_starship_show')]
public function show(Starship $ship): Response
{
return $this->render('starship/show.html.twig', [
'ship' => $ship,
]);
}
}

This is one slim controller now - I love it. If you're saying "but Starship isn't a service", you're right, but bear with me.

Back in the app, we're on the Starship show page. Refresh... and... it still works! Let's try visiting a ship we know doesn't exist: one with ID 999. We get a 404 error. We still have the same logic as before... How?!

Entities are not services... that's still, and always, true. Look in our MainController::homepage() controller. We are injecting the Request object. This also isn't a service. If you tried to autowire this into a service's constructor, you'd get an error.

Controller Value Resolvers

Controllers are special. When Symfony calls a controller method, it first looks at all the arguments and passes them through something called "controller value resolvers". There are several, and we've used a bunch already - though we didn't know it. There's a RequestValueResolver to inject the Request object and a ServiceValueResolver if an argument is type-hinted with a service.

Symfony's Doctrine integration provides an EntityValueResolver. This is how we're able to inject the Starship entity. It works because we've type-hinted Starship, a valid Doctrine entity, and we have an id route parameter. Since every entity has an id, the resolver automatically queries for the entity then passes it to us. If the entity isn't found, it throws a 404. I love this!

Using slug in the URL

Back to our mission: to use the Starship slug in the URL instead of the ID. First, update the #[Route] attribute to /starship/{slug}:

20 lines | src/Controller/StarshipController.php
// ... lines 1 - 9
class StarshipController extends AbstractController
{
#[Route('/starships/{slug}', name: 'app_starship_show')]
public function show(Starship $ship): Response
// ... lines 14 - 18
}

Next, we need to update all the places where we generate the URL for this route. Don't worry, there are only 2.

Start with templates/main/homepage.html.twig. Search for "show" - here we go. In the path function, replace id: ship.id with slug: ship.slug:

72 lines | templates/main/homepage.html.twig
// ... lines 1 - 4
{% block body %}
<main class="flex flex-col lg:flex-row">
// ... lines 7 - 8
<div class="px-12 pt-10 w-full">
// ... lines 10 - 17
<div class="space-y-5">
{% for ship in ships %}
<div class="bg-[#16202A] rounded-2xl pl-5 py-5 pr-11 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between">
<div class="flex justify-center min-[1174px]:justify-start">
// ... line 22
<div class="ml-5">
// ... lines 24 - 27
<h4 class="text-[22px] pt-1 font-semibold">
<a
class="hover:text-slate-200"
href="{{ path('app_starship_show', { slug: ship.slug }) }}"
>{{ ship.name }}</a>
</h4>
// ... lines 34 - 36
</div>
</div>
// ... lines 39 - 49
</div>
{% endfor %}
</div>
// ... lines 53 - 68
</div>
</main>
{% endblock %}

Now, open templates/main/_shipStatusAside.html.twig, find "show", and in this path replace id: myShip.id with slug: myShip.slug:

37 lines | templates/main/_shipStatusAside.html.twig
<aside
// ... lines 2 - 3
>
// ... lines 5 - 11
<div>
<div class="flex flex-col space-y-1.5">
// ... lines 14 - 17
<h3 class="tracking-tight text-[22px] font-semibold">
<a class="hover:underline" href="{{ path('app_starship_show', {
slug: myShip.slug
}) }}">{{ myShip.name }}</a>
</h3>
</div>
// ... lines 24 - 34
</div>
</aside>

Jump back to our app and click "Back" to go to the homepage. Hover over a ship link and look the URL. It's much prettier! Click the link.

Red alert!

Cannot autowire argument $ship...".

The problem is that when there is no route wildcard called id, it reverts back to trying to autowire Starship like a service. When the route wildcard is not called id, we need to help it a bit.

#[MapEntity] Attribute

Back in StarshipController::show(), move Starship $ship to its own line to give us some room. Above it, add an attribute: #[MapEntity] with an array with a key of slug - this is the route parameter name and a value of also slug - this is the property name it should query on:

23 lines | src/Controller/StarshipController.php
// ... lines 1 - 10
class StarshipController extends AbstractController
{
// ... line 13
public function show(
#[MapEntity(mapping: ['slug' => 'slug'])]
Starship $ship,
): Response {
// ... lines 18 - 20
}
}

Back to the app and refresh. It's working again, red alert cancelled!

Try putting random text in for the slug and... 404! Perfect!

Now our ship URLs are pretty, human-readable, and SEO-friendly!

Flying through space is dangerous business. Sometimes starships experience "rapid unscheduled disassemblies"... or in other words, they explode. We need a way to remove ships from our database that no longer... err... exist. Next, we'll see how to delete entities with Doctrine.