Índice de contenido
- ¿Qué son las relaciones mórficas en Laravel y por qué existen?
- Diferencia entre relaciones normales y relaciones polimórficas
- Cómo funcionan las relaciones mórficas en Eloquent
- Qué columnas crea realmente morphs()
- Tipos de relaciones mórficas en Laravel
- Relación mórfica uno a uno (1:1)
- Relación mórfica muchos a muchos (N:M)
- Ejemplo: carrito de compras con productos polimórficos
- El problema: distintos tipos de productos con columnas distintas
- Por qué un Tutorial no se comporta igual que un Libro
- Cómo limitar columnas en una relación mórfica usando constrain()
- Definiendo selects distintos por cada modelo
- Personalizar columnas en la selección en Relaciones mórficas
- Ejemplo completo con ShoppingCart, Tutorial y Book
- Por qué esta solución evita errores y mejora rendimiento
- Errores comunes al trabajar con relaciones mórficas en Laravel
- Buenas prácticas al usar relaciones mórficas en proyectos reales
- Preguntas frecuentes sobre relaciones mórficas en Laravel
- Conclusión: cuándo las relaciones mórficas realmente marcan la diferencia
Si alguna vez trabajando con Laravel pensaste “esto debería poder resolverse con una sola relación”, probablemente estabas a punto de descubrir las relaciones mórficas. En Eloquent, este tipo de relaciones permiten que un modelo esté asociado a distintos modelos sin duplicar tablas ni lógica, algo especialmente útil cuando el dominio del negocio empieza a crecer.
En esta guía voy a explicarte qué son las relaciones mórficas en Laravel, cómo funcionan, los tipos que existen y, sobre todo, cómo resolver problemas reales que aparecen cuando los modelos no comparten exactamente las mismas columnas.
¿Qué son las relaciones mórficas en Laravel y por qué existen?
Las relaciones mórficas (o relaciones polimórficas) permiten que un modelo pertenezca a más de un tipo de modelo usando una única relación.
En lugar de tener múltiples tablas o múltiples claves foráneas, Laravel guarda dos columnas clave:
- *_id → el ID del modelo relacionado
- *_type → el nombre de la clase del modelo relacionado
Esto hace posible que, por ejemplo, una tabla comments pueda pertenecer tanto a Post como a Video sin duplicar estructura.
La gran ventaja es la flexibilidad: puedes extender tu sistema sin modificar el esquema base cada vez que aparece un nuevo modelo relacionado.
Diferencia entre relaciones normales y relaciones polimórficas
En una relación tradicional, el vínculo es fijo:
un comentario pertenece a un post, una factura pertenece a un cliente, etc.
En una relación mórfica, ese vínculo cambia dinámicamente por registro.
Un comentario puede pertenecer a un post o a un video.
Una imagen puede pertenecer a un usuario o a un producto.
Esto reduce mantenimiento, evita tablas repetidas y mantiene el modelo de datos limpio.
Cómo funcionan las relaciones mórficas en Eloquent
Laravel abstrae toda la complejidad mediante métodos muy claros.
morphTo, morphOne y morphMany explicados de forma sencilla
- morphTo() → se usa en el modelo “hijo”
- morphOne() → relación uno a uno
- morphMany() → relación uno a muchos
Por ejemplo, el modelo que "recibe" la relación siempre usa morphTo().
Qué columnas crea realmente morphs()
Cuando usas:
$table->morphs('commentable');Laravel crea automáticamente:
- commentable_id
- commentable_type
Y es Eloquent, no la base de datos, quien interpreta a qué modelo pertenece cada registro.
Qué guarda Laravel en *_id y *_type
- *_id: el ID real del modelo relacionado
- *_type: el nombre completo de la clase (App\Models\Post, por ejemplo)
Esto es importante entenderlo porque más adelante veremos por qué Laravel puede intentar leer columnas que no existen si no controlamos bien la relación.
Tipos de relaciones mórficas en Laravel
Relación mórfica uno a uno (1:1)
Un modelo tiene un único registro relacionado, pero ese registro puede pertenecer a distintos modelos.
Ejemplo típico: imágenes de perfil para usuarios y posts.
Relación mórfica uno a muchos (1:N)
Un modelo puede tener muchos registros relacionados, como comentarios en posts o videos.
Es el caso más común y donde más partido se le saca al polimorfismo.
Relación mórfica muchos a muchos (N:M)
Aquí entran escenarios como etiquetas (tags) que pueden asociarse a múltiples modelos diferentes usando una tabla pivote polimórfica.
Ejemplo: carrito de compras con productos polimórficos
Aquí es donde las cosas se ponen interesantes.
El carrito podía contener distintos tipos de productos:
- Tutoriales
- Libros
Hasta ahí, todo perfecto para una relación mórfica.
El problema apareció cuando los tutoriales tenían opciones adicionales (como código fuente por clase o clases exclusivas) que afectaban el precio, mientras que los libros no tenían ninguna modalidad extra.
El problema: distintos tipos de productos con columnas distintas
Los tutoriales tenían columnas como:
- exclusive_extra
- price_code_extra
Pero el modelo Book no tiene esas columnas, ni debería tenerlas.
Si dejaba la relación definida de forma genérica, Laravel intentaba leer columnas inexistentes y la relación fallaba.
Por qué un Tutorial no se comporta igual que un Libro
Aunque ambos son “productos” desde el punto de vista del carrito, no comparten exactamente la misma estructura.
Y aquí es donde muchos ejemplos teóricos se quedan cortos:
En proyectos reales, los modelos polimórficos no siempre son idénticos.
Qué pasa si Laravel intenta leer columnas que no existen
Si haces un select * implícito en una relación mórfica, Eloquent no sabe qué columnas existen o no en cada modelo.
Resultado:
errores SQL o consultas innecesarias que afectan rendimiento.
Cómo limitar columnas en una relación mórfica usando constrain()
La solución fue definir explícitamente qué columnas debe seleccionar Laravel según el modelo.
Y para eso existe constrain().
Definiendo selects distintos por cada modelo
En el modelo del carrito, la relación quedó así:
class ShoppingCart extends Model
{
public function itemable()
{
return $this->morphTo()->constrain([
Tutorial::class => function ($query) {
$query->select(
'id',
'title',
'url_clean',
'price',
'price_offers',
'exclusive_extra',
'price_code_extra'
);
},
Book::class => function ($query) {
$query->select(
'id',
'title',
'url_clean',
'price',
'price_offers'
);
},
]);
}
}Personalizar columnas en la selección en Relaciones mórficas
Teniendo una relación mórfica, te voy a mostrar cómo puedes obtener columnas distintas. Así de simple. Aunque suene más complejo de lo que parece, es exactamente lo que puedes ver por aquí.
Yo estoy armando un carrito de compras. Entiéndase que, para mi tienda en línea, los productos pueden ser tutoriales o libros. ¿Cuál es el dilema?
Por ejemplo, para los cursos, yo tengo algunos que incluyen la opción de decidir si quieres:
- El código fuente por clase.
- Clases exclusivas.
Esto corresponde al panel que tenemos aquí, y como influye en el precio, sí o sí necesito pasar esa información al carrito de compras.
Este par de columnas no se encuentra definido en el modelo de libro.
En el caso del libro:
Solo se compra completo, sin modalidades adicionales.
Por lo demás, las demás columnas son exactamente las mismas que tengo referenciadas abajo.
La sintaxis es sencilla:
class ShoppingCart extends Model
{
***
public function itemable()
{
return $this->morphTo()->constrain([
Tutorial::class => function ($query) {
$query->select('id', 'title', 'url_clean', 'price', 'price_offers', 'exclusive_extra', 'price_code_extra');
},
Book::class => function ($query) {
$query->select('id', 'title', 'url_clean', 'price', 'price_offers'); // , NULL as price_exclusive_extra
},
]);
}- En el método que indica que es una relación mórfica, creamos un constraint.
- Indicamos cada una de las relaciones que tengamos.
- Definimos las columnas que deben existir en cada caso.
En el libro, la definición es más sencilla, ya que no necesita columnas adicionales.
En el curso, sí hay que definir esas columnas extra.
Esto evita que la relación falle buscando columnas inexistentes, como ocurriría si se dejara definida de forma genérica.
Ejemplo completo con ShoppingCart, Tutorial y Book
Cada modelo devuelve solo las columnas que realmente existen, sin forzar estructuras artificiales.
En el caso del libro, la relación es más simple porque no necesita datos adicionales.
En el caso del tutorial, se incluyen las columnas extra que influyen directamente en el precio final.
Por qué esta solución evita errores y mejora rendimiento
- Laravel no busca columnas inexistentes
- Las consultas son más livianas
- El código refleja mejor el dominio real del negocio
Desde que implementé esto, el carrito dejó de ser frágil y pasó a ser completamente predecible.
Errores comunes al trabajar con relaciones mórficas en Laravel
- Columnas inexistentes en modelos polimórficos
- Asumir que todos los modelos comparten la misma estructura.
- Sobrecargar consultas sin necesidad
- No limitar columnas cuando sabes exactamente qué necesitas.
- Confundir claves primarias con claves polimórficas
- Las relaciones mórficas no usan claves foráneas tradicionales a nivel de base de datos.
Buenas prácticas al usar relaciones mórficas en proyectos reales
Cuándo sí usar polimorfismo
- Entidades reutilizables
- Modelos extensibles en el tiempo
- Dominios donde el tipo puede variar
Cuándo NO conviene usar relaciones mórficas
- Cuando las relaciones son fijas
- Cuando necesitas integridad referencial estricta a nivel SQL
Preguntas frecuentes sobre relaciones mórficas en Laravel
- ¿Puedo usar selects distintos en morphTo?
- Sí, usando constrain(), como en el ejemplo del carrito.
- ¿Las relaciones mórficas afectan el performance?
- No necesariamente. Mal usadas, sí. Bien controladas, son muy eficientes.
- ¿Se pueden validar a nivel base de datos?
- No directamente. La validación se maneja principalmente desde Eloquent.
Conclusión: cuándo las relaciones mórficas realmente marcan la diferencia
Las relaciones mórficas en Laravel no son solo una curiosidad del framework.
Bien usadas, permiten crear sistemas flexibles, mantenibles y escalables, incluso cuando los modelos no son idénticos entre sí.
En escenarios reales (como un carrito con productos distintos) entender cómo controlar columnas, consultas y comportamiento marca la diferencia entre un sistema frágil y uno sólido.