Querying the Database
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 SubscribeNow that we've saved some stuff to the database, how can we read or query for it? Once again, at least for simple stuff, Doctrine doesn't want you to worry about querying. Instead, we just ask Doctrine for the objects we want.
Head over to src/Controller/VinylController.php
and find the browse()
action.
// ... lines 1 - 10 | |
class VinylController extends AbstractController | |
{ | |
// ... lines 13 - 37 | |
public function browse(string $slug = null): Response | |
{ | |
$genre = $slug ? u(str_replace('-', ' ', $slug))->title(true) : null; | |
$mixes = $this->mixRepository->findAll(); | |
return $this->render('vinyl/browse.html.twig', [ | |
'genre' => $genre, | |
'mixes' => $mixes, | |
]); | |
} | |
} |
Here, we're loading all of the $mixes
in our project... and we're currently doing it via this MixRepository
service class that we created in the last episode. This class talks to a GitHub repository and reads from a hard-coded text file.
We're going to stop using this MixRepository
and instead load these $mixes
from the database.
Querying through the Entity Manager
Ok: to save objects, we leveraged the EntityManagerInterface
service, which is the most important service by far in Doctrine. Whelp, this service can also query for objects. Let's take advantage of that. Add a new argument to browse()
, type-hinted with EntityManagerInterface
... and call it $entityManager
.
// ... lines 1 - 6 | |
use Doctrine\ORM\EntityManagerInterface; | |
// ... lines 8 - 12 | |
class VinylController extends AbstractController | |
{ | |
// ... lines 15 - 39 | |
public function browse(EntityManagerInterface $entityManager, string $slug = null): Response | |
{ | |
// ... lines 42 - 51 | |
} | |
} |
Then, below, replace the $mixes
line with two lines. Start with $mixRepository = $entityManager->getRepository()
passing this the name of the class that we want to query from. Yes, we think about querying from an entity class, not a table. In this case, we want to query from VinylMix::class
.
We'll talk more about this repository concept in a minute. Then, to get the mixes themselves, say $mixes = $mixRepository->
and call one of the methods on it: findAll()
.
To see what this gives us, let's dd($mixes)
.
// ... lines 1 - 39 | |
public function browse(EntityManagerInterface $entityManager, string $slug = null): Response | |
{ | |
// ... lines 42 - 43 | |
$mixRepository = $entityManager->getRepository(VinylMix::class); | |
$mixes = $mixRepository->findAll(); | |
dd($mixes); | |
// ... lines 47 - 51 | |
} | |
// ... lines 53 - 54 |
Ok, testing time! Spin over, head back to the homepage, click "Browse mixes" to hit that action, and... voila! We get six results! And each of them, most importantly, is a VinylMix
object.
Behind the scenes, Doctrine did query the table and the columns. But instead of giving us that raw data, it put it onto objects and gave us those, which is so much nicer.
Working with Objects in Twig
If we remove the dd()
... this array of VinylMix
object will be passed into the template, instead of the array of array data that we had before. But... the page still works. Though, these images are broken because apparently the service I'm using to load them is down right now. Ah... the joys of video recording. But that won't stop us!
The fact that all the data still renders without any errors is... actually kind of by luck. When we render the template - templates/vinyl/browse.html.twig
- we loop over all of the mixes
. The template works because the old GitHub repository text file had the same keys (like title
, trackCount
, and genre
) as our VinylMix
class.
// ... lines 1 - 28 | |
{% for mix in mixes %} | |
// ... line 30 | |
<div class="mixed-vinyl-container p-3 text-center"> | |
// ... line 32 | |
<p class="mt-2"><strong>{{ mix.title }}</strong></p> | |
<span>{{ mix.trackCount }} Tracks</span> | |
| | |
<span>{{ mix.genre }}</span> | |
| | |
<span>{{ mix.createdAt|ago }}</span> | |
// ... lines 39 - 40 | |
{% endfor %} | |
// ... lines 42 - 46 |
There is one cool thing happening here, though. When we say mix.genre
, mix
is now an object... and this genre
property is private. That means we cannot access it directly. But Twig is smart. It realizes that this is private and looks for a getGenre()
method. So in our template, we say mix.genre
, but in reality, it calls the getGenre()
method. That's pretty awesome.
Visualizing the Queries for the Page
Know what else is awesome? We can see the queries any page made! Down in the web debug toolbar, Doctrine gives us a fancy new icon. Oooo. And if we click into that... tah dah! There's one database query... and we can even see what it is. You can also see a formatted version of it... though I need to refresh the page for this to work... because the Turbo JavaScript library we installed in the first tutorial doesn't always play nice with this profiler area. Anyways, we can also see a runnable version of the query or run "Explain" on it.
The "Repository"
All right, back in the controller, even though we can query through the EntityManagerInterface
, we normally query through something called the repository. dd()
this $mixRepository
object to get more info about it.
// ... lines 1 - 12 | |
class VinylController extends AbstractController | |
{ | |
// ... lines 15 - 39 | |
public function browse(EntityManagerInterface $entityManager, string $slug = null): Response | |
{ | |
// ... lines 42 - 44 | |
dd($mixRepository); | |
// ... lines 46 - 51 | |
} | |
} |
Then go back to the /browse
page and... it's an App\Repository\VinylMixRepository
object. Hey! We know that class! It lives in our code, in the src/Repository/
directory. It was generated by MakerBundle.
Inside the ORM\Entity
attribute above our entity class, MakerBundle generated a repositoryClass
option that points to this. Thanks to this config, our entity, VinylMix
, is tied to VinylMixRepository
. So when you ask Doctrine to give us the repository for the VinylMix
class, it knows to return the VinylMixRepository
object.
The repository for an entity knows everything about how to query for its data. And, without us doing anything, it already has a bunch of useful methods on it for basic queries, like findAll()
, findOneBy()
and several others. In a bit, we'll learn how to add new methods to the repository to make custom queries.
Anyway, VinylMixRepository
is actually a service in the container... so we can get it more easily by autowiring it directly. Add a VinylMixRepository $mixRepository
argument... and then we don't need this line at all. That is simpler... and it still works!
// ... lines 1 - 38 | |
public function browse(VinylMixRepository $mixRepository, string $slug = null): Response | |
{ | |
// ... lines 41 - 42 | |
$mixes = $mixRepository->findAll(); | |
// ... lines 44 - 48 | |
} | |
// ... lines 50 - 51 |
The takeaway is this: if you want to query from a table, you'll do that through the repository of the entity whose data you need.
Next: The fact that we changed our code to load from the database and didn't need to update our Twig template at all was kind of awesome! And courtesy of some Twig magic. Let's talk more about that magic and create a virtual property that we can print in the template.
Received following error: Cannot autowire service "App\Controller\VinylController": argument "$isDebug" of method "__construct()" is type-hinted "bool", you should configure its value explicitly. The Solution: I had to define another service in the services.yaml, on the last rows, after the Mixrepository Service: