This course is still being released! Check back later for more chapters.
Fetching a Relation's Data
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNavigate to the homepage and click on any of the starships with an 'In Progress' status.
Hey! We're already listing the parts... sort of... these are all hard-coded!
Now, how do we get the parts that are related to this ship?
Open the controller for this page: src/Controller/StarshipController.php
Querying for Related Parts Like any Other Property
To query for parts, we would typically autowire the StarshipPartRepository
. Start the same way here with a StarshipPartRepository $partRepository
argument:
// ... lines 1 - 5 | |
use App\Repository\StarshipPartRepository; | |
// ... lines 7 - 12 | |
class StarshipController extends AbstractController | |
{ | |
'/starships/{slug}', name: 'app_starship_show') | (|
public function show( | |
// ... lines 17 - 18 | |
StarshipPartRepository $partRepository, | |
): Response { | |
// ... lines 21 - 25 | |
} | |
} |
Next, set $parts
, to $partRepository->findBy()
:
This is pretty standard stuff: if you want to query where some property equals a value, use findBy()
and pass the property name and the value. When it comes to relationships, it's the same darn thing!
$parts = $partRepository->findBy(['starship' => $ship])
.
And no, we're not doing Starship ID
or anything of the sort. Keep IDs out of this! Instead, pass the Starship
object itself. You could pass the id
if you're feeling lazy, but in the spirit of Doctrine, relationships, and thinking about objects, passing the entire Starship
object is the way to go.
Let's debug and see what we've got: dd($parts)
:
// ... lines 1 - 12 | |
class StarshipController extends AbstractController | |
{ | |
// ... line 15 | |
public function show( | |
// ... lines 17 - 18 | |
StarshipPartRepository $partRepository, | |
): Response { | |
$parts = $partRepository->findBy(['starship' => $ship]); | |
dd($parts); | |
// ... lines 23 - 25 | |
} | |
} |
Refresh, and voila! An array of 10 StarshipPart
objects, all related to this Starship
. Pretty awesome, right? If you think so, hold onto your pants.
The Grabbing the Related Parts the Easy Way
Replace $parts
with $ship->getParts()
:
// ... lines 1 - 12 | |
class StarshipController extends AbstractController | |
{ | |
// ... line 15 | |
public function show( | |
// ... lines 17 - 19 | |
): Response { | |
// ... line 21 | |
dd($ship->getParts()); | |
// ... lines 23 - 25 | |
} | |
} |
Refresh! Instead of an array of StarshipPart
objects, we get a PersistentCollection
object that looks... empty. Remember the ArrayCollection
that make:entity
added to our Starship
constructor? PersistentCollection
and ArrayCollection
are part of the same collection family. They're objects but they act like arrays. Cool... but why does this collection look empty? It's because Doctrine is smart: it doesn't query for the parts until we need them. Loop over $ship->getParts()
and dump $part
:
// ... lines 1 - 12 | |
class StarshipController extends AbstractController | |
{ | |
// ... line 15 | |
public function show( | |
// ... lines 17 - 19 | |
): Response { | |
// ... lines 21 - 22 | |
foreach ($ship->getParts() as $part) { | |
dump($part); | |
} | |
// ... lines 26 - 29 | |
} | |
} |
Suddenly that empty-looking collection is full of the 10 StarshipPart
objects. Magic!
Lazy Relation Queries
There are two queries at play here. The first one is for the Starship
, and the second one is for all its StarshipPart
s. The first comes from Symfony querying for the Starship
based on the slug. The second is more interesting: happens the moment we foreach
over the parts
. At that exact instance Doctrine says:
I just remembered: I don't actually have the
StarshipPart
s data for thisStarship
. Let me go and get that.
Isn't that just amazing? Makes me want to throw a party for Doctrine.
Tidying Up and Looping Over Parts
Get rid of the parts
variable entirely... and remove StarshipPartRepository
: that was way too much work. Instead, set a parts
variable to $ship->getParts()
:
// ... lines 1 - 12 | |
class StarshipController extends AbstractController | |
{ | |
'/starships/{slug}', name: 'app_starship_show') | (|
public function show( | |
#[MapEntity(mapping: ['slug' => 'slug'])] | |
Starship $ship, | |
): Response { | |
return $this->render('starship/show.html.twig', [ | |
'ship' => $ship, | |
'parts' => $ship->getParts(), | |
]); | |
} | |
} |
Now that we've got our shiny new parts
variable, Loop over that in the template. Open up templates/starship/show.html.twig
and replace the hard-coded part with our loop: for part in parts
, part.name
, part.price
, part.notes
, endfor
:
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 19 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 21 - 25 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
// ... lines 29 - 61 | |
<ul class="text-sm font-medium text-slate-400 tracking-wide"> | |
{% for part in parts %} | |
<li class="border-b border-slate-600 py-2"> | |
<span class="block text-white font-semibold"> | |
{{ part.name }} (✦ {{ part.price }}) | |
</span> | |
<span class="text-xs text-slate-500 italic"> | |
{{ part.notes }} | |
</span> | |
</li> | |
{% endfor %} | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
Still too much Work?
And we've done it! We've managed to display all 10 related parts, without doing any serious work thanks to $ship->getParts()
.
But you know what? Even this is too much work. Get rid of the parts
variable entirely:
// ... lines 1 - 12 | |
class StarshipController extends AbstractController | |
{ | |
// ... line 15 | |
public function show( | |
// ... lines 17 - 18 | |
): Response { | |
return $this->render('starship/show.html.twig', [ | |
'ship' => $ship, | |
]); | |
} | |
} |
for part in ship.parts
:
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 19 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 21 - 25 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
// ... lines 29 - 61 | |
<ul class="text-sm font-medium text-slate-400 tracking-wide"> | |
{% for part in ship.parts %} | |
// ... lines 64 - 71 | |
{% endfor %} | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
And... still not broken! For kicks and giggles, let's also display the number of parts for this ship. ship.parts|length
:
// ... lines 1 - 4 | |
{% block body %} | |
// ... lines 6 - 19 | |
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8"> | |
// ... lines 21 - 25 | |
<div class="space-y-5"> | |
<div class="mt-8 max-w-xl mx-auto"> | |
<div class="px-8 pt-8"> | |
// ... lines 29 - 58 | |
<h4 class="text-xs text-slate-300 font-semibold mt-2 uppercase"> | |
Parts ({{ ship.parts|length }}) | |
</h4> | |
// ... lines 62 - 73 | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
We still have two queries, but Doctrine, again is smart. It knows we've already queried for all the StarshipPart
s, so when we count them, we don't actually need to make another count query.
Next up: we'll talk about an often-misunderstood topic in Doctrine relations: the owning vs inverse side of each relationship.