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
11.

Listing Parts

|

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

New mission: we need a page that lists every available part. Our Ferengi sales team will use this for some classic upselling. You know, the usual:

Hey, you just bought a starship, how about some shiny new dilithium crystal organizers or cupholder stabilizers?

Let's use MakerBundle to give us a head start. Find your terminal and run:

symfony console make:controller

Call it... wait for it... PartController. Brilliant! To keep things focused, say no to tests.

Voila! One class and one template. So far, so good. Take a peek at the new PartController:

19 lines | src/Controller/PartController.php
// ... lines 1 - 2
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class PartController extends AbstractController
{
#[Route('/part', name: 'app_part')]
public function index(): Response
{
return $this->render('part/index.html.twig', [
'controller_name' => 'PartController',
]);
}
}

Not much to see: it renders a template. Wow.

Change the URL to /parts, and rename it to app_part_index:

19 lines | src/Controller/PartController.php
// ... lines 1 - 8
final class PartController extends AbstractController
{
#[Route('/parts', name: 'app_part_index')]
public function index(): Response
// ... lines 13 - 16
}
}

Copy the route name so we can link to it... and open up base.html.twig.

Linking to the Parts Page

Remember that "About" link that's sitting there doing nothing? Commandeer that and turn it into a "Parts" link. Set the href to {{ path('app_part_index') }}:

44 lines | templates/base.html.twig
// ... line 1
<html>
// ... lines 3 - 13
<body class="text-white" style="background: radial-gradient(102.21% 102.21% at 50% 28.75%, #00121C 42.62%, #013954 100%);">
<div class="flex flex-col justify-between min-h-screen relative">
<div>
<header class="h-[114px] shrink-0 flex flex-col sm:flex-row items-center sm:justify-between py-4 sm:py-0 px-6 border-b border-white/20 shadow-md">
// ... lines 18 - 20
<nav class="flex space-x-4 font-semibold">
// ... lines 22 - 24
<a class="hover:text-amber-400 pt-2" href="{{ path('app_part_index') }}">
Parts
</a>
// ... lines 28 - 33
</nav>
</header>
// ... line 36
</div>
// ... lines 38 - 40
</div>
</body>
</html>

Head to the homepage, click our newly minted link, and... well, it's not the prettiest sight, but it works!

Before we celebrate, we should change the title from the rather uninspiring Hello PartController. Open up templates/part/index.html.twig We're already overriding the title block, so let's make it something exciting like Parts:

21 lines | templates/part/index.html.twig
// ... lines 1 - 2
{% block title %}Parts!{% endblock %}
// ... lines 4 - 21

Adding Some Substance: Looping Over Parts

To loop over the parts, in PartController, we need to query for all the parts.

Add a StarshipPartRepository argument to autowire that in. Call it whatever you want, like $leeroyJenkins or... $repository. To get all the parts — it's simple: $parts = repository->findAll():

22 lines | src/Controller/PartController.php
// ... lines 1 - 4
use App\Repository\StarshipPartRepository;
// ... lines 6 - 9
final class PartController extends AbstractController
{
#[Route('/parts', name: 'app_part_index')]
public function index(StarshipPartRepository $repository): Response
{
$parts = $repository->findAll();
// ... lines 16 - 19
}
}

Printing Parts in the Template

Now that we have this parts variable in our template, we can loop over it:

22 lines | src/Controller/PartController.php
// ... lines 1 - 9
final class PartController extends AbstractController
{
#[Route('/parts', name: 'app_part_index')]
public function index(StarshipPartRepository $repository): Response
{
$parts = $repository->findAll();
return $this->render('part/index.html.twig', [
'parts' => $parts,
]);
}
}

To spice things up, I'll paste in this template:

{% extends 'base.html.twig' %}
{% block title %}Parts!{% endblock %}
{% block body %}
<div class="space-y-5 mx-5">
{% for part in parts %}
<div class="bg-[#16202A] rounded-2xl p-5 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between items-center">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"
class="h-[83px] w-[84px] fill-current {{ cycle(['text-red-400', 'text-blue-400', 'text-green-400', 'text-purple-400', 'text-yellow-400'], loop.index0) }}"
><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M308.5 135.3c7.1-6.3 9.9-16.2 6.2-25c-2.3-5.3-4.8-10.5-7.6-15.5L304 89.4c-3-5-6.3-9.9-9.8-14.6c-5.7-7.6-15.7-10.1-24.7-7.1l-28.2 9.3c-10.7-8.8-23-16-36.2-20.9L199 27.1c-1.9-9.3-9.1-16.7-18.5-17.8C173.9 8.4 167.2 8 160.4 8l-.7 0c-6.8 0-13.5 .4-20.1 1.2c-9.4 1.1-16.6 8.6-18.5 17.8L115 56.1c-13.3 5-25.5 12.1-36.2 20.9L50.5 67.8c-9-3-19-.5-24.7 7.1c-3.5 4.7-6.8 9.6-9.9 14.6l-3 5.3c-2.8 5-5.3 10.2-7.6 15.6c-3.7 8.7-.9 18.6 6.2 25l22.2 19.8C32.6 161.9 32 168.9 32 176s.6 14.1 1.7 20.9L11.5 216.7c-7.1 6.3-9.9 16.2-6.2 25c2.3 5.3 4.8 10.5 7.6 15.6l3 5.2c3 5.1 6.3 9.9 9.9 14.6c5.7 7.6 15.7 10.1 24.7 7.1l28.2-9.3c10.7 8.8 23 16 36.2 20.9l6.1 29.1c1.9 9.3 9.1 16.7 18.5 17.8c6.7 .8 13.5 1.2 20.4 1.2s13.7-.4 20.4-1.2c9.4-1.1 16.6-8.6 18.5-17.8l6.1-29.1c13.3-5 25.5-12.1 36.2-20.9l28.2 9.3c9 3 19 .5 24.7-7.1c3.5-4.7 6.8-9.5 9.8-14.6l3.1-5.4c2.8-5 5.3-10.2 7.6-15.5c3.7-8.7 .9-18.6-6.2-25l-22.2-19.8c1.1-6.8 1.7-13.8 1.7-20.9s-.6-14.1-1.7-20.9l22.2-19.8zM112 176a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zM504.7 500.5c6.3 7.1 16.2 9.9 25 6.2c5.3-2.3 10.5-4.8 15.5-7.6l5.4-3.1c5-3 9.9-6.3 14.6-9.8c7.6-5.7 10.1-15.7 7.1-24.7l-9.3-28.2c8.8-10.7 16-23 20.9-36.2l29.1-6.1c9.3-1.9 16.7-9.1 17.8-18.5c.8-6.7 1.2-13.5 1.2-20.4s-.4-13.7-1.2-20.4c-1.1-9.4-8.6-16.6-17.8-18.5L583.9 307c-5-13.3-12.1-25.5-20.9-36.2l9.3-28.2c3-9 .5-19-7.1-24.7c-4.7-3.5-9.6-6.8-14.6-9.9l-5.3-3c-5-2.8-10.2-5.3-15.6-7.6c-8.7-3.7-18.6-.9-25 6.2l-19.8 22.2c-6.8-1.1-13.8-1.7-20.9-1.7s-14.1 .6-20.9 1.7l-19.8-22.2c-6.3-7.1-16.2-9.9-25-6.2c-5.3 2.3-10.5 4.8-15.6 7.6l-5.2 3c-5.1 3-9.9 6.3-14.6 9.9c-7.6 5.7-10.1 15.7-7.1 24.7l9.3 28.2c-8.8 10.7-16 23-20.9 36.2L315.1 313c-9.3 1.9-16.7 9.1-17.8 18.5c-.8 6.7-1.2 13.5-1.2 20.4s.4 13.7 1.2 20.4c1.1 9.4 8.6 16.6 17.8 18.5l29.1 6.1c5 13.3 12.1 25.5 20.9 36.2l-9.3 28.2c-3 9-.5 19 7.1 24.7c4.7 3.5 9.5 6.8 14.6 9.8l5.4 3.1c5 2.8 10.2 5.3 15.5 7.6c8.7 3.7 18.6 .9 25-6.2l19.8-22.2c6.8 1.1 13.8 1.7 20.9 1.7s14.1-.6 20.9-1.7l19.8 22.2zM464 304a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"/></svg>
<div class="ml-5">
<h4 class="text-[22px] font-semibold">
<a class="hover:text-slate-200" href="#">
{{ part.name }} <span class="text-sm text-slate-400">(assigned to SHIP NAME)</span>
</a>
</h4>
<div class="text-lg text-green-400 font-medium">{{ part.price }}</div>
<p class="text-slate-400 text-sm">{{ part.notes }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

It's just a bunch of stuff to make it look nice. You can get this code from the code block on this page.

Refresh, and... so much better!

A Little Trick: Using the Cycle Function

One interesting thing I'm using here is the cycle() function:

27 lines | templates/part/index.html.twig
// ... lines 1 - 4
{% block body %}
<div class="space-y-5 mx-5">
{% for part in parts %}
<div class="bg-[#16202A] rounded-2xl p-5 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between items-center">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"
class="h-[83px] w-[84px] fill-current {{ cycle(['text-red-400', 'text-blue-400', 'text-green-400', 'text-purple-400', 'text-yellow-400'], loop.index0) }}"
// ... lines 12 - 21
</div>
</div>
{% endfor %}
</div>
{% endblock %}

I wanted to give each gear a random color to make it look more appealing. The cycle() function lets us pass a bunch of strings, and then loop.index 0 cycles through them, It's a small touch, but adds that flair the Ferengi love.

Lastly, replace assigned to SHIP NAME with {{ part.ship }} - this time, I'm not using ship.part, but the other side of the relationship, part.ship.name. Oops, my bad, it should be part.starship.name:

27 lines | templates/part/index.html.twig
// ... lines 1 - 4
{% block body %}
<div class="space-y-5 mx-5">
{% for part in parts %}
<div class="bg-[#16202A] rounded-2xl p-5 flex flex-col min-[1174px]:flex-row min-[1174px]:justify-between items-center">
<div class="flex items-center">
// ... lines 10 - 12
<div class="ml-5">
<h4 class="text-[22px] font-semibold">
<a class="hover:text-slate-200" href="#">
{{ part.name }} <span class="text-sm text-slate-400">(assigned to {{ part.starship.name }})</span>
</a>
</h4>
// ... lines 19 - 20
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

And... got it!

Next up, we'll talk joins. Join me! Sorry, couldn't resist.