Establecer relaciones 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 SubscribeMuy bien, vamos a sumergirnos en la parte final de ManyToMany
. En una esquina, tenemos la entidad Starship
, que está vinculada mediante una relaciónManyToMany
con la entidad Droid
. Esta relación nos proporciona una tabla adicional llamada "tabla de unión" que lleva la cuenta de los droides que se han subido a cada nave estelar. Pero, ¿cómo asignamos un Droid
a un Starship
? Salta a AppFixtures
.
Añadir algunos droides
En primer lugar, vamos a añadir algunos droides a la mezcla. Añadiré código que construya tres droides. Importa la clase con una opción rápida o Alt
+ Enter
:
// ... lines 1 - 4 | |
use App\Entity\Droid; | |
// ... lines 6 - 11 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager): void | |
{ | |
// ... lines 16 - 22 | |
$droid1 = new Droid(); | |
$droid1->setName('IHOP-123'); | |
$droid1->setPrimaryFunction('Pancake chef'); | |
$manager->persist($droid1); | |
$droid2 = new Droid(); | |
$droid2->setName('D-3P0'); | |
$droid2->setPrimaryFunction('C-3PO\'s voice coach'); | |
$manager->persist($droid2); | |
$droid3 = new Droid(); | |
$droid3->setName('BONK-5000'); | |
$droid3->setPrimaryFunction('Comedy sidekick'); | |
$manager->persist($droid3); | |
$manager->flush(); | |
// ... lines 38 - 63 | |
} | |
} |
Y... ¡ya tenemos droides! Nada del otro mundo: crear un nuevoDroid
, establecer las propiedades necesarias, persistir y vaciar.
Asignación de droides a naves estelares
Ahora pasemos a la parte divertida: asignar un Droid
a un Starship
. Crea una variable Starship
y prepárate para la magia:
// ... lines 1 - 11 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager): void | |
{ | |
$starship = StarshipFactory::createOne([ | |
// ... lines 17 - 21 | |
]); | |
// ... lines 23 - 63 | |
} | |
} |
La forma de relacionar estas dos entidades es sorprendentemente sencilla, y te parecerá un déjà vu de nuestra relación OneToMany
. ¡Apuesto a que incluso puedes adivinarlo!
Antes del flush()
es: $starship->addDroid($droid1)
. Haz lo mismo con los otros dos droides: $starship->addDroid($droid2)
y$starship->addDroid($droid3)
:
// ... lines 1 - 11 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager): void | |
{ | |
$starship = StarshipFactory::createOne([ | |
// ... lines 17 - 21 | |
]); | |
// ... lines 23 - 25 | |
$starship->addDroid($droid1); | |
$manager->persist($droid1); | |
// ... lines 28 - 31 | |
$starship->addDroid($droid2); | |
$manager->persist($droid2); | |
// ... lines 34 - 37 | |
$starship->addDroid($droid3); | |
$manager->persist($droid3); | |
$manager->flush(); | |
// ... lines 41 - 66 | |
} | |
} |
La tripulación está lista para sus tortitas hechas con droides, ¡así que probemos esto!
symfony console doctrine:fixtures:load
Sin errores. Para ver si realmente funciona, ejecuta:
symfony console doctrine:query:sql 'SELECT * FROM droid'
Como esperábamos: tres filas, una por cada droide que hemos creado. Ahora, echa un vistazo a la tabla de unión, starship_droid
.
symfony console doctrine:query:sql 'SELECT * FROM starship_droid'
¡Guau! Tres filas, una por cada asignación de droide a nave.
La magia de Doctrine
La verdadera magia es que, con Doctrine, sólo tenemos que preocuparnos de relacionar un objeto Droid
con un objeto Starship
. Luego, se encarga del resto, gestionando la inserción y eliminación de filas en la tabla de unión.
Después de la descarga, sabemos que tenemos tres filas en la tabla de unión. Ahora, tras la descarga, elimina una asignación:$starship->removeDroid($droid1)
:
// ... lines 1 - 11 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager): void | |
{ | |
// ... lines 16 - 39 | |
$manager->flush(); | |
$starship->removeDroid($droid1); | |
$manager->flush(); | |
// ... lines 44 - 69 | |
} | |
} |
Recarga los accesorios y comprueba la tabla de unión.
symfony console doctrine:query:sql 'SELECT * FROM droid'
¡Sólo quedan dos filas! Doctrine ha eliminado la fila de nuestro droide eliminado.
Caras Propias vs Inversas
Un toque final en ManyToMany
: ¿recuerdas cuando hablamos de lados propios e inversos de una relación? Como vimos, nuestros métodos sincronizan el otro lado de la relación, añadiendo el Droid
al Starship
cuando llamamos a addDroid()
:
// ... lines 1 - 15 | |
class Starship | |
{ | |
// ... lines 18 - 207 | |
public function addDroid(Droid $droid): static | |
{ | |
if (!$this->droids->contains($droid)) { | |
$this->droids->add($droid); | |
} | |
return $this; | |
} | |
// ... lines 216 - 222 | |
} |
Así que el lado propio no importa mucho.
Pero, ¿cuál es el lado propietario? En un ManyToMany
, cualquiera de los dos lados podría ser el lado propietario.
Para averiguar quién es el propietario, mira la opción inversedBy
. Dice ManyToMany
y inversedBy: starships
, lo que significa que la propiedad Droid.starships
es el lado inverso.
Ahora bien, esto es casi trivial, pero si eres un maniático del control y quieres dictar el nombre de la tabla de unión, puedes añadir un atributo JoinTable
. Pero recuerda que tiene que ir en el lado propietario. Aparte de eso, no te preocupes: no es gran cosa.
A continuación, vamos a utilizar la nueva relación para representar los droides asignados a cada nave.
I had a problem with starship_droid table, after loading fixtures there is only one row - the last droid.
I add
_real()
and it works correctly