Unir a través de una relación de muchos a muchos
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 Subscribe¿Te has preguntado alguna vez en qué nave de la flota hay más droides? Yo también Hagamos una lista de todas las naves en orden ascendente según su número de droides.
Sumérgete en src/Controller/MainController.php. La consulta es:$ships = $repository->findIncomplete();.
Haz clic en ese método y dale un nombre nuevo y elegante:findIncompleteOrderedByDroidCount():
| // ... lines 1 - 14 | |
| class StarshipRepository extends ServiceEntityRepository | |
| { | |
| // ... lines 17 - 24 | |
| public function findIncompleteOrderedByDroidCount(): Pagerfanta | |
| { | |
| // ... lines 27 - 34 | |
| } | |
| // ... lines 36 - 65 | |
| } |
Cópialo, vuelve al controlador y sustituye el método antiguo por el nuevo:
| // ... lines 1 - 10 | |
| class MainController extends AbstractController | |
| { | |
| ('/', name: 'app_homepage') | |
| public function homepage( | |
| // ... lines 15 - 16 | |
| ): Response { | |
| $ships = $repository->findIncompleteOrderedByDroidCount(); | |
| // ... lines 19 - 27 | |
| } | |
| } |
Aún no hemos cambiado nada, así que una actualización rápida nos da lo mismo.
Para ordenar las naves estelares por su número de droides, tenemos que unir la tabla de unión hasta droid, agrupar porstarship, y luego contar los droides. Guau. En realidad, ¡es bastante bonito!
En StarshipRepository, añade un leftJoin(). Pero no vamos a pensar en la tabla de unión ni en la base de datos. No, céntrate sólo en las relaciones en Doctrine. Así que estamos uniendo a través de s, que es nuestra nave estelar, y droids, la propiedad que tiene la relación ManyToMany con Droid. Por último, aliasamos esos droides como droid.
Para contar los droides, añade un groupBy('s.id').
Para ordenar sustituye el orderBy() existente por orderBy('COUNT(droid)', 'ASC'):
| // ... lines 1 - 14 | |
| class StarshipRepository extends ServiceEntityRepository | |
| { | |
| // ... lines 17 - 24 | |
| public function findIncompleteOrderedByDroidCount(): Pagerfanta | |
| { | |
| $query = $this->createQueryBuilder('s') | |
| // ... line 28 | |
| ->orderBy('COUNT(droid)', 'ASC') | |
| ->leftJoin('s.droids', 'droid') | |
| ->groupBy('s.id') | |
| // ... lines 32 - 33 | |
| ; | |
| // ... lines 35 - 36 | |
| } | |
| // ... lines 38 - 67 | |
| } |
Después, pulsa actualizar y ¡boom! En la parte superior, verás droids none. Pero a medida que te desplazas hacia abajo, el recuento de droides aumenta. Si eres lo suficientemente valiente como para aventurarte unas páginas más adelante, ¡empezaremos a ver naves estelares con dos, tres o incluso cuatro droides!
¿La clave? No hay nada especial en esta unión. Nos unimos a través de la propiedad y Doctrine se encarga del resto.
Si echas un vistazo a la consulta en esta página, verás que se encarga de todos los detalles. Busca starship_droid para encontrar la consulta. Esto es feo, pero si formateas la consulta, selecciona de starship, encargándose de la unión a la tabla de unión y uniéndose de nuevo a droid. Eso nos permite contar y ordenar por ese recuento en esa tabla droid. ¡Impresionante Doctrine, impresionante!
¡Eso es técnicamente todo para ManyToMany! Pero a continuación vamos a tratar un caso de uso más avanzado, pero aún común: añadir datos a la tabla join, como la fecha en que el droide se unió a la nave estelar.
11 Comments
Hi,
I also only got a single row with the groupBy, I had to add COUNT(droid) AS HIDDEN droidCount in the select.
Hey @Cedric
I just gave it a try, and the query works well. I'm confused why adding a select changes the number of fetched rows. Did you download the course code from this page or are you coding it from scratch?
Cheers!
Hi @MolloKhan,
Thanks for your quick reply. I just tried again by mounting the Docker container with the Postgres database, and everything works.
I was using a local MariaDB database during the course.
Ohh that makes sense. I'm glad you found a solution. Cheers!
Hello! Loving this course. When I use a mysql database I get this error after updating the findIncompleteOrderedByDroidCount() function
An exception has been thrown during the rendering of a template ("An exception occurred while executing a query: SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #10 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'starshop2.s1_.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by").Tried a few things to fix it but I'm coming up blank. Thanks!Hey @Logan-M
We're glad to hear you're liking our content. Can I see your query/code?
Cheers!
Here is my code below. The only difference I notice is the use statement for starshipStatusEnum (Mine is in a different location). I am using a MySQL database not the PostgreSQL.
`<?php
namespace App\Repository;
use App\Entity\Starship;
use App\Model\StarshipStatusEnum;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
/**
*/
class StarshipRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
}
/**
*/
public function findIncompleteOrderedByDroidCount(): Pagerfanta
{
$query = $this->createQueryBuilder('s')
;
return new Pagerfanta(new QueryAdapter($query));
}
public function findMyShip(): Starship
{
return $this->findAll()[0];
}
// /**
// @return Starship[] Returns an array of Starship objects // /
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('s')
// ->andWhere('s.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('s.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Starship
// {
// return $this->createQueryBuilder('s')
// ->andWhere('s.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
`
The raw query looks like this:
SELECT s0_.id AS id_0, s0_.name AS name_1, s0_.captain AS captain_2, s0_.class AS class_3, s0_.status AS status_4, s0_.arrived_at AS arrived_at_5, s0_.slug AS slug_6, s0_.created_at AS created_at_7, s0_.updated_at AS updated_at_8 FROM starship s0_ LEFT JOIN starship_droid s2_ ON s0_.id = s2_.starship_id LEFT JOIN droid d1_ ON d1_.id = s2_.droid_id WHERE s0_.status <> ? GROUP BY s0_.id ORDER BY COUNT(d1_.id) ASCThanks for taking the time to look at this.
Hey @Logan-M!
Ah, in recent versions of MySQL, GROUP BY is more strict than Postgres. Basically, you can't do dynamic operations like we're doing in
->orderBy()- it needs to be a column.Take a look at this comment above for, I think, the solution - this should work in MySQL and Postgres.
I hope this helps!
Kevin
Thanks! That makes sense.
In the last step, PagerFanta doesn't work. I only see one ship and 14 pages.
Hey @giorgiocba
I just gave this a try (with the new code) and it works fine. Could you copy-paste the AppFixtures code from this code block and try again?
https://symfonycasts.com/screencast/doctrine-relations/many-to-many-foundry#codeblock-3d67ea2ef7
Cheers!
"Houston: no signs of life"
Start the conversation!