Relaciones N a N en Laravel 10 con Eloquent ORM

Guía Completa Relaciones N a N en Laravel 10

Las relaciones entre tablas son una parte fundamental en el desarrollo de aplicaciones web con Laravel. Existen diferentes tipos de relaciones que nos permiten modelar las interacciones entre los datos de nuestra base de datos.

En este artículo vamos a explorar en detalle las relaciones N a N, también conocidas como relaciones muchos a muchos. Veremos cómo configurarlas y sacarles el máximo provecho en Laravel 10 utilizando el ORM Eloquent.

Introducción a las relaciones N a N en Laravel 10

Las relaciones uno a uno, uno a muchos y muchos a uno son las más comunes en el desarrollo de aplicaciones. Sin embargo, en ocasiones necesitamos modelar relaciones más complejas donde una entidad puede estar relacionada con múltiples instancias de otra entidad y viceversa.

Para estos casos, Laravel soporta las relaciones N a N (many to many) a través del uso de tablas pivote. Veamos con más detalle en qué consisten.

¿Qué son las relaciones N a N?

Las relaciones N a N permiten vincular registros de dos tablas de forma bidireccional. Por ejemplo, un usuario puede tener múltiples roles, y a su vez cada rol puede estar asignado a múltiples usuarios.

Otro caso típico son las etiquetas en una aplicación de blogs. Un artículo puede tener varias etiquetas, y una etiqueta se puede utilizar en múltiples artículos.

En una relación N a N, ninguna de las dos entidades depende directamente de la otra, a diferencia de las relaciones uno a muchos o muchos a uno. Ambos tipos de modelos son independientes.

Ejemplos de casos de uso

Algunos ejemplos de casos de uso comunes para las relaciones N a N:

  • - Usuarios y roles

  • - Artículos y etiquetas

  • - Productos y categorías

  • - Doctores y pacientes

  • - Estudiantes e inscripciones a cursos

  • - Actores y películas en las que han participado

Como podemos observar, son situaciones en las que una entidad tiene una relación bidireccional con otra.

Limitaciones de otros tipos de relaciones

Las relaciones uno a uno, uno a muchos y muchos a uno presentan limitaciones cuando necesitamos modelar interacciones más flexibles:

  • - En una relación uno a muchos, el modelo "muchos" depende del modelo "uno" para existir.

  • - En una relación muchos a uno, los modelos "muchos" compiten por un único modelo "uno".

  • - Las relaciones uno a uno limitan a un modelo a solo una relación.

Las relaciones N a N nos dan mayor flexibilidad al permitir que ambos modelos se relacionen de forma independiente.


Configuración de tablas pivot en Laravel 10

Para poder trabajar con relaciones N a N, Laravel utiliza una tabla pivot o intermedia que permite vincular los registros de ambos modelos.

Veamos cómo configurar estas tablas pivot.

Migraciones para crear tablas pivot

Supongamos que tenemos una tabla users y una tabla roles. Primero necesitamos crear una migración para la tabla pivot role_user:

    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->unsignedBigInteger('role_id');
    $table->timestamps();

    $table->foreign('user_id')->references('id')->on('users');
    $table->foreign('role_id')->references('id')->on('roles');

Como vemos, la tabla pivot contiene las claves primarias de cada una de las tablas relacionadas como claves foráneas.

Definición de modelos

Una vez creadas las migraciones, debemos definir los modelos User, Role y RoleUser:

// Modelo User
class User extends Model
{
    //
}

// Modelo Role
class Role extends Model
{
    //
}



// Modelo RoleUser
namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    
}

Note que el modelo pivot extiende de la clase Pivot de Laravel.

Configuración de llaves foráneas

Por último, es recomendable agregar los nombres de las claves foráneas en las propiedades $with de cada modelo:

// En User
protected $with = ['roleUsers'];

// En Role
protected $with = ['roleUsers'];

Esto mejorará el rendimiento de algunas consultas.


Manejo de relaciones N a N con Eloquent ORM

Ahora que tenemos configuradas las tablas, podemos definir la relación N a N en los modelos y realizar operaciones típicas de CRUD.

Métodos para definir relaciones N a N

En cada modelo debemos definir un método que devuelva la relación:

// En User
public function roles()
{
    return $this->belongsToMany(Role::class);
}

// En Role
public function users()
{
    return $this->belongsToMany(User::class);
}

Con esto ya podemos acceder a la colección de roles desde un usuario y viceversa:

$user = User::find(1);

foreach ($user->roles as $role) {
    //
}

Consultas con with() para eager loading

Podemos utilizar with() al consultar para cargar las relaciones de forma eager:

$users = User::with('roles')->get();

foreach ($users as $user) {
    echo $user->roles; // Colección de roles
}

Esto es más eficiente que cargar las relaciones de forma lazy.

Attach, detach y sync para manipular relaciones

Hay varios métodos disponibles para asignar y desasignar relaciones:

$user->roles()->attach($roleId); // Asociar

$user->roles()->detach($roleId); // Desasociar

$user->roles()->sync([1, 2, 3]); // Sincronizar

sync() acepta un array de IDs para reemplazar todas las relaciones existentes.


Consultas avanzadas en relaciones N a N

Las relaciones N a N se prestan para consultas avanzadas muy potentes.

WhereHas para filtrar resultados

Podemos utilizar `whereHas()` para filtrar:

$users = User::whereHas('roles', function ($q) {
    $q->where('name', 'admin');
})->get();

Esto devolverá solo los usuarios que tengan el rol "admin".

Búsquedas y agrupaciones usando tablas pivot

También podemos realizar joins e interactuar directamente con la tabla pivot:

$roleCount = User::leftJoin('role_user', 'users.id', '=', 'role_user.user_id')
    ->groupBy('users.id')
    ->selectRaw('users.id, count(*) as role_count')
    ->having('role_count', '>', 5)
    ->get();

Esto nos permite amplia flexibilidad en las consultas.

Optimizaciones de consultas con lazy eager loading

Para evitar cargar todas las relaciones, podemos usar load() para lazy eager loading:

$users = User::all();

// Carga roles solo si se accede a la propiedad
if ($someCondition) {
    $users->load('roles');
}

Esto puede mejorar el rendimiento cuando no siempre se necesitan las relaciones.


Ejemplo práctico Relaciones N a N en Laravel 10

Para ver cómo implementar de forma completa las relaciones N a N, desarrollaremos una aplicación simple de una biblioteca con usuarios y libros.

Primero, las migraciones:

// Tabla Usuarios
Schema::create('usuarios', function (Blueprint $table) {
    $table->id();
    $table->string('nombre');
  });
  
  // Tabla Libros
  Schema::create('libros', function (Blueprint $table) {
    $table->id();
    $table->string('titulo');
    $table->text('descripcion');
  });
  
  // Tabla Pivot Usuario_libro
  Schema::create('usuario_libro', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('usuario_id');
    $table->unsignedBigInteger('libro_id');
    $table->timestamps();
  
    $table->foreign('usuario_id')->references('id')->on('usuarios');
    $table->foreign('libro_id')->references('id')->on('libros');
  });

Ahora los modelos Usuario, Libro y UsuarioLibro:

class Usuario extends Model
{
  public function libros()
  {
    return $this->belongsToMany(Libro::class);
  }
}

class Libro extends Model
{
  public function usuarios()
  {
    return $this->belongsToMany(UseUsuarior::class);
  }
}

class UsuarioLibro extends Pivot
{
  //
}

Con esto ya podemos comenzar a trabajar con las relaciones:

<?php

// Obtener los libros de un usuario
$usuario = Usuario::find(1);
foreach ($usuario->libros as $libro) {
  //
}

// Obtener los usuarios que han "alquilado" un libro
$libro = Libro::find(1);
foreach ($libro->usuarios as $usuario) {
  //
}

// Asignar un libro a un usuario
$usuario->libros()->attach($libro_id);

// Quitar un libro de un usuario
$usuario->libros()->detach($libro_id);

// Libros con más de 3 usuarios asignados
$libros = Libro::whereHas('usuarios', function ($q) {
            $q->havingCount('usuario_libro', '>', 3);
        })->get();

Conclusión

Las relaciones N a N son indispensables en el desarrollo de aplicaciones web complejas. Permiten modelos flexibles para representar escenarios del mundo real.

Configurar estas relaciones en Laravel resulta muy sencillo gracias a las migraciones, los métodos de Eloquent y las potentes consultas disponibles.

Lo principal es entender cómo funcionan las tablas pivot y definir correctamente los métodos en los modelos. Una vez hecho esto, realizar operaciones CRUD y consultas avanzadas es muy similar a otros tipos de relaciones.

Dominar las relaciones N a N nos dará una ventaja competitiva para construir aplicaciones Laravel robustas y escalables. ¡Pongamos en práctica estos conocimientos!

Preguntas Frecuentes

Usa el método toArray():

Usa el método sync() pasando un array vacío:

Puedes agregar nuevos campos a la migración de la tabla pivot. Luego puedes acceder a ellos como atributos del modelo pivot.

Sí, cada relación requeriría su propia tabla pivot con nombre distinto.