Fetching with DQL, the QueryBuilder & find()
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 SubscribeOur database is now full of shiny, dummy starships! But this homepage is still showing the hardcoded ships. Lame! Time to load these from the database. That'll improve the awesomeness of our app x10!
Spin over to your terminal. Remember that SQL query to select all starships? Run it again:
symfony console doctrine:query:sql 'select * from starship'
That's raw SQL but Doctrine ORM has its own query language called DQL: Doctrine Query Language! It's like SQL, but instead of querying from tables, with DQL you think in terms of querying for the entity objects. Run the same query as above but as DQL:
Writing Manual DQL
Note
Since DoctrineBundle 3.0, the command was renamed to symfony console dbal:run-sql
symfony console doctrine:query:dql 'select s from App\Entity\Starship s'
This looks a bit funky, but it's PHP dumping our Starship objects - and there's three of them, just like the raw query.
Let's leverage this in our homepage controller. Open src/Controller/MainController.php and find the homepage() method. Instead of injecting this StarshipRepository (this is the old one from the Model directory), replace with EntityManagerInterface $em from Doctrine.
| // ... lines 1 - 9 | |
| class MainController extends AbstractController | |
| { | |
| ('/', name: 'app_homepage') | |
| public function homepage( | |
| EntityManagerInterface $em, | |
| ): Response { | |
| // ... lines 16 - 22 | |
| } | |
| } |
EntityManagerInterface
In the last chapter, we saw that Doctrine passes an ObjectManager to the AppFixture::load() method. This EntityManagerInterface is a type of ObjectManager and it's what we'll use to autowire the Doctrine entity manager.
Using createQuery()
Below, write: $ships = $em->createQuery() and pass the DQL string: SELECT s FROM App\Entity\Starship s. Finally, call ->getResult().
| // ... lines 1 - 9 | |
| class MainController extends AbstractController | |
| { | |
| // ... line 12 | |
| public function homepage( | |
| // ... line 14 | |
| ): Response { | |
| $ships = $em->createQuery('SELECT s FROM App\Entity\Starship s')->getResult(); | |
| // ... lines 17 - 22 | |
| } | |
| } |
This executes the query, grabs the data but returns an array of Starship objects instead of the raw data, which is amazing!
Leave the rest of the method as is.
Spin over and refresh the homepage. It's basically the same... that's a good sign! Look closely at the web debug toolbar - there's a new "Doctrine" section. OooooooOooo.
Doctrine Profiler
Click to open the "Doctrine" profiler panel. So cool. This shows all the queries that were executed during the last request. We see just one: that makes sense!
We can see a formatted query that's more readable, a runnable query that we can copy and paste into our favourite SQL tool, an "Explain query" button to see database-specific info about how the query was executed, and a "View query backtrace".
This is my favorite! It shows the call stack that led to this query. Super useful to track down what code triggered the query, in this case, our homepage() method.
Using the QueryBuilder
One bummer is that DQL isn't that pretty! Luckily, Doctrine has a "query builder". This thing is awesome: instead of writing the DQL string manually, we build it with an object. Back in our homepage() method, replace $em->createQuery() with $em->createQueryBuilder(). Off it, chain ->select('s'), then ->from(Starship::class, 's') hitting tab add the use statement from App\Entity. Bonus! We can use Starship::class instead of the string.
Finally, before ->getResult(), call ->getQuery().
| // ... lines 1 - 10 | |
| class MainController extends AbstractController | |
| // ... lines 12 - 13 | |
| public function homepage( | |
| // ... line 15 | |
| ): Response { | |
| $ships = $em->createQueryBuilder() | |
| ->select('s') | |
| ->from(Starship::class, 's') | |
| ->getQuery() | |
| ->getResult(); | |
| // ... lines 22 - 27 | |
| } | |
| } |
Back in the app, refresh the homepage... still works!
We still need to refactor one thing. Click on one of the ships... oh no!
Starship not found.
Ahh, our StarshipController::show() action is still using the old StarshipRepository with the hardcoded data. We need to fix that!
Open src/Controller/StarshipController.php and find the show() method. Since we need to query for data, replace StarshipRepository $repository with EntityManagerInterface $em.
| // ... lines 1 - 10 | |
| class StarshipController extends AbstractController | |
| { | |
| ('/starships/{id<\d+>}', name: 'app_starship_show') | |
| public function show(int $id, EntityManagerInterface $em): Response | |
| { | |
| // ... lines 16 - 23 | |
| } | |
| } |
In this case, the query is so simple, there's a shortcut method.
Using find()
Write $ship = $em->find(Starship::class, $id).
| // ... lines 1 - 10 | |
| class StarshipController extends AbstractController | |
| { | |
| // ... line 13 | |
| public function show(int $id, EntityManagerInterface $em): Response | |
| { | |
| $ship = $em->find(Starship::class, $id); | |
| // ... lines 17 - 23 | |
| } | |
| } |
The first argument of find() is the entity class want to fetch, and the second is the ID. Easy!
Back to the app and... refresh. It works! Look at the web debug toolbar - a single query was run to fetch the starship.
We're done with our old Model/ directory. Well, almost, the StarshipStatusEnum is still needed so move it to Entity/ to keep things organized. PhpStorm will handle all the renaming. Now, delete src\Model and celebrate! I love deleting unused code!
Next up! Let's check out entity repositories as a way to move querying logic out of our controllers.
11 Comments
okay, got it working
this wasn't enough:
sudo apt install php8.3-fpm
sudo apt install php8.3-pgsql
sudo phpenmod -v 8.3 -s fpm pdo_pgsql pgsql
obviously, I had to restart the symfony server, because that is the one running web app
symfony server:stop
symfony server:start -d
Hey @Martin-C
Thanks for sharing and I'm glad you got it working. It's super weird that
findAll()worked but errored withcreateQuery()...it worked with findAll() because it was just a mock returning instances of Starship
new Starship(1, 'USS LeafyCruiser (NCC-0001)', 'Garden', 'Jean-Luc Pickles', StarshipStatusEnum::IN_PROGRESS, new \DateTimeImmutable('+1 day')),
etc...
Oohh, got it!
php -m | grep pdo
pdo_pgsql
pdo_sqlite
hmm, once I replace dummy findAll() method with real
$ships = $em->createQuery('SELECT s FROM App\Entity\Starship s')->getResult();
I get "An exception occurred in the driver: could not find driver"
How is this is supposed to be running? Docker ps returns only db container, but where is the php service configured?
Obviously, if I run CLI
symfony console doctrine:query:dql 'select s from App\Entity\Starship s'
everything's OK.
Is this example suppose to use any PHPStorm internal docker intepreter?
I solved it like this:
Edit php.ini file.
decommand these:
extension=bz2
extension=curl
extension=gd
extension=gettext
extension=mbstring
extension=exif
extension=mysqli
extension=pdo_mysql
extension=pdo_pgsql
extension=pdo_sqlite
extension=pgsql
I have the same problem. I think there was an update for symfony, after these tutorials were published. Did you managed to fix it by any mean?
Hey @Bart-V!
With the Symfony CLI, PHP is expected to be installed locally on your machine. Docker is only used for the "services" your app requires.
pdo_pgsqlis required for theDATABASE_URLwe're using in this course but isn't always installed by default. I looked at my Homebrew PHP installation and it looks like, for me, pgsql was compiled into PHP.If you got it working I guess it was available, just not enabled.
As a total failsafe, you can always switch your
DATABASE_URLto a DB driver you have available (like SQLite). There's nothing in this course that is Postgres specific. We just wanted to show how it works with SymfonyCLI.--Kevin
Got another solution.
With the first one (edit php.ini); everytime I tried the fixtures:load it kept mentioning:
But if you run the next command, the fixtures:load worked perfectly. Without mentioning the line above.
I found this, because of your mentioning the line
for me, pgsql was compiled into PHPThank you, Kevin!!
You're welcome!
Yeah, when PHP is installed via apt, you need to install pgsql with apt.
This conversation has spurred me to write a blog post about installing PHP natively on different operating systems. So, thank you too!
"Houston: no signs of life"
Start the conversation!