Relación Muchos-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, tenemos una entidad Starship
y una entidad Droid
configuradas y listas para mezclarse. ¿Cómo conseguimos que estas dos entidades se conecten?
Imagínatelo así: Cada Starship
va a necesitar un equipo de Droids
para que las cosas funcionen sin problemas... y para el alivio cómico ocasional. Cada Droid
, a su vez, debería poder servir a muchos Starships
. Olvídate de la base de datos y céntrate en los objetos. Nuestra entidad Starship
necesita una propiedad droids
que contenga una colección de todos los Droid
s que tiene asignados.
¡Genial! Vuelve a tu terminal y ejecuta:
symfony console make:entity
Actualiza Starship
y añade una propiedad droids
. Utiliza "relación" para entrar en el práctico asistente. Esta vez, necesitamos una relación ManyToMany
:
Cada
Starship
puede tener muchosDroids
, y cadaDroid
puede servir en muchosStarships
. ¡Suena perfecto!
A continuación, nos pregunta si queremos mapear el lado inverso de la relación. Esto es preguntarnos si queremos dar a nuestro Droids
la capacidad de listar todos los Starships
a los que está conectado: $droid->getShips()
eso suena útil. Así que digamos "sí". Para el nuevo nombre de campo dentro de Droid
, starships
servirá perfectamente.
Observa que ha actualizado tanto Starship
como Droid
. Echa un vistazo a los cambios en cada uno.
La magia "Muchos a muchos
En Starship
, ahora tenemos una nueva propiedad droids
, que es unaManyToMany
. También ha inicializado droids
en ArrayCollection
y ha añadido los métodos getDroids()
, addDroid()
y removeDroid()
:
// ... lines 1 - 15 | |
class Starship | |
{ | |
// ... lines 18 - 50 | |
/** | |
* @var Collection<int, Droid> | |
*/ | |
#[ORM\ManyToMany(targetEntity: Droid::class, inversedBy: 'starships')] | |
private Collection $droids; | |
public function __construct() | |
{ | |
// ... line 59 | |
$this->droids = new ArrayCollection(); | |
} | |
// ... lines 62 - 199 | |
/** | |
* @return Collection<int, Droid> | |
*/ | |
public function getDroids(): Collection | |
{ | |
return $this->droids; | |
} | |
public function addDroid(Droid $droid): static | |
{ | |
if (!$this->droids->contains($droid)) { | |
$this->droids->add($droid); | |
} | |
return $this; | |
} | |
public function removeDroid(Droid $droid): static | |
{ | |
$this->droids->removeElement($droid); | |
return $this; | |
} | |
} |
Si estás pensando que esto se parece mucho a una relación OneToMany
, ¡ding, ding! ¡Pídete una pizza! ¡Porque lo es!
En Droid
, la historia es parecida. Tenemos una propiedad starships
, que es una ManyToMany
, y se inicializa en el constructor. Luego tenemos las mismasgetStarships()
, addStarship()
, y removeStarship()
:
// ... lines 1 - 10 | |
class Droid | |
{ | |
// ... lines 13 - 23 | |
/** | |
* @var Collection<int, Starship> | |
*/ | |
#[ORM\ManyToMany(targetEntity: Starship::class, mappedBy: 'droids')] | |
private Collection $starships; | |
public function __construct() | |
{ | |
$this->starships = new ArrayCollection(); | |
} | |
// ... lines 34 - 63 | |
/** | |
* @return Collection<int, Starship> | |
*/ | |
public function getStarships(): Collection | |
{ | |
return $this->starships; | |
} | |
public function addStarship(Starship $starship): static | |
{ | |
if (!$this->starships->contains($starship)) { | |
$this->starships->add($starship); | |
$starship->addDroid($this); | |
} | |
return $this; | |
} | |
public function removeStarship(Starship $starship): static | |
{ | |
if ($this->starships->removeElement($starship)) { | |
$starship->removeDroid($this); | |
} | |
return $this; | |
} | |
} |
Genera la migración para esto. Vuelve al terminal y ejecuta:
symfony console make:migration
Desvelar la tabla de unión
¡Maravilloso! Echa un vistazo a lo que ha generado: es fascinante. ¡Tenemos una nueva tabla llamada starship_droid
! Incluye una clave ajena starship_id
parastarship
y una clave ajena droid_id
para droid
:
// ... lines 1 - 12 | |
final class Version20250311014256 extends AbstractMigration | |
{ | |
// ... lines 15 - 19 | |
public function up(Schema $schema): void | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->addSql('CREATE TABLE starship_droid (starship_id INT NOT NULL, droid_id INT NOT NULL, PRIMARY KEY(starship_id, droid_id))'); | |
$this->addSql('CREATE INDEX IDX_1C7FBE889B24DF5 ON starship_droid (starship_id)'); | |
$this->addSql('CREATE INDEX IDX_1C7FBE88AB064EF ON starship_droid (droid_id)'); | |
$this->addSql('ALTER TABLE starship_droid ADD CONSTRAINT FK_1C7FBE889B24DF5 FOREIGN KEY (starship_id) REFERENCES starship (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | |
$this->addSql('ALTER TABLE starship_droid ADD CONSTRAINT FK_1C7FBE88AB064EF FOREIGN KEY (droid_id) REFERENCES droid (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); | |
} | |
// ... lines 29 - 36 | |
} |
Así es como se estructura una relación ManyToMany
en la base de datos: con una tabla join. La verdadera magia de Doctrine es que sólo tenemos que pensar en objetos. Un objeto Starship
tiene muchos objetos Droid
, y un objeto Droid
tiene muchos objetos Starship
. Doctrine se encarga de los tediosos detalles de guardar esa relación en la base de datos.
Antes de continuar, ejecuta esa migración. Vuelve al terminal y hazlo:
symfony console doctrine:migrations:migrate
¡Genial! Ya tenemos una nueva y reluciente tabla de unión. Vale... ¿pero cómo relacionamos los objetos Droid
con los objetos Starship
? Eso a continuación... ¡y te va a encantar!