gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Tenemos este genial método ->andWhere()
que busca en las propiedades name
oiconKey
de la entidad Category
. Pero, ¿podríamos buscar también en los datos de las galletas de la suerte dentro de cada categoría? ¡Claro que sí!
Veamos cómo se establece esa relación. En Category
, tenemos una relación OneToMany
sobre una propiedad llamada $fortuneCookies
sobre la entidad FortuneCookie
.
Si pensamos en el problema desde la perspectiva de la base de datos, para actualizar nuestra cláusulaWHERE
para incluir WHERE fortune_cookie.fortune = :searchTerm
, primero necesitamos JOIN
a la tabla fortune_cookie
.
Y eso es lo que vamos a hacer en Doctrine... excepto con un giro. En lugar de pensar en unir tablas, vamos a pensar en unir clases de entidades. Esto puede parecer raro al principio, pero es genial. En este caso, queremos JOIN
a través de esta propiedad fortuneCookies
a la entidadFortuneCookie
.
¡Vamos a hacerlo! Volviendo a CategoryRepository
... podemos añadir la unión en cualquier parte de la consulta. A diferencia de SQL, al QueryBuilder no le importa el orden en que hagas las cosas. Añade ->leftJoin()
porque estamos uniendo desde una categoría a muchas galletas de la suerte. Pasa esto a category.fortuneCookies
y luego a fortuneCookie
, que será el alias de la entidad unida.
... lines 1 - 17 | |
class CategoryRepository extends ServiceEntityRepository | |
{ | |
... lines 20 - 40 | |
public function search(string $term): array | |
{ | |
return $this->createQueryBuilder('category') | |
->leftJoin('category.fortuneCookies', 'fortuneCookie') | |
... lines 45 - 49 | |
} | |
... lines 51 - 93 | |
} |
Cuando decimos category.fortuneCookies
, nos referimos a la propiedad fortuneCookies
. Lo bueno es que... ¡esto es todo lo que necesitamos! No necesitamos decirle a Doctrine a qué entidad o tabla nos estamos uniendo... y no necesitamos elON fortune_cookie.category_id = category.id
que veríamos normalmente en SQL. No necesitamos nada de esto porque Doctrine ya tiene esa información en el mapeoOneToMany
. Sólo decimos "unir a través de esta propiedad" y ¡él hace el resto!
Una cosa a tener en cuenta, de la que hablaremos más adelante, es que, al unirnos a algo, no estamos seleccionando más datos. Sólo estamos haciendo que las propiedades de FortuneCookie
estén disponibles dentro de nuestra consulta. Esto significa que podemos hacer que ->andWhere()
sea aún más largo. Añade OR fortuneCookie
(utilizando el nuevo alias de la unión) .fortune
(porque fortune
es el nombre de la propiedad de FortuneCookie
que almacena el texto) LIKE :searchTerm
.
... lines 1 - 40 | |
public function search(string $term): array | |
{ | |
return $this->createQueryBuilder('category') | |
... line 44 | |
->andWhere('category.name LIKE :searchTerm OR category.iconKey LIKE :searchTerm OR fortuneCookie.fortune LIKE :searchTerm') | |
... lines 46 - 49 | |
} | |
... lines 51 - 95 |
¡Listo! Vuelve al sitio. Una de mis fortunas tiene la palabra "conclusión". Gira a la página de inicio, busca "conclusión" y... ¡lo tienes! ¡Parece que tenemos al menos una coincidencia en nuestra categoría "Proverbios"! ¡Falta la conclusión!
Pero si haces clic en el icono de la base de datos de la barra de herramientas de depuración web... esta página tiene dos consultas. La primera es para la categoría - tiene FROM category
e incluye el LEFT JOIN
que acabamos de añadir. La segunda es FROM fortune_cookie
.
Y si vamos a la página principal sin buscar, hay siete consultas en total: una para obtener todas las categorías... y luego otras 6 para encontrar las galletas de la suerte de cada una de las seis categorías. Esto se denomina el problema de la consulta N+1. Vamos a hablar de él a continuación y a solucionarlo con uniones.
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"beberlei/doctrineextensions": "^1.3", // v1.3.0
"doctrine/doctrine-bundle": "^2.7", // 2.9.1
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.13", // 2.15.1
"symfony/asset": "6.2.*", // v6.2.7
"symfony/console": "6.2.*", // v6.2.10
"symfony/dotenv": "6.2.*", // v6.2.8
"symfony/flex": "^2", // v2.2.5
"symfony/framework-bundle": "6.2.*", // v6.2.10
"symfony/proxy-manager-bridge": "6.2.*", // v6.2.7
"symfony/runtime": "6.2.*", // v6.2.8
"symfony/twig-bundle": "6.2.*", // v6.2.7
"symfony/webpack-encore-bundle": "^1.16", // v1.16.1
"symfony/yaml": "6.2.*" // v6.2.10
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
"symfony/maker-bundle": "^1.47", // v1.48.0
"symfony/stopwatch": "6.2.*", // v6.2.7
"symfony/web-profiler-bundle": "6.2.*", // v6.2.10
"zenstruck/foundry": "^1.22" // v1.32.0
}
}