Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Reutilizar consultas en el Generador de consultas

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.

Start your All-Access Pass
Buy just this tutorial for $6.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Abre 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.

Hacer que el argumento QueryBuilder sea opcional

Pero... ¡podemos hacerlo aún mejor! En lugar de pedir el objeto QueryBuildercomo 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 QueryBuildersi 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 $qba 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.

Leave a comment!

4
Login or Register to join the conversation
Claudio-B Avatar
Claudio-B Avatar Claudio-B | posted hace 1 mes | edited

Before aksing, thank you so much for your great job.

I get this error:

App\Model\CategoryFortuneStats::__construct(): Argument #1 ($fortunesPrinted) must be of type int, null given, called in C:\wamp64\www\mixed_vinyl\src\Repository\FortuneCookieRepository.php on line .."

if a category is empty (0 fortuneCoockie). How to check the case in CategoryFortuneStats constructor?

Thank you in advance
Claudio

Reply

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!

Reply
Claudio-B Avatar
Claudio-B Avatar Claudio-B | Victor | posted hace 1 mes

Very kind of you!
Ciao!

Reply

Glad it helped! :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice