Relaciones MUCHOS a muchos Inversas o Invertidos en Laravel

Video thumbnail

Las relaciones muchos a muchos son un tipo de relación disponible en las bases de datos relacionales, donde múltiples registros de una tabla se relacionan con múltiples registros de otra, y que, usualmente en algún punto paginamos registros en Laravel. Por ejemplo, si tenemos una relación entre Post y Etiquetas, un Post puede tener muchas Etiquetas y una Etiqueta puede estar asignada a múltiples Posts.

Esto significa que un registro puede estar asociado con muchos otros registros. Para guardar este tipo de relación se utiliza una tabla pivote, la cual almacena al menos las claves primarias (PKs) de ambas entidades.

En este tipo de relación podemos obtener las etiquetas de un post, que sería la relación directa, o podemos obtener los posts asociados a una etiqueta, que sería la relación inversa.

Las relaciones muchos a muchos en Laravel son una de las más potentes (y también de las más confusas) cuando empiezas a trabajar con Eloquent en proyectos reales. Sobre el papel parecen simples, pero en cuanto necesitas acceder a la relación inversa, filtrar resultados o usar tablas pivote más complejas, empiezan los problemas.

En esta guía te explico cómo funcionan realmente, cuándo usar cada tipo y qué errores evitar, basándome en casos reales como el uso de tags, posts y cursos, no en ejemplos académicos simplificados.

¿Qué es una relación muchos a muchos en Laravel?

Una relación muchos a muchos (many to many) se da cuando un registro de una tabla puede estar relacionado con muchos registros de otra tabla, y viceversa.

Ejemplos clásicos:

  • Un post tiene muchas etiquetas y una etiqueta pertenece a muchos posts.
  • Un usuario tiene varios roles y un rol puede asignarse a muchos usuarios.
  • Un estudiante puede estar en varios cursos y un curso tener varios estudiantes.

Cuándo usar una relación many to many

Usa esta relación cuando:

  • Ninguna de las dos entidades “posee” realmente a la otra.
  • Ambas pueden existir de forma independiente.
  • Necesitas consultar la relación desde ambos lados.

Diferencia entre uno a muchos y muchos a muchos

En uno a muchos, una clave foránea vive en una sola tabla.

En muchos a muchos, necesitas una tabla intermedia que conecte ambas.

Y aquí es donde entra en juego la tabla pivote.

La tabla pivote en Laravel (pivot table)

Qué es y por qué es necesaria

La tabla pivote es la que almacena las relaciones entre ambas entidades. No guarda información “de negocio”, sino qué registro está relacionado con cuál.

En un proyecto real trabajando con posts y etiquetas, terminé usando una tabla taggables porque necesitaba reutilizar las etiquetas en distintos modelos. Ahí entendí que la tabla pivote no es opcional: es el núcleo de la relación.

Convención de nombres de la tabla intermedia

Laravel asume por defecto:

  • Nombres de modelos en singular
  • Orden alfabético
  • Ejemplo: post_tag

Si no sigues esta convención, tendrás que indicarlo explícitamente en belongsToMany.

Claves foráneas y estructura mínima

Una tabla pivote básica tiene:

  • post_id
  • tag_id

En relaciones más avanzadas puede incluir:

  • timestamps
  • flags
  • tipos (en relaciones polimórficas)

Definir relaciones muchos a muchos con belongsToMany

Trabajando con la mucho a mucho relación directa

Antes que eso, vamos a ver como es la conexión directa, en este ejemplo tengo una relación normalitas entre Posts y Tags o etiquetas, en donde un Post tiene múltiples etiquetas y una etiqueta puede estar asignada a uno o muchos Post, por lo tanto es una relación de muchos a muchos; para esto, tenemos una tabla pivote llamada taggable en la cual guardamos dichos valores: la misma tiene la siguiente estructura:

  • La columna de tag_id guarda los identificadores de la tabla Tag
  • La columna de taggable_id guarda los identificadores de la tabla Post

Relación muchos a muchos directa

Si vamos a mantener una relación en la cual una etiqueta PERTENECE a un Post, tenemos:

class Post extends Model
{
    protected $fillable = ...
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
        // return $this->morphToMany(Tag::class, 'taggable'); // si quieres una relacion de tipo morfica
    }

