This course is still being released! Check back later for more chapters.

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
Login to bookmark this video
Buy Access to Course
09.

Ordering a Relation and "fetch" type

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Click into an "in progress" starship. Then open: templates/starship/show.html.twig. To list the parts, use for part in ship.parts.

This will work like a charm. But with a catch: the order of the parts isn't guaranteed. They pop out of the database in whatever order they want!

I'd rather have these ordered by name. Does this mean we need to write a custom query... and can't use our handy ship.parts anymore?

Fear not, friends! Let's learn some tricks!

Rearranging the Parts

Head over to the Starship entity and find the parts property. Above parts, add a new attribute: #[ORM\OrderBy(['name' => 'ASC'])], not position:

183 lines | src/Entity/Starship.php
// ... lines 1 - 13
class Starship
{
// ... lines 16 - 45
#[ORM\OrderBy(['name' => 'ASC'])]
private Collection $parts;
// ... lines 48 - 181
}

Refresh the page, and got it!

If you're scratching your head wondering why T is coming before c, you haven't forgotten your ABCs. It's just that Postgres is a case-sensitive database. So the uppercase T apparently comes before lowercase C in alphabetical order.

Smart Queries

Check the queries for this page and view the formatted SQL. It queries from starship_part, where starship_id equals our ID, ordered by name ascending: it's exactly the query we want!

The N+1 Problem

Head back to the homepage and open up its template: templates/main/homepage.html.twig. After "arrived", add a div then print out the part count: ship.parts|length:

75 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 - 33
<div>
Arrived: {{ ship.arrivedAt|ago }}
</div>
<div>
Parts: {{ ship.parts|length }}
</div>
</div>
</div>
// ... lines 42 - 52
</div>
{% endfor %}
</div>
// ... lines 56 - 71
</div>
</main>
{% endblock %}

Back on the homepage, it works like a charm. Check out the queries for this page, they're interesting. Some of these look a bit wild because of our pagination, but essentially, we have one query for the starship, and if we search for starship_part, there are 5 extra queries for the parts for each starship.

Here's what's happening: we grab the starships, then as soon as we count ship.parts, Doctrine realizes it doesn't have that data yet. So it fetches all the parts for each ship one by one and counts them. This is a common situation: we have one query for the ships and then one extra query for the parts of each ship. It's known as the N+1 problem: 1 query for the starships, and N queries for the parts of each ship. It's a minor performance problem that we're going to tackle later.

Efficient Querying

But there's a bigger problem here! We query for every starship_part just to count them. We don't need the part data, we just need to know how many we've got. This is minor... until you have a ship with a ton of parts.

To fix this, in the OneToMany in the Starship entity, add a fetch option set to EXTRA_LAZY:

183 lines | src/Entity/Starship.php
// ... lines 1 - 13
class Starship
{
// ... lines 16 - 44
#[ORM\OneToMany(targetEntity: StarshipPart::class, mappedBy: 'starship', fetch: 'EXTRA_LAZY', orphanRemoval: true)]
// ... line 46
private Collection $parts;
// ... lines 48 - 181
}

Let's go see what that did!

Counting the Parts

Head back to the homepage. Earlier, we had nine queries... Now??? Still nine queries, but the query for the parts changed. Instead of querying for all their data, it just counts them. Much smarter, right?

You might be wondering - I certainly did - why we don't use fetch="EXTRA_LAZY" all the time? First, this is a small performance optimization that you don't need to worry about unless you have a ship full of parts, and you just want to count them. More importantly, depending on if you count or loop over the parts first, this could cause an extra query.

The Criteria System

Onto our next challenge! What if we only want the related parts for a ship that cost above a certain price? Can we still use the ship.parts shortcut or do we need to do a custom query? Stay tuned, we're going to explore the criteria system next.