Relaciones polimórficas en Laravel

Video thumbnail

Índice de contenido

Cuando comencé a trabajar con Laravel y Eloquent, uno de los conceptos que más me llamó la atención fue el de las relaciones polimórficas. Son extremadamente útiles cuando queremos reutilizar una misma tabla o modelo para asociarlo con múltiples entidades sin duplicar estructuras.

En esta guía te explico ya no solo como manejar las páginas de errores en Laravel si no, qué son las relaciones polimórficas, cómo se usan y te comparto casos reales de uso —incluyendo ejemplos funcionales de mi propio proyecto con libros, pagos y archivos y también, con publicaciones.

En Laravel para usar las relaciones de muchos a muchos, lo tenemos muy fácil, en vez de crear una tabla pivot para cada relación como mostramos anteriormente, podemos crear las relaciones polimórficas que en otras palabras, permite emplear la misma tabla pivote para cualquier relación que queramos relacionar con las etiquetas y Laravel de manera interna sabe a que le pertenece cada relación mediante una etiqueta:

Campo de tipo para identificar la relación

De esta forma, con esta columna que es gestionada internamente por Laravel, podemos emplear una misma tabla pivote para mapear distintos tipos como usuarios, videos o publicaciones en nuestros modelos en Laravel de manera transparente para nosotros.

¿Qué son las relaciones polimórficas en Laravel?

Una relación polimórfica permite que un modelo esté asociado a varios otros modelos usando una sola relación flexible.

Por ejemplo: una tabla images puede estar vinculada tanto a users como a posts.

En lugar de crear una tabla por cada relación, Laravel utiliza columnas genéricas como imageable_id e imageable_type para guardar el ID y el modelo al que pertenece.

// Ejemplo simple
class Image extends Model {
   public function imageable(): MorphTo {
       return $this->morphTo();
   }
}

⚙️ Tipos de relaciones polimórficas

Laravel ofrece varios métodos según el tipo de relación que necesites:

Tipo    Método    Equivalente clásico
Uno a uno    morphOne()    hasOne()
Uno a muchos    morphMany()    hasMany()
Muchos a muchos    morphToMany() / morphedByMany()    belongsToMany()

Todos los tipos de relaciones polimorfismo, se emplea el prefijo de morph para definir las mismas:

  • morphOne(MODEL, PIVOTTABLE): Define una relación uno a uno polimórfica. Por ejemplo, una relación para el perfil puede emplearse para distintos tipos como usuarios, personas o empresas, su equivalente a las relaciones clásicas viene siendo el de tipo hasOne().
  • morphMany(MODEL, PIVOTTABLE): Se utiliza para definir una relación uno a muchos polimórfica. Por ejemplo, una relación de categorías puede estar relacionada con otros modelos como posts o vídeos, su equivalente a las relaciones clásicas vienen siendo hasMany().
  • ManyToMany:
    • morphToMany(MODEL, PIVOTTABLE): Este método se utiliza para definir una relación muchos a muchos polimórfica. Por ejemplo, la tabla tags que puede estar relacionada con diferentes tipos de modelos como posts o vídeos.
    • morphedByMany(MODEL, PIVOTTABLE): Este método se utiliza en un modelo para establecer una relación de muchos a muchos con otros modelos utilizando una relación polimórfica, esta relación se coloca al "etiquetable".

Con esta estructura, un mismo modelo puede ser compartido por múltiples entidades sin duplicar código.

Otro ejemplo de relación que puede ser de tipo polimórfica es el de documentos/comentarios, que pueden ser de una persona, publicación, usuario, entre otros este tipo de relación sería de uno a muchos de tipo polimórfica.

Anteriormente empleamos una relación polimórfica entre los usuarios y los tokens de autenticación mediante Sanctum.

Las relaciones polimórficas permiten que un registro en una tabla esté relacionado con múltiples modelos diferentes.

Las relaciones polimórficas en Laravel Eloquent son una herramienta poderosa para manejar situaciones en las que un registro puede estar relacionado con diferentes entidades. En lugar de crear tablas separadas para cada tipo de relación, las relaciones polimórficas nos permiten establecer conexiones flexibles entre modelos. Aquí tienes una introducción con ejemplos:

A diferencia de las relaciones tradicionales (como 1 a n o n a n), donde la relación es siempre fija, en las relaciones polimórficas, la relación puede variar según el registro.

Aunque comenzamos introduciendo el uso de las relaciones polimorfismo para las relaciones de tipo muchos a muchos, también las podemos emplear en el resto de las relaciones pero, es en el uso de las relaciones de tipo muchos a muchos que tiene principal importancia (y también las de uno a muchos).

Es importante notar que para la relación principal, la de etiquetas se emplea el método de morphToMany() para definir la relación, y para la "etiquetable" se emplea morphedByMany(), es decir, esta última sería la que tiene la relación polimórfica. 

La definición de estos métodos pueden ser un poco abstractos, así que veamos algunos ejemplos.

Ejemplo 1: Relación uno a muchos posts y tags

En este ejercicio, vamos a crear las relaciones entre etiquetas y posts, en donde un post puede tener de 0 a N etiquetas y una etiqueta puede estar asignada a 0 o N posts.

Comenzamos creando las migraciones:

$ php artisan make:migration create_tags_table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTagsTable extends Migration
{
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('tags');
    }
}

Otra migración para la tabla pivote:

$ php artisan make:migration create_taggables_table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTaggablesTable extends Migration
{
    public function up()
    {
        Schema::create('taggables', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('tag_id');             $table->unsignedBigInteger('taggable_id');
            $table->string('taggable_type'); // 'App\Models\Post'
            $table->timestamps();
            
            $table->unique(['tag_id', 'taggable_id', 'taggable_type']);
            $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
        });
    }

    public function down()
    {
        Schema::dropIfExists('taggables');
    }
} 

Que usualmente se le coloca el sufijo de able como en el caso anterior.

Y en los modelos:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

En el controlador de posts, podemos realizar algunas modificaciones para la asignación de etiquetas a los posts:

class PostController extends Controller
{
    public function create()
    {
        $tags = Tag::pluck('id', 'title');
        $categories = Category::pluck('id', 'title');
        $post = new Post();
        return view('dashboard.post.create', compact('post', 'categories''tags'));
    }

    public function store(StorePostPost $request)
    {
        $post = Post::create($requestData);
        $post->tags()->sync($request->tags_id);
        ***
    }
    
    public function edit(Post $post)
    {
        $tags = Tag::pluck('id', 'title');
        ***
        return view('dashboard.post.edit', compact('post', 'categories''tags'));
    }

    public function update(UpdatePostPut $request, Post $post)
    {
        //$post->tags()->attach(1);
        $post->tags()->sync($request->tags_id);
        ***
    }
}

En cuanto a la vista, queda como:

<div class="mt-3">
    <label for="tag_id">Tags</label>
    <select multiple class="form-control" name="tags_id[]" id="tags_id">
        @foreach ($tags as $title => $id)
    <option {{ in_array($id, old('tags_id') ?: $post->tags->pluck("id")->toArray()) ? "selected": "" }} value="{{ $id }}">{{ $title }}</option>
        @endforeach
    </select>
</div>

Con el código anterior, creamos un listado de selección múltiple de todas las etiquetas, las etiquetas que se encuentren asignadas al post, se encuentran seleccionadas por defecto.

También creamos el proceso CRUD para las etiquetas:

<?php

namespace App\Http\Controllers\Dashboard;

use App\Models\Tag;
use App\Http\Controllers\Controller;
use App\Http\Requests\Tag\PutRequest;
use App\Http\Requests\Tag\StoreRequest;

class TagController extends Controller
{

    public function index()
    {

        if (!auth()->user()->hasPermissionTo('editor.tag.index')) {
            return abort(403);
        }

        $tags = Tag::paginate(2);
        return view('dashboard/tag/index', compact('tags'));
    }

    public function create()
    {
        if (!auth()->user()->hasPermissionTo('editor.tag.create')) {
            return abort(403);
        }
        $tag = new Tag();
        return view('dashboard.tag.create', compact('tag'));
    }

    public function store(StoreRequest $request)
    {
        if (!auth()->user()->hasPermissionTo('editor.tag.create')) {
            return abort(403);
        }
        Tag::create($request->validated());
        return to_route('tag.index')->with('status', 'Tag created');
    }

    public function show(Tag $tag)
    {
        if (!auth()->user()->hasPermissionTo('editor.tag.index')) {
            return abort(403);
        }
        return view('dashboard/tag/show', ['tag' => $tag]);
    }

    public function edit(Tag $tag)
    {
        if (!auth()->user()->hasPermissionTo('editor.tag.update')) {
            return abort(403);
        }
        return view('dashboard.tag.edit', compact('tag'));
    }

    public function update(PutRequest $request, Tag $tag)
    {
        if (!auth()->user()->hasPermissionTo('editor.tag.update')) {
            return abort(403);
        }
        $tag->update($request->validated());
        return to_route('tag.index')->with('status', 'Tag updated');
    }

    public function destroy(Tag $tag)
    {
        if (!auth()->user()->hasPermissionTo('editor.tag.destroy')) {
            return abort(403);
        }
        $tag->delete();
        return to_route('tag.index')->with('status', 'Tag delete');
    }
}

