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 SubscribeAbre CategoryRepository
. Aquí tenemos unos cuantos lugares en los que ->leftJoin()
nos lleva a fortuneCookies
y seleccionamos galletas de la suerte. En el futuro, puede que necesitemos hacer eso en más métodos aún... así que sería súper estupendo si pudiéramos reutilizar esa lógica en lugar de repetirla una y otra vez. ¡Hagámoslo!
... lines 1 - 17 | |
class CategoryRepository extends ServiceEntityRepository | |
{ | |
... lines 20 - 52 | |
public function findWithFortunesJoin(int $id): ?Category | |
{ | |
return $this->createQueryBuilder('category') | |
->addSelect('fortuneCookie') | |
->leftJoin('category.fortuneCookies', 'fortuneCookie') | |
... lines 58 - 61 | |
} | |
... lines 63 - 105 | |
} |
En cualquier lugar de aquí dentro, añade un nuevo private function
llamadoaddFortuneCookieJoinAndSelect()
. Éste aceptará un objeto QueryBuilder
(asegúrate de que obtienes el de Doctrine\ORM
- el "Object Relational Mapper"), y llamémoslo $qb
. Esto también devolverá un QueryBuilder
.
... lines 1 - 7 | |
use Doctrine\ORM\QueryBuilder; | |
... lines 9 - 18 | |
class CategoryRepository extends ServiceEntityRepository | |
{ | |
... lines 21 - 82 | |
private function addFortuneCookieJoinAndSelect(QueryBuilder $qb): QueryBuilder | |
{ | |
} | |
... lines 87 - 111 | |
} |
El siguiente paso es bastante sencillo. Ve a robar la lógica JOIN
de arriba... y, aquí abajo, di return $qb
... y pégalo... asegurándote de limpiar cualquier desorden de espaciado que se haya podido producir.
... lines 1 - 82 | |
private function addFortuneCookieJoinAndSelect(QueryBuilder $qb): QueryBuilder | |
{ | |
return $qb | |
->addSelect('fortuneCookie') | |
->leftJoin('category.fortuneCookies', 'fortuneCookie'); | |
} | |
... lines 89 - 115 |
Y... ¡listo! Ahora podemos llamar a este método, pasarle el QueryBuilder
, y añadirá el JOIN
y el SELECT
por nosotros.
El resultado es bastante bonito. Aquí arriba, podemos decir$qb = $this->createQueryBuilder('category')
... y abajo,return $this->addFortuneCookieJoinAndSelect()
pasando $qb
.
... lines 1 - 41 | |
public function search(string $term): array | |
{ | |
$qb = $this->createQueryBuilder('category'); | |
return $this->addFortuneCookieJoinAndSelect($qb) | |
... lines 47 - 51 | |
} | |
... lines 53 - 115 |
Creamos el $qb
, se lo pasamos al método, lo modifica... y también nos devuelve el QueryBuilder
, así que podemos encadenarlo como siempre.
Gira y prueba la función "Buscar". Y... oh... ¡claro que se rompe! Tenemos que eliminar este exceso de código. Si lo probamos ahora... ¡gran éxito!
Para celebrarlo, repite lo mismo aquí abajo. Sustituye return
por$qb =
... más abajo, di return $this->addFortuneCookieJoinAndSelect()
pasando por $qb
, y luego elimina ->addSelect()
y ->leftJoin()
.
... lines 1 - 53 | |
public function findWithFortunesJoin(int $id): ?Category | |
{ | |
$qb = $this->createQueryBuilder('category'); | |
return $this->addFortuneCookieJoinAndSelect($qb) | |
->andWhere('category.id = :id') | |
->setParameter('id', $id) | |
->getQuery() | |
->getOneOrNullResult(); | |
} | |
... lines 64 - 115 |
Esto es para la página de Categorías, así que si hacemos clic en cualquier categoría... ¡perfecto! Sigue funcionando.
Pero... ¡podemos hacerlo aún mejor! En lugar de pedir el objeto QueryBuilder
como argumento, hazlo opcional.
Mira: aquí abajo, retoca esto para que si tenemos un $qb
, lo utilicemos, si no,$this->createQueryBuilder('category')
. Así que si se ha pasado un QueryBuilder
, úsalo y llama a ->addSelect()
, si no, crea un nuevo QueryBuilder
y llama a->addSelect()
con él.
... lines 1 - 78 | |
private function addFortuneCookieJoinAndSelect(QueryBuilder $qb = null): QueryBuilder | |
{ | |
return ($qb ?? $this->createQueryBuilder('category')) | |
... lines 82 - 83 | |
} | |
... lines 85 - 111 |
La ventaja es que aquí no necesitamos inicializar en absoluto nuestro QueryBuilder
... y lo mismo ocurre con el método anterior.
... lines 1 - 41 | |
public function search(string $term): array | |
{ | |
return $this->addFortuneCookieJoinAndSelect() | |
... lines 45 - 49 | |
} | |
... line 51 | |
public function findWithFortunesJoin(int $id): ?Category | |
{ | |
return $this->addFortuneCookieJoinAndSelect() | |
... lines 55 - 58 | |
} | |
... lines 60 - 111 |
Pero puedes ver lo importante que es que utilicemos un alias coherente en todas partes. Estamos haciendo referencia a category.name
,category.iconKey
, y category.id
... así que tenemos que asegurarnos de que siempre creamos un QueryBuilder
utilizando ese alias exacto. De lo contrario... las cosas explotarían.
Añadamos un método reutilizable más: private function addOrderByCategoryName()
... porque probablemente querremos ordenar siempre nuestros datos de la misma manera. Dale el argumento habitual QueryBuilder $qb = null
, devuelve un QueryBuilder
, y el interior es bastante sencillo. Robaré el código anterior... déjame darle a "enter" para que se vea un poco mejor... y empezaré de la misma manera. Crearemos un QueryBuilder
si es necesario, y luego diremos ->addOrderBy('category.name')
, seguido deCriteria::DESC
, que hemos utilizado antes en nuestro método search()
. Y sí, estamos ordenando en orden alfabético inverso porque, bueno, sinceramente no tengo ni idea de en qué estaba pensando cuando codifiqué esa parte.
... lines 1 - 85 | |
private function addOrderByCategoryName(QueryBuilder $qb = null): QueryBuilder | |
{ | |
return ($qb ?? $this->createQueryBuilder('category')) | |
->addOrderBy('category.name', Criteria::DESC); | |
} | |
... lines 91 - 117 |
Para utilizar esto, tenemos que separar un poco las cosas. Empieza con$qb = $this->addOrderByCategoryName()
y no le pases nada. Luego pasa ese $qb
a la segunda parte.
... lines 1 - 41 | |
public function search(string $term): array | |
{ | |
$qb = $this->addOrderByCategoryName(); | |
return $this->addFortuneCookieJoinAndSelect($qb) | |
... lines 47 - 50 | |
} | |
... lines 52 - 118 |
En cuanto tengas varios métodos abreviados, no podrás encadenarlos todos... lo cual es un pequeño fastidio. Pero esto nos permite eliminar el->addOrderBy()
de aquí abajo.
Si lo intentamos ahora... ¡la página sigue funcionando! Y si intentamos buscar algo en la página principal... ¡también funciona!
A continuación: vamos a conocer el sistema Criteria
: una forma realmente genial de filtrar eficazmente las relaciones de colección dentro de la base de datos, manteniendo tu código sencillísimo.
Hey @Claudio-B ,
Well, you just need make sure you pass an integer to the constructor of your CategoryFortuneStats
, i.e. instead of null pass 0 for example. To avoid multiple checks in multiple places before new CategoryFortuneStats()
you can make sure your repository method that calculates the number always returns an integer, i.e. do something like this:
if ($result === null) {
return 0;
}
But if you want to do this check in the constructor of CategoryFortuneStats
- just allow passing null
for that fortunesPrinted
argument, i.e.
public function __construct(?int $fortunesPrinted)
{
if ($fortunesPrinted === null) {
$fortunesPrinted = 0;
}
$this->fortunesPrinted = $fortunesPrinted;
}
Something like this, I hope this helps! :)
Cheers!
// 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
}
}
Before aksing, thank you so much for your great job.
I get this error:
if a category is empty (0 fortuneCoockie). How to check the case in CategoryFortuneStats constructor?
Thank you in advance
Claudio