Relación muchos a muchos inversa

En el modelo inverso también debes definir belongsToMany, no hasMany.

Este es un error muy común. si defines con hasMany y simplemente no funciona. El problema viene, cuando quieres obtener las relaciones de manera inversa, es decir, dado una etiqueta, quieres obtener todos los Post que pertenezcan a esa etiqueta; para eso, desde la etiqueta, tenemos que crear la relación directa, ya que con el hasMany NO podemos hacer esta operación; así que:

Para las etiquetas que es la relación inversa (si quieres obtener los post de una etiqueta)

class Tag extends Model
{
    protected $fillable = ...
    public function posts()
    {
        return $this->belongsToMany(Post::class);
        // return $this->hasMany(Post::class);
    }
}

Quedando como:

class Tag extends Model
{
    protected $fillable = ['title', 'url_clean'];
    public function post()
    {
        return $this->hasMany(Post::class);
    }
    public function myPosts()
    {
        return $this->belongsToMany(Post::class,'taggables','tag_id','taggable_id');
    }
}

Y con esto ya podemos obtener la relación directa entre Tags/Etiquetas y Posts; así que:

$posts = $tag::myPost()->get();

Ejemplo básico para obtener registros

Una vez definida ambas relaciones:

$post = Post::find(1);
$tags = $post->tags;

$tag = Tag::find(1);
$posts = $tag->posts;

Acceder a los datos de una relación muchos a muchos

Obtener relaciones desde el modelo principal

Para obtener las etiquetas de un post:

$posts = Post->with('tags')->get();

Obtener relaciones desde el modelo inverso

Tag::with('posts')->get();

En mi caso, tengo una relación más, que es la de cursos, en la cual un post pertenecen a un curso, así que:

$courses_tags = Tag::whereHas('myPost', function ($query) {
      return $query->where('type', 'courses');
})->get();

Y la relación o modelo para nuestro curso es:

class Course extends Model
{
    protected $fillable = ['name', 'description', 'image', 'post_id'];
    public function post()
    {
        return $this->belongsTo(Post::class);
    }
}
class Post extends Model
{
    protected $fillable = ['title', 'url_clean', 'content', 'category_id', 'posted', 'description', 'final_content', 'aux_content', 'web_content', 'image', 'path', 'date', 'type'];
    public function category()
    {
        return $this->belongsTo(Category::class);
    }
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

Cargar relaciones con eager loading

Siempre que puedas, usa with() para evitar el problema N+1 y mejorar el rendimiento.

Muchos a muchos entre productos y tags

Otro ejemplo de relaciones de tipo muchos a muchos primero, se puede crear una tabla intermedia llamada "product_tag" que tenga las claves foráneas para el producto y la etiqueta, es decir, esta sería la tabla pivote. Ahora, se pueden definir los modelos de Eloquent de Laravel y sus correspondientes relaciones entre ellos. 

El modelo de producto, se usar el método belongsToMany() para definir la relación con la etiqueta, en donde un post tiene múltiples etiquetas

Mientras que en el modelo de etiqueta se puede usar el mismo método para definir la relación inversa con los productos:

class Product extends Model
{
   public function tags()
   {
       return $this->belongsToMany(Tag::class);
   }
}
class Tag extends Model
{
   public function products()
   {
       return $this->belongsToMany(Product::class);
   }
}

Para referenciarlas tenemos:

$product = Product::find(1);
$tags = $product->tags;

$tag = Tag::find(1);
$products = $tag->products;

Trabajar con la tabla pivote

Añadir campos extra con withPivot

Si tu tabla pivote tiene más columnas:

return $this->belongsToMany(Role::class)
           ->withPivot('active');

Manejar timestamps en la tabla pivote

return $this->belongsToMany(Role::class)
           ->withTimestamps();

Acceder a los datos del pivote

$role->pivot->active;

Operaciones comunes en relaciones muchos a muchos

attach, detach y sync

$post->tags()->attach(1);
$post->tags()->detach(1);
$post->tags()->sync([1, 2, 3]);

Cuándo usar sync y cuándo no

  • sync reemplaza relaciones
  • attach añade sin borrar
  • detach elimina

Usar sync sin cuidado puede borrar datos de las relaciones existentes sin que te des cuenta.

Errores habituales al sincronizar relaciones

  • Pasar modelos en lugar de IDs
  • Usar sync cuando solo quieres añadir
  • No validar datos antes de sincronizar

Filtrar relaciones muchos a muchos en Laravel

Usar whereHas con many to many

Tag::whereHas('posts', function ($query) {
   $query->where('type', 'courses');
})->get();

Este patrón lo terminé usando cuando necesitaba obtener solo etiquetas asociadas a cursos, algo que no suele explicarse en tutoriales básicos.

Filtrar por campos del pivote

return $this->belongsToMany(Role::class)
           ->wherePivot('active', 1);

Casos reales de filtrado avanzado

  • Etiquetas activas
  • Roles con fecha válida
  • Relaciones dependientes de estado

Relaciones muchos a muchos polimórficas

Las relaciones polimórficas es un tópico mas avanzado y temido por algunos, pero, en esencia es un mecanismo que nos permite compartir una tabla pivote y con esto sus relaciones muchos a muchos (aunque también la puedes emplear en el resto de las relaciones); en definitiva, el “problema” que tenemos con el esquema tradicional es que, la FK o clave foránea es especifica a una tabla, por lo tanto, si queremos crear una relación (por ejemplo comentarios y posts, y luego queremos agarrar la tabla de comentarios también para videos) debemos de duplicar la tabla relacional (en este ejemplo la de comentarios -comentarios_video y comentarios_post-) lo cual implica mas trabajo.

Pero, con las mórficas nos permiten usar la misma tabla original (comentarios) agregando una columna adicional (ya lo hace Laravel internamente por nosotros) que especifica a quien pertenece la relación (comentario).

Cuándo usar morphToMany

Úsalo cuando:

  • Una entidad (como tags) puede relacionarse con distintos modelos
  • Quieres reutilizar la misma tabla pivote

Diferencia entre belongsToMany y morphToMany

  • belongsToMany: relación clásica
  • morphToMany: relación flexible basada en tipo

Ejemplo real con etiquetas (tags)

class Post extends Model
{
   public function tags()
   {
       return $this->morphToMany(Tag::class, 'taggable');
   }
}

Y en el modelo Tag:

class Tag extends Model
{
   public function posts()
   {
       return $this->morphedByMany(Post::class, 'taggable');
   }
}

Errores comunes en relaciones muchos a muchos en Laravel

  • Usar hasMany en lugar de belongsToMany
    • Error clásico y muy frecuente.
  • Problemas con claves foráneas
    • Nombres incorrectos
    • Orden incorrecto
    • Falta de índices
  • Confusión entre relaciones normales y polimórficas
    • Si no necesitas polimorfismo, no lo uses. Complica más de lo que ayuda.

Buenas prácticas para relaciones many to many en Laravel

Diseño de base de datos

  • Nombres claros
  • Claves bien definidas
  • Índices en tablas pivote

Rendimiento y consultas eficientes

  • Usa eager loading
  • Filtra en base de datos, no en PHP

Cuándo replantear el modelo

  • Si la tabla pivote empieza a tener demasiada lógica, quizá necesite ser un modelo propio.

Preguntas frecuentes sobre relaciones muchos a muchos en Laravel

  • ¿Qué pasa si no uso tabla pivote?
    • No puedes modelar una relación many to many correctamente.
  • ¿Puedo usar muchos a muchos sin modelo intermedio?
    • Sí, pero cuando la tabla pivote crece, es mejor crear un modelo propio.
  • ¿Cómo depurar una relación many to many que no funciona?
    • Revisa nombres de tablas
    • Revisa claves foráneas
    • Prueba la consulta directamente en SQL

Conclusión

Dominar las relaciones muchos a muchos en Laravel marca una gran diferencia entre saber usar Eloquent y usar Eloquent bien. Cuando entiendes cómo funciona la tabla pivote, cómo definir la relación inversa y cómo filtrar correctamente, empiezas a resolver problemas reales sin parches.

Aprende no solo a manejar muchos registro sino también, a poder hacer operaciones en una unidad de trabajo mediante las transacciones a la base de datos.

Sabes como puedes obtener registros con una relación invertida en Laravel, por ejemplo, si tienes etiquetas y quieres obtener los POST, es muy fácil! pero tienes que saber COMO Hacerlo!

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz

EN In english