En el código anterior solamente se muestra el controlador, debes de implementar el resto del código como lo son las clases Requests, vistas, rutas y permisos asociados, cualquier duda, puedes consultar el código fuente al final de la sección.

Ahora, veremos algunos otros ejemplos que fueron tomados de la documentación oficial y que puedes tomar de referencia para conocer cómo emplear el resto de los tipos de relaciones disponibles, si pudistes entender la relación polimórfica de tipo muchos a muchos que presentamos antes, estos serán muchos más fáciles de comprender.

Ejemplo 2: Relación uno a muchos

En este ejemplo tenemos como modelo principal el de comentarios, como modelos de tipo "able/seleccionable" tenemos los de vídeos y posts, es decir, los comentarios pueden ser empleados por la entidad de posts y vídeos:

posts
    id - integer
    title - string
    body - text
 
videos
    id - integer
    title - string
    url - string
 
comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

En cuanto a los modelos, quedan como:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class Comment extends Model
{
    /**
     * Get the parent commentable model (post or video).
     */
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Ejemplo 3: Relación uno a uno

En este ejemplo tenemos como modelo principal el de imágenes, como modelos de tipo "able/seleccionable" tenemos los de usuarios y posts, es decir, estas entidades tienen una imagen asociada

posts
    id - integer
    name - string
 
users
    id - integer
    name - string
 
images
    id - integer
    url - string
    imageable_id - integer
    imageable_type - string

La migración:

Schema::create('images', function (Blueprint $table) {
   $table->id();
   $table->string('url');
   $table->morphs('imageable'); // Crea imageable_id y imageable_type
   $table->timestamps();
});

En cuanto a los modelos, quedan como:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class Image extends Model
{
    /**
     * Get the parent imageable model (user or post).
     */
    public function imageable(): MorphTo
    {
        return $this->morphTo();
    }
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
 
class Post extends Model
{
    /**
     * Get the post's image.
     */
    public function image(): MorphOne
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}
 
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
 
class User extends Model
{
    /**
     * Get the user's image.
     */
    public function image(): MorphOne
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

Ejemplo 4: Relación uno a muchos libros y pagos, leftJoin y relaciones polimórficas opcionales 1-N

Video thumbnail

En mi proyecto personal —una API para venta de libros que también maneja cursos y archivos— aproveché las relaciones polimórficas para unificar el sistema de pagos (FilePayment).

Cada compra pertenece a un recurso diferente (libro, curso, etc.), pero la estructura de pago es la misma.
En lugar de duplicar las tablas, utilicé una relación uno a muchos polimórfica:

class FilePayment extends Model {
   public function filePaymentable(): MorphTo {
       return $this->morphTo();
   }
}
class Book extends Model {
   public function filePayments() {
       return $this->morphMany(FilePayment::class, 'filePaymentable');
   }
}

Ejemplo 5: Relación de tipo muchos a muchos inversas en Laravel

Video thumbnail

Muchas veces es necesario obtener los registros de una entidad principal dado la secundaria en una relación de tipo muchos a muchos; la relación típica es que tenemos una relación de tipo muchos a muchos entre los posts y etiquetas o tags y queremos obtener los posts que pertenezcan a ciertas etiquetas; para ello, podemos hacer una consulta como la siguiente:

 Post::whereHas('tags', function($q) {
       $q->where('tag_id', 1);
    })->whereHas('tags', function($q) {
       $q->where('tag_id', 4);
    })->get();

O si las etiquetas son dinámicas:

$tag_ids=[25,40,30];
    Post::where(function($query)use($tag_ids){
        foreach ($id as $value){
            $query->whereHas('tags',function ($query)use($value){
                $query->where('tag_id',$value);
            });
        }
    })->get();

El modelo de post luce como:

class Post extends Model
{
    public function tags()
    {
        return $this->belongsToMany(Tag::class, 'post_tags', 'post_id', 'tag_id');
    }
}

Validación con instanceof en relaciones polimórficas

Video thumbnail

Otra situación útil: validar el tipo real del modelo asociado antes de operar sobre él; te quiero mostrar un posible uso del instanceof en las relaciones polimórficas en Laravel.

Por ejemplo, al descargar un archivo, quiero asegurarme de que realmente pertenece a un libro:

if ($file->fileable instanceof Book) {
   // Ejecutar lógica de descarga segura
}

El modelo es este:

class Book extends Model
{
  // ...
  public function files()
  {
      return $this->morphMany(File::class, 'fileable');
  }
}

Estas validaciones evita errores o posibles vulnerabilidades si un usuario intenta acceder a un recurso que no le corresponde.

Un libro puede tener de cero a muchos archivos, tal como se observa.

Por lo tanto, es una relación de uno a muchos entre libros y archivos.

Y esos archivos pueden pertenecer a diferentes tipos de modelos: libros, imágenes, videos, etc.

En un futuro, los archivos podrían estar asociados a videos, imágenes u otros modelos, por lo tanto, queremos evitar cualquier posible problema —ya sea por un error de lógica o una manipulación indebida por parte del usuario—.

class File extends Model
{
  // ...
  protected $fillable = ['file', 'type', 'fileable_type', 'fileable_id'];
  public function fileable(): MorphTo
  {
      return $this->morphTo();
  }
}

En resumen, el uso de instanceof dentro de las relaciones polimórficas nos permite asegurarnos de que el objeto que estamos procesando corresponde al tipo de modelo esperado.

De esta forma, cuando procesemos un archivo relacionado a un libro, podemos tener la certeza de que realmente pertenece a un Book, evitando errores en los procesos posteriores o vulnerabilidades dentro de la aplicación.

Buenas prácticas y errores comunes

✅ Buenas prácticas:

  • Usa nombres consistentes (*_able) en las columnas polimórficas.
  • Coloca los filtros de relación dentro del closure del join.
  • Valida tipos con instanceof cuando trabajes con recursos dinámicos.
  • Documenta claramente qué modelos pueden usar la relación.

❌ Errores comunes:

  • Usar join en lugar de leftJoin cuando necesitas todos los registros.
  • Duplicar tablas pivote en lugar de reutilizarlas polimórficamente.
  • No definir correctamente los tipos en las migraciones (morphs() lo soluciona).

Conclusión

Las relaciones polimórficas en Laravel son una herramienta poderosa que te ahorra tiempo y código cuando manejas modelos reutilizables.

Dominar este patrón te permite crear arquitecturas limpias, escalables y fáciles de mantener.

En resumen, en los dos últimos ejemplos vemos que tenemos que emplear el método morphTo() para la relación principal y para los de tipo "able/seleccionable" se emplean los de tipo morphMany()morphOne() respectivamente.

Finalmente, los métodos tipo morph() para especificar las relaciones reciben varios parámetros que pueden ser útiles si Laravel no logra interpretar de manera correcta el nombre de la tabla y relación, por ejemplo:

return $this->morphToMany(Tag::class, 'taggable', 'taggables', 'taggable_id');

Como consideración adicional, como comentamos anteriormente, las relaciones muchos a muchos y uno a muchos de tipo polimorfismo son las más interesantes en este tipo de relaciones ya que, al querer hacer 'etiquetable/able' una relación de tipo muchos a muchos sin ser polimórfica, tendríamos que duplicar la tabla pivote para tal fin (y en la de uno a muchos no podríamos crearla para que simule una del tipo polimórfica del mismo tipo), pero, con el uso de las relaciones polimórficas podemos emplear la misma tabla; en contraparte, el uso de las relaciones polimórficas para la relación de tipo uno a uno puede ser fácilmente manejados mediante una relación tradicional del mismo tipo, es decir, que no sean polimórficas, por ejemplo, recordemos que para las relaciones de tipo uno a uno tenemos:

posts
    id - integer
    name - string
 
users
    id - integer
    name - string
 
images
    id - integer
    url - string
    imageable_id - integer
    imageable_type - string

Pudiéramos tener una relación similar a la anterior en donde los posts y usuarios puedan tener una imagen mediante:

posts
    id - integer
    name - string
    image_id - integer
 
users
    id - integer
    name - string
    image_id - integer
 
images
    id - integer
    url - string

Más información sobre las relaciones en:

https://laravel.com/docs/master/eloquent-relationships

❓ Preguntas frecuentes

¿Cuándo conviene usar relaciones polimórficas?
Cuando un modelo (como Image o FilePayment) puede relacionarse con múltiples entidades sin repetir estructura.

¿Qué diferencia hay entre morphToMany y morphedByMany?
morphToMany se usa en el modelo que tiene la relación, mientras que morphedByMany se usa en el modelo que “recibe” esa relación.

¿Puedo usar morphs() en migraciones para todos los casos?
Sí, morphs('nombre') crea automáticamente las columnas nombre_id y nombre_type.

¿Qué son las relaciones mórficas en Laravel y por qué existen?

Video thumbnail

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.

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.

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.

El siguiente paso es aprender a manejar el problema de N+1 en Laravel, el cual es muy propenso con las relaciones de tipo muchos a uno/muchos.

Veremos que son y como emplear las relaciones de tipo polimórficas en Laravel de tipo uno a uno, uno a muchos y muchos a muchos.

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz

EN In english