Doctrina DQL
¡Hola amigos! Gracias por acompañarme en este tutorial, que trata sobre los entresijos de la ejecución de consultas en Doctrine. Parece sencillo... y lo es durante un tiempo. Pero entonces empiezas a añadir uniones, agrupaciones, a tomar sólo datos específicos en lugar de objetos completos, recuentos... y... bueno... ¡se pone interesante! Este tutorial trata de profundizar en todas esas cosas buenas, incluida la ejecución de consultas SQL nativas, el lenguaje de consulta Doctrine, el filtrado de colecciones, la solución del problema "N + 1" y mucho más.
Estoy entusiasmado. Así que ¡manos a la obra!
Configuración del proyecto
Para INSERTAR la mayor cantidad de conocimientos de consulta en tu cerebro, te recomiendo encarecidamente que codifiques conmigo. Puedes descargar el código del curso desde esta página. Después de descomprimirlo, tendrás un directorio start/ con el mismo código que ves aquí. También hay un ingenioso archivoREADME.md con todas las instrucciones de configuración. El último paso será ir a tu terminal, entrar en el proyecto y ejecutar
symfony serve -d
para iniciar un servidor web integrado en https://127.0.0.1:8000. Haré trampas, haré clic en eso, y... di "hola" a nuestra última iniciativa: Consultas de Fortuna. Verás, tenemos un negocio paralelo de distribución multinacional de galletas de la fortuna... y esta elegante aplicación nos ayuda a hacer un seguimiento de todas las fortunas que hemos concedido a nuestros clientes.
Son exactamente 2 páginas: éstas son las categorías, y puedes hacer clic en una para ver su fortuna... incluyendo cuántas se han impreso. Se trata de un proyecto Symfony 6.2, y en este punto, no podría ser más sencillo. Tenemos una entidad Category, una entidad FortuneCookie, exactamente un controlador y ninguna consulta extravagante.
Nota al margen: este proyecto utiliza MySQL... pero casi todo de lo que vamos a hablar funcionará con Postgres o cualquier otra cosa.
Crear nuestro primer método de repositorio personalizado
Hablando de ese único controlador, aquí en la página de inicio, puedes ver que estamos autocableando CategoryRepository y utilizando la forma más sencilla de consultar algo en Doctrine: findAll().
| // ... lines 1 - 5 | |
| use App\Repository\CategoryRepository; | |
| // ... lines 7 - 10 | |
| class FortuneController extends AbstractController | |
| { | |
| ('/', name: 'app_homepage') | |
| public function index(CategoryRepository $categoryRepository): Response | |
| { | |
| $categories = $categoryRepository->findAll(); | |
| return $this->render('fortune/homepage.html.twig',[ | |
| 'categories' => $categories | |
| ]); | |
| } | |
| // ... lines 22 - 29 | |
| } |
Nuestro primer truco será super sencillo, pero interesante. Quiero reordenar estas categorías alfabéticamente por nombre. Una forma sencilla de hacerlo es cambiando findAll() por findBy(). Esto se utiliza normalmente para encontrar elementos DONDE coinciden con un criterio - algo como ['name' => 'foo'].
Pero... también puedes dejarlo vacío y aprovechar el segundo argumento: una matriz de orden por. Así que podríamos decir algo como ['name' => 'DESC'].
Pero... cuando necesito una consulta personalizada, me gusta crear métodos de repositorio personalizados para centralizarlo todo. Dirígete al directorio src/Repository/ y abreCategoryRepository.php. Dentro, podemos añadir los métodos que queramos. Vamos a crear uno nuevo llamado public function findAllOrdered(). Éste devolverá un array... e incluso anunciaré que se trata de un array de objetos Category.
| // ... lines 1 - 4 | |
| use App\Entity\Category; | |
| // ... lines 6 - 16 | |
| class CategoryRepository extends ServiceEntityRepository | |
| { | |
| // ... lines 19 - 23 | |
| /** | |
| * @return Category[] | |
| */ | |
| public function findAllOrdered(): array | |
| { | |
| } | |
| // ... lines 31 - 73 | |
| } |
Antes de rellenar esto, aquí atrás... llámalo: ->findAllOrdered().
| // ... lines 1 - 10 | |
| class FortuneController extends AbstractController | |
| { | |
| // ... line 13 | |
| public function index(CategoryRepository $categoryRepository): Response | |
| { | |
| $categories = $categoryRepository->findAllOrdered(); | |
| // ... lines 17 - 20 | |
| } | |
| // ... lines 22 - 29 | |
| } |
¡Encantado!
Hola DQL (Lenguaje de consulta Doctrine)
Si has trabajado antes con Doctrine, probablemente esperas que utilice el Constructor de consultas. Hablaremos de ello dentro de un momento. Pero quiero empezar de forma aún más sencilla. Doctrine trabaja con muchos sistemas de bases de datos, como MySQL, Postgres, MSSQL, etc. Cada uno de ellos tiene un lenguaje SQL, pero no todos son iguales. Así que Doctrine tuvo que inventar su propio lenguaje similar a SQL llamado "DQL", o "Doctrine Query Language". ¡Es divertido! Se parece mucho a SQL. La mayor diferencia es probablemente que nos referimos a clases y propiedades en lugar de a tablas y columnas.
Escribamos una consulta DQL a mano. Digamos que $dql es igual aSELECT category FROM App\Entity\Category as category. Estamos asociando la claseApp\Entity\Category a la cadena category de la misma forma que asociaríamos el nombre de una tabla a algo en SQL. Y aquí, con sólo seleccionar category, estamos seleccionando todo, lo que significa que devolverá objetos Category.
Y ya está Para ejecutarlo, crea un objeto Query con$query = $this->getEntityManager()->createQuery($dql);. Luego ejecútalo conreturn $query->getResult().
| // ... lines 1 - 16 | |
| class CategoryRepository extends ServiceEntityRepository | |
| { | |
| // ... lines 19 - 26 | |
| public function findAllOrdered(): array | |
| { | |
| $dql = 'SELECT category FROM App\Entity\Category as category'; | |
| $query = $this->getEntityManager()->createQuery($dql); | |
| return $query->getResult(); | |
| } | |
| // ... lines 34 - 76 | |
| } |
También hay un $query->execute(), y aunque realmente no importa, yo prefierogetResult().
Cuando vayamos a probarlo... ¡no cambia nada! ¡Funciona! ¡Acabamos de utilizar DQL directamente para hacer esa consulta!
Añadiendo el DQL ORDER BY
Entonces... ¿qué aspecto tiene añadir el ORDER BY? ¡Probablemente puedas adivinar cómo empieza ORDER BY!
Lo interesante es que, para ordenar por name, no vamos a hacer referencia a la columna name de la base de datos. No, nuestra entidad Category tiene una propiedad $name, y es a ella a la que nos vamos a referir. Probablemente la columna también se llame name... pero podría llamarse unnecessarily_long_column_namey seguiríamos ordenando por la propiedad name.
La cuestión es que, como tenemos una propiedad $name, aquí podemos decirORDER BY category.name.
Ah, y en SQL, utilizar el alias es opcional: puedes decir ORDER BY name. Pero en DQL, es obligatorio, así que debemos decir category.name. Por último, añade DESC.
| // ... lines 1 - 26 | |
| public function findAllOrdered(): array | |
| { | |
| // ... line 29 | |
| $query = $this->getEntityManager()->createQuery($dql); | |
| dd($query->getSQL()); | |
| // ... lines 32 - 33 | |
| } | |
| // ... lines 35 - 79 |
Si ahora recargamos la página... ¡está ordenada alfabéticamente!
La transformación DQL -> SQL
Cuando escribimos DQL, entre bastidores, Doctrine lo convierte en SQL y luego lo ejecuta. Busca qué sistema de base de datos estamos utilizando y lo traduce al lenguaje SQL de ese sistema. Podemos ver el SQL con dd() (por "volcar y morir") $query->getSQL().
| // ... lines 1 - 26 | |
| public function findAllOrdered(): array | |
| { | |
| // ... line 29 | |
| $query = $this->getEntityManager()->createQuery($dql); | |
| dd($query->getSQL()); | |
| // ... lines 32 - 33 | |
| } | |
| // ... lines 35 - 79 |
Y... ¡ahí está! ¡Esa es la consulta SQL real que se está ejecutando! Tiene este feo aliasc0_, pero es lo que esperamos: coge todas las columnas de esa tabla y las devuelve. ¡Es genial!
Por cierto, también puedes ver la consulta dentro de nuestro perfilador. Si quitamos esa depuración y refrescamos... aquí abajo, podemos ver que estamos haciendo siete consultas. Hablaremos de por qué hay siete dentro de un rato. Pero si hacemos clic en ese pequeño icono... ¡bum! ¡Ahí está la primera consulta! También puedes ver una versión bonita de la misma, así como una versión que puedes ejecutar. Si tienes alguna variable dentro de las cláusulas WHERE, la versión ejecutable las rellenará por ti.
Siguiente: Normalmente no escribimos DQL a mano. En lugar de eso, lo construimos con el Generador de consultas. Veamos qué aspecto tiene.
25 Comments
If you're on the Symfony 6 track and feel like you're missing a course between "Doctrine, Symfony 6 & the Database" and this one, you are. It's this one: https://symfonycasts.com/screencast/doctrine-relations
Hey Ryan-L,
Yeah, unfortunately that course is not re-written with the latest Symfony 6 yet so that's why it's missing in the Symfony 6 track. Thanks for this reference, might be useful indeed :)
Cheers!
I believe Rufnex is referring to the project's folder and files icons. I was having the same question.
But I found it's the Atom Material Icons plugin, which can be installed via the Marketplace.
yeah, you found a correct package =)
Hey there! forget about my stupid question earlier about the database. OF COURSE should i run a make:migration and then migrate
duh!
sorry sorry sorry - do not publish my post please ;)
Hey Pieter,
No problem! It's still might be useful for some users ;) But I'm happy to hear you were able to figure out the steps yourself. If you ever have any problems following our tutorials - feel free to leave a comment and our support team will help you :)
Cheers!
Hi,
I dived right into this course, downloaded the code, downgraded to php 8.1, did the composer install and think i have everything rolling but then... the database! Where can I find an SQL dump with the tables/data from this course at start?
Hey Pieter,
When you download and unzip the course code, you will find the README file inside. Follow the instructions there to start the project locally :) Specifically for this course, we do recommend to use Docker for the DB:
Read the README for more details. But if you don't want to use Docker - you can run those commands directly in your CLI if you have MySQL installed, just make sure you update the DB credentials in your
.env.local. :)But if you still have any problems starting the project - let us know!
Cheers!
I downloaded the course code and follower the instructions in the README, but my site looks like this screenshot right now. What do I need to do to fix it?
Here is the link to my GitHub repository, if that's useful.
Hey Alex,
Seems you have problems with assets. Did you have
composer installcommand finished without any errors?Also, could you try to run the next commands:
Do you see any errors in the output or did they finish OK? Please, reload your website again, does it help?
Also, do you use
symfony servecommand to spin your website or did you configure a real web server like Apache/Nginx? Could you try to run the website viasymfony serve?Also, you can open Chrome dev tools and in the Network tab watch for the errors regarding the assets, it may contain some hints.
Cheers!
I downloaded course files, but there are no migrations.
@Victor @MolloKhan Guys, can you fix that?
Hey @Jared
Could you tell me what migrations are missing? I don't see any migrations being generated in this tutorial. You only need to create the database and then run
bin/console doctrine:schema:update- check out the README fileLet me know if I'm missing something. Cheers!
Thank you so much, @MolloKhan ! I fixed it.
For now I encountered a new problem: my assets won't show up. I ran
bin/console assets:install, and thebuilddirectory has appeared inpublic, but files likeapp.css, app.jsreturn a404error.Yea, this course uses Webpack instead of Asset Mapper. You need to run
yarn install(ornpm install) and thenyarn encore devCheers!
Doesn't work :( Still got messages in the chrome console:
GET http://localhost/build/app.css net::ERR_ABORTED 404 (Not Found)I run the following commands:
yarn installandyarn encore devA
builddirectory has appeared, files are present in thepublicdirectory, but I'm still getting404errors.I got it. The issue was with the Docker containers: NGINX and PHP-FPM. NGINX uses
fastcgi_passto redirect PHP files to PHP-FPM, but the assets from the build directory are not present in the NGINX container.Thank you @MolloKhan , problem is solved!
Interesting that configs for nginx and php-fpm work like a charm for Symfony 7, but not for Symfony 6.2.
I mean assets, because I had no problems in version 7.
Ha! That's interesting. I suppose they improved Docker integration in Symfony 7
Cheers!
Don't get the project started on Mac with Docker.
"composer install" and "docker compose up -d" finished without errors, but "symfony console doctrine:database:create --if-not-exists" throws:
[critical] Error thrown while running command "doctrine:database:create --if-not-exists". Message: "An exception occurred in the driver: SQLSTATE[HY000] [2002] Connection refused"
I get the exact same error message was this resolved?
Hey Iqpal,
I bet it was, it's a connection problem which means you most probably have invalid credentials configured. Have you followed my advice in the comment below? https://symfonycasts.com/screencast/doctrine-queries/dql#comment-31434
Cheers!
Hey Thilo,
First of all, make sure you install all the dependencies, i.e. Composer install should finish successfully. The error about the DB sounds like DB server is down. Probably try to restart the Docker completely, run the
docker compose upagain and make sure no errors. Let us know if you still have the same DB errorCheers!
Hey, side question ;o) What phpstrom UI do you use? Greetings.
Hey Rufnex,
Are you talking about PhpStorm theme? If so, we use Darcula in our videos :)
Cheers!
"Houston: no signs of life"
Start the conversation!