Qué son las relaciones polimórficas de Laravel Eloquent, por qué son importantes y cómo implementarlas en tus aplicaciones Laravel, desde las migraciones a la configuración y uso de los modelos.
Al crear el modelo de datos en las aplicaciones a veces necesitas que los registros de ciertas entidades estén relacionadas a veces con una y a veces con otra entidad distinta. Esta es una situación habitual que se resuelve con las relaciones polimórficas de Laravel Eloquent y que vamos a abordar en este artículo.
Ojo, no nos estamos refiriendo a relaciones de 1 a n, o a relaciones de n a m, que fueron abordadas en otros artículos del Manual de Laravel. En las relaciones de estos tipos una entidad está relacionada con otra de manera inequívoca (es siempre la misma entidad con la que relacionamos). En las relaciones polimórficas esta relación puede ser variable en cada uno de los registros de una tabla. Lo vamos a ver muy bien con un ejemplo que lo dejará muy claro.
Para explicar cómo funcionan las Relaciones polimórficas entre modelos de Laravel Eloquent hemos abordado los siguientes puntos.
- Entendiendo el concepto de relación polimórfica y las diferencias con las relaciones comunes
- Relaciones polimórficas en Eloquent
- Cómo se definen las migraciones para crear una tabla polimórfica
- Cómo se definen las relaciones en los modelos
- Código para el acceso de unos modelos a otros en relaciones polimórficas
- Cómo relacionar los modelos con sus entidades polimórficas en el momento de creación del modelo
- Otros tipos de relaciones polimórficas
Entendiendo el concepto de relación polimórfica y las diferencias con las relaciones comunes
Antes de ver las relaciones polimórficas, vamos a repasar la idea de las relaciones comunes. En este esquema de trabajo tenemos tablas que se relacionan con otras de manera siempre fija.
Por ejemplo, tenemos la tabla de "clientes
". Esta tabla se puede relacionar con la tabla de "facturas", "presupuestos", "albaranes"... En cada una de esas tablas (facturas
, presupuestos
, albaranes
…) tendremos el identificador del cliente, porque siempre de manera fija van a estar relacionadas con el cliente. Esta es la situación más normal que hemos visto anteriormente.
En qué se diferencias de las relaciones polimórficas
La diferencia entre las relaciones comunes y las relaciones polimórficas es que la relación puede ir cambiando entre los distintos registros de las tablas.
Por ejemplo tenemos la tabla de "archivos_adjuntos
" en una aplicación de gestión empresarial. El archivo adjunto podría estar relacionado con un cliente, con un presupuesto, con un proveedor. Todas estas entidades podrían tener archivos adjuntos. Sin embargo, un archivo adjunto solamente pertenece a una de estas entidades.
En una situación como esa, si no tuviéramos relaciones polimórficas deberíamos generar tablas de "archivos_adjutos_clientes
", que tendría el id del cliente asociado, "archivos_adjuntos_proveedores
", que tendría el id del proveedor asociado, "archivos_adjuntos_presupuestos
", que tendría el id del presupuesto asociado. Esto sería mucho trabajo de mantenimiento.
Podrían haber otras soluciones alternativas creadas por nosotros mismos de manera más "manual" para evitar repetir una tabla de archivos adjuntos para cada entidad en la que queremos adjuntar archivos, como que la tabla de "archivos_adjuntos" tenga muchos identificadores, uno por cada entidad con la que pretendamos relacionarla. Posiblemente si has diseñado modelos de bases de datos has acabado en alguna solución como esta y no está del todo mal si sabes de antemano qué entidades van a tener archivos adjuntos, de una manera estable. Sin embargo, si no sabes qué entidades podrán tener adjuntos, o éstas puedan cambiar a lo largo del desarrollo, extendiéndose, entonces no es muy práctico tampoco porque necesitarías agregar más y más identificadores a esta tabla con cada actualización. También podríamos trabajar generando tablas pivote para cada tipo de relación que puedes crear, pero tampoco es lo mejor por temas de mantenimiento.
Por ejemplo, imagina que más adelante necesitas una entidad de "visitas_clientes
" y que también tiene archivos adjuntos, o la tabla de "producto
" con sus archivos adjuntos, la tabla de "empleado
" con sus adjuntos, etc. Esto puede derivar también en bastante mantenimiento y, lo peor, en código sucio por tener que consultar muchos identificadores cada vez que quieres saber a qué entidad pertenece un archivo adjunto.
La mejor solución sería tener una única tabla de "archivos_adjuntos
" que tiene una relación polimórfica. Gracias a esto tenemos menos trabajo de mantenimiento, porque solamente existe una tabla para los archivos adjuntos y con ella podemos gestionar las relaciones con con todas las tablas que pueda ser necesario.
Esta solución será extensible en cualquier momento sin modificar el modelo de datos. Además, este tipo de relaciones las controla el framework, por lo que tú tienes un trabajo mínimo para crear las tablas y definir el tipo de relación en los modelos. Es decir, en muy pocas líneas de código lo podemos gestionar todo.
Relaciones polimórficas en Eloquent
Ahora que ya hemos entendido la necesidad de las relaciones polimórficas, vamos a ver una definición más formal.
Las relaciones polimórficas de Eloquent permiten que un modelo de una aplicación Laravel pueda pertenecer a más de un tipo de modelo en una única asociación. Esto es especialmente útil cuando tienes un modelo cuyos registros pueden estar asociados a uno entre varios modelos.
Algunos casos de uso donde podemos encontrar relaciones polimórficas serían los siguientes:
- Comentarios en una aplicación. Supongamos que tienes dos modelos,
Post
yVideo
, y ambos pueden recibir comentarios. En lugar de tener dos tablas separadas,post_comments
yvideo_comments
, puedes tener una sola tablacomments
que maneje comentarios para ambos modelos. - Imágenes. Puedes tener una tabla de
images
, donde cada una de esas imágenes puede pertenecer a un modelo distinto. En vez de tenerpost_images
ovideo_images
lo puedes implementar con una relación polimórfica con una sola tabla de "images
". - Actividades. Puedes tener actividades que registran cambios en distintos modelos, por ejemplo actividades en la tabla de alumnos, colegios, profesores…
- Valoraciones. Puedes tener una tabla que registra las valoraciones en distintas entidades, como películas, series, directores…
- Favoritos. Puedes tener una tabla de favoritos que tiene una relación polimórfica mediante la cual y sin mantenimiento puedes conseguir que tu aplicación pueda marcar como favoritos elementos como juegos, productos, colecciones, accesorios, etc.
Cómo se definen las migraciones para crear una tabla polimórfica
Ahora vamos a entrar en una parte más práctica, explicando cómo se implementan este tipo de relaciones, comenzando por la definición de las tablas. La tabla polimórfica necesita tener un par de campos que serían:
- El identificador de la entidad relacionada
- El nombre del modelo que tiene ese identificador.
Esto lo podemos conseguir con una migración muy sencilla. Por ejemplo esta sería la creación de una tabla comentarios, con un campo de relación polimórfica que se consigue con el método morphs()
.
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->morphs('commentable');
$table->foreignId('user_id');
$table->text('body');
$table->timestamps();
});
}
Como no sabemos qué entidad será la que se relacionará cada comentario a la relación le llamamos "commentable
", eso es bastante abstracto y abarca cualquier cosa que pueda ser comentable.
Para que no te líe, el
$table->foreignId('user_id');
es para la relación con el usuario, porque suponemos que un comentario lo genera un usuario. Pero esa no es una relación polimórfica.
El método morphs()
creará en el fondo dos campos en la tabla:
commentable_id
- Este es el ID del modelo al que pertenece el comentario (ya sea unPost
o unVideo
).commentable_type
- Esta es la referencia al tipo de modelo al que pertenece el comentario (puede ser 'App\Post' o 'App\Video').
Alternativa de migración más explícita
En versiones antiguas de Laravel, o si queremos ser nosotros los que definan los nombres de los campos para la realización polimórifica, podríamos escribir la migración también de esta manera:
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->foreignId('user_id');
$table->unsignedBigInteger('commentable_id');
$table->string('commentable_type');
$table->timestamps();
});
Cómo se definen las relaciones en los modelos
Para poder pivotar desde unas entidades a otras de manera cómoda por medio de las propiedades de los modelos tenemos que definir las relaciones en las clases.
Por ejemplo, siguiendo el caso de los comentarios, podríamos definir una relación en dos sentidos:
- Desde los comentarios hacia uno de los modelos polimórficos
- Desde los vídeos o post hacia los comentarios
Debemos comenzar definiendo la relación en el modelo del comentario, con aquellas entidades polimórficas que fuera menester. Por ejemplo para pivotar desde un comentario hacia el elemento que está comentando, ya sea un vídeo o un post usaríamos un código como este:
public function commentable()
{
return $this->morphTo();
}
Ahora usamos el nombre de esta relación: "commentable
" para poder definir la relación en los modelos de posts y de vídeos. Usaremos un código como este:
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Como has visto, colocamos la clase del modelo de comments Comment::class
y luego el la relación que estamos definiendo de manera polimórfica: 'commentable
'.
Cómo crear modelos y asignar las relaciones polimórficas de Laravel
También es muy importante saber cómo podemos crear nuevos modelos asignando a la vez sus relaciones polimórficas. Por ejemplo, al crear un comentario, asignarlo directamente a un vídeo o un post.
Esto es bastante sencillo gracias a Laravel y Eloquent, con un código como el que podemos ver a continuación:
$video = Video::find(1); // Suponiendo que el curso con ID 1 ya existe
$comment = new Comment(['subject' => 'Nuevo comentario...']);
$video->comments()->save($comment);
Como has visto, usamos la relación comments()
(no te olvides de los paréntesis para acceder a la relación en vez de la colección de comentarios) y luego el método save()
. Así, de una vez, se salvará el comentario y al mismo tiempo se asociará al vídeo que se está comentando.
Por supuesto, para que lo anterior funcione, necesitas haber creado correctamente las relaciones en los modelos Comment y Video.
También podríamos asignar a mano los valores de las columnas en la tabla de comentarios,
commentable_id
ycommentable_type
. Sería perfectamente viable, pero estaríamos dejando de aprovechar las ventajas de la relación que nos aporta Eloquent.
Código para el acceso de unos modelos a otros en relaciones polimórficas
Una vez hemos hecho la configuración de las clases que implementan los modelos, como acabamos de ver, ya podemos acceder desde unos modelos desde el otro.
Puedes recuperar todos los comentarios para un post o un vídeo con este código:
$post->comments;
$video->comments;
Por ejemplo podríamos recorrer todos los comentarios que tiene un vídeo de esta manera:
// Accedemos a un video específico por su identificador
$video = Video::find($videoId);
foreach($video->comments as $comment) {
echo $comment->body . "<br>";
}
Si estás trabajando dentro de una vista Blade de Laravel y quieres mostrar el listado de comentarios, algo que sería perfectamente habitual, puedes usar un código como este:
@foreach($video->comments as $comment)
<p>{{ $comment->body }}</p>
@endforeach
También podemos realizar el camino en sentido contrario. Es decir, para un comentario específico, puedes obtener el modelo al que pertenece (ya sea un Post
o un Video
), por ejemplo:
$comment->commentable
Otros tipos de relaciones polimórficas
Igual que ocurre con las relaciones comunes, hay varios tipos de relaciones polimórficas. En este artículo hemos hablado de relaciones polimórficas de manera genérica, pero hemos llevado a la práctica el ejercicio explicando cómo se haría una relación polimórfica de uno a muchos (1 a n), que es donde podemos sacarle mayor partido a este tipo de relación. Sin embargo, es importante saber que Laravel Eloquent ofrece también otros tipos de relaciones polimorfismo, como de 1 a 1, o de n a m (muchos a muchos).
En la documentación del framework tienes ejemplos para poder implementar más tipos de relaciones que podrías necesitar también. Con lo que has aprendido aquí estamos seguros que podrás entenderlo sin problemas, no obstante escribiremos algo sobre el tema más adelante.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...