Accessing Data on a ManyToMany
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 SubscribeSimple goal really: print all the droids assigned to a Starship
. If you got comfortable with the OneToMany
relation from Starship
to its parts, then you're going to love this!
Open the template for the Starship
show page: templates/starship/show.html.twig
. I'll steal the h4
and p
tag for arrived at
, paste them below, and change the h4
to Droids
. Clear out arrived at
... and break that line up:
// ... 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"> | |
Droids | |
</h4> | |
<p class="text-[22px] font-semibold"> | |
// ... lines 63 - 67 | |
</p> | |
// ... lines 69 - 84 | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
We have a ship
variable, which is a Starship
object. And remember, it has a droids
property and a getDroids()
method. So, for droid in ship.droids
. This calls the getDroids()
method, and that returns a collection of Droid
objects. So we can say {{ droid.name }}
.
The loop.last
I want commas, but not an extra comma at the end. Say: {% if not loop.last %}, {% endif %}
. There are fancier ways to do this, but keep it simple for now.
If there aren't any droids, use an else
tag and say No droids on board (clean up your own mess)
. Rude!
// ... 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"> | |
Droids | |
</h4> | |
<p class="text-[22px] font-semibold"> | |
{% for droid in ship.droids %} | |
{{ droid.name }}{% if not loop.last %}, {% endif %} | |
{% else %} | |
No droids on board (clean up your own mess) | |
{% endfor %} | |
</p> | |
// ... lines 69 - 84 | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
Droids on the Homepage
On the homepage, we want to show off the droids here too. Open up the template: templates/main/homepage.html.twig
. Right after parts
, add another div with Droids: {{ ship.droidNames ?: 'none' }}
:
// ... 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"> | |
<img class="h-[83px] w-[84px]" src="{{ asset(ship.statusImageFilename) }}" alt="Status: {{ ship.statusString }}"> | |
<div class="ml-5"> | |
// ... lines 24 - 36 | |
<div> | |
Parts: {{ ship.parts|length }}</div> | |
<div> | |
Droids: {{ ship.droidNames ?: 'none' }} | |
</div> | |
</div> | |
</div> | |
// ... lines 44 - 54 | |
</div> | |
{% endfor %} | |
</div> | |
// ... lines 58 - 73 | |
</div> | |
</main> | |
{% endblock %} |
The Smart Method
We could use our loop.last
comma thing again, but we've needed the droid names in two spots, so let's add a smart method for this in the Starship
class. This could go anywhere, but I'll stick it at the bottom with the other droid methods. Create a public function getDroidNames(): string
. To return a comma-separated string of droid names, check this out: return implode(', ',
$this->droids->map(fn(Droid $droid) => $droid->getName())->toArray()):
// ... lines 1 - 15 | |
class Starship | |
{ | |
// ... lines 18 - 223 | |
public function getDroidNames(): string | |
{ | |
return implode(', ', $this->droids->map(fn(Droid $droid) => $droid->getName())->toArray()); | |
} | |
} |
Wow, that was a mouthful! Let's break it down:
First, $this->droids
is our collection of Droid
objects. Second, map()
applies a function to each Droid
in the collection. Third, fn(Droid $droid) => $droid->getName()
is a hipster way to say:
Give me the name of each droid
Fourth, toArray()
converts the collection to an array so it can be used with implode()
. Finally, implode(', ', ...)
takes that array of names and turns it into a comma-separated string.
Now that we've got a getDroidNames()
method, we can say {{ ship.droidNames ?: 'none' }}
.
We're done! Refresh... and enjoy the droid names on the homepage.
Next: let's use Foundry to set the ManyToMany relationship in the fixtures. Another place Foundry shines!