> Manuales > Manual de Laravel

Cómo configurar las migraciones y los modelos de Laravel para gestionar relaciones de muchos a muchos (N a M), con el ORM Eloquent. Cómo hacer operaciones típicas de relación entre los modelos.

Relaciones Laravel Eloquent de muchos a muchos

En el Manual de Laravel hemos podido aprender ya a gestionar relaciones más sencillas, como las de 1 a N. No es que las relaciones de muchos a muchos sean muy complejas, pero sí que nos obligarán a hacer algún paso extra en nuestros modelos, así como la creación de la llamada tabla pivote.

En este artículo aprenderás a crear tus relaciones de muchos a muchos, de manera que tus modelos sean capaces de abstraerse de la complejidad de este tipo de relación, pasando fácilmente de un elemento de una entidad a sus elementos relacionados de otras entidades. Afortunadamente, una vez completados una serie de pasos a la hora de configurar los modelos, el manejo de estas relaciones será tan fácil como un juego de niños.

El primer paso que tendrías que hacer es conocer, al menos de manera general, los mecanismos para realizar relaciones más sencillas en Laravel. Esto es algo que ya hemos tratado en artículos anteriores del Manual de Laravel. Como es todo bastante parecido, ofreceremos menos explicaciones de las cosas que ya deberías de conocer. Te recomendamos para ello al menos la lectura de las relaciones de 1 a N. Por supuesto, también tienes que conocer y saber usar los modelos de Laravel / Eloquent.

Tabla pivote en relaciones de N a M

Primero comencemos por un pequeño refresco de nuestros conocimientos de bases de datos relacionales. Recordemos que las relaciones de muchos a muchos generan una tabla adicional, en la que tenemos los índices de los dos elementos de la relación.

Por ejemplo, tenemos la relación tag y article (etiquetas y artículos). Una etiqueta puede tener asociados muchos artículos y un artículo puede tener asociadas varias etiquetas. Lo vemos en la siguiente imagen.

En este caso tendremos, además de las tablas de "Tags" y "Articles", una tercera tabla que dará soporte a la relación de muchos a muchos. Ésta es la denominada "tabla pivote" o simplemente "pivot". Básicamente, la tabla pivot mantiene los identificadores de tag y de article, para cada elemento con su relación.

Nota: adicionalmente, si por el hecho de existir esa relación se generan más datos, podrían ir en la tabla pivote. Por ejemplo, imagina que quieres ver los artículos ordenados de una manera especial en la página de un tag. Entonces ese orden podría colocarse en la tabla pivote, como un campo extra.

En la tabla pivote no te hace falta más que los identificadores y no necesitas tener el típico autoincrement como clave primaria para la tabla, ni tampoco los timestamps de Laravel, a no ser que los necesites para alguna cosa.

Otra cosa importante es que la tabla pivote debería tener un nombre determinado, para ajustarnos a las convenciones de Eloquent, y así evitar tener que escribir más código del estrictamente necesario. El nombre simplemente es la unión de las dos entidades, separado por un guión bajo. Además, las entidades se ordenan alfabéticamente, por lo que primero nombraremos a la tabla article: "article_tag".

Para que quede más claro, coloco a continuación el código de una migración para hacer esa tabla pivote. Recuerda que tienes más información en el artículo de las migraciones de Laravel.

Schema::create('article_tag', function (Blueprint $table) {
    $table->integer('article_id')->unsigned();
    $table->integer('tag_id')->unsigned();

    $table->foreign('article_id')->references('id')->on('articles');
    $table->foreign('tag_id')->references('id')->on('tags');
});

Migraciones con foreignId

Actualmente Laravel soporta un campo con el que definir de una manera más cómoda las claves foráneas de las tablas, mediante el método foreignId(). Este método ya configura el identificador con bigInt y unsigned de una.

Otra ventaja es que podemos aplicar el modificador constrained() que sirve para que cree el índice que nos garantice la integridad referencial. Usando estos métodos la migración tendría el siguiente aspecto:

Schema::create('collection_game', function (Blueprint $table) {
    $table->id();
    $table->foreignId('collection_id')->constrained();
    $table->foreignId('game_id')->constrained();
    $table->timestamps();
});

Configuración de los modelos

En la configuración de los modelos de Laravel, colocaremos la relación con el método "belongsToMany". Es algo muy parecido a como hacíamos con relaciones anteriormente, sólo que usamos un método nuevo.

En el modelo "Tag" crearemos un método que nos devuelva los artículos relacionados, de esta manera:

public function articles() {
    return $this->belongsToMany('App\Article');
}

Para la relación inversa, desde Article a Tag, usamos exactamente el mismo método "belongsToMany". Pero caro, en el modelo de "Article" la relación es con "Tag". Nos quedará más o menos así:

public function tags() {
    return $this->belongsToMany('App\Tag');
}

Con estos sencillos pasos ya podemos desde un modelo obtener todos los registros en entidades relacionadas de muchos a muchos.

Nota: ten en cuenta que, si no hemos seguido las convenciones de Laravel para el nombrado de identificadores en las tablas, nombres de las tablas o claves foráneas, tendrías que realizar esas relaciones en los modelos enviando algunos parámetros extra. Lo ideal es seguir las convenciones, para no tener trabajo de más, aunque si tu proyecto no lo puede hacer así, por favor, revisa la documentación de Laravel.

Definir la relación en el modelo haciendo uso de Type Hinting de PHP

Actualmente PHP soporta lo que se denomina "Type Hinting" que es una característica que permite que PHP compruebe el tipo de aquellos elementos con los que trata.

En el caso de querer aprovecharnos de esta característica de PHP, que nos puede ayudar a cometer menos errores, o menor dicho, detectarlos antes de llevar el código a producción, podemos indicar que el método que define la relación debe devolver un tipo BelongsToMany.

El método nos quedaría de esta manera:

public function collections(): BelongsToMany
{
    return $this->belongsToMany(Collection::class);
}

Utilizar o no Type Hinting es meramente opcional. Podríamos quitar la parte de : BelongsToMany en la cabecera del método y el código seguiría funcionando del mismo modo. Como decimos, puede resultar una ayuda a la hora de detectar los errores de manera más temprana, pero sobre todo lo comentamos porque actualmente en la documentación de Laravel encontrarás el código usando Type Hinting.

Acceder a las relaciones desde un modelo a otro

Una vez definida la relación, lo interesante es que puedo acceder a los elementos relacionados como por arte de magia, sin tener que hacer consultas complicadas. Para ello usaremos una propiedad en el modelo, que tiene el nombre igual al método que hemos usado para definir la relación.

Por ejemplo, si tengo una instancia de un modelo Article y quiero acceder a sus tags, colocaré el código:

$article->tags

Esto nos permite acceder a una colección, que será el listado de todos los tags que tiene el article que teníamos en el modelo. Ten en cuenta que este acceso se realiza por "lazy load", de modo que Laravel no extraerá la lista de tags de un artículo hasta que no se la pidas explícitamente.

No obstante, el acceso por lazy load no siempre lo mejor. Imagina que estás haciendo un listado de artículos y tienes 25 artículos que mostrar. Entonces le pides a Eloquent esa colección de modelos y te los entrega tal cual. Pero de cada artículo quieres que se vean en ese listado todas sus etiquetas, por lo que usarás la relación para acceder a ellas. El problema de esto es que con cada acceso a los tag de un artículo se genera una nueva consulta. Para evitar esta situación podemos decirle a Laravel que nos traiga de una vez todas las etiquetas relacionadas de cada artículo encontrado.

$articlesConTags = Article::with('tags')->get();

Conclusión

Con esto conocemos lo básico de las relaciones de muchos a muchos. Esperamos que te haya resultado de utilidad y recuerda que Laravel Eloquent tiene mucha flexibilidad, por lo que existen bastantes detalles de configuración que no hemos llegado a analizar para las relaciones de N a M y que resultarán imprescindibles si es que tus tablas no siguen todas las convenciones de Laravel.